content: /**
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.activemq.transport.amqp.sasl;
  
  import java.security.Principal;
  import java.security.cert.X509Certificate;
  import java.util.Set;
  
  import org.apache.activemq.broker.BrokerService;
  import org.apache.activemq.command.ConnectionInfo;
  import org.apache.activemq.security.AuthenticationBroker;
  import org.apache.activemq.security.SecurityContext;
  import org.apache.activemq.transport.amqp.AmqpTransport;
  import org.apache.qpid.proton.engine.Sasl;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * SASL Authentication engine.
   */
  public class AmqpAuthenticator {
  
      private static final Logger LOG = LoggerFactory.getLogger(AmqpAuthenticator.class);
  
      private static final String[] mechanisms = new String[] { "PLAIN", "ANONYMOUS" };
  
      private final BrokerService brokerService;
      private final AmqpTransport transport;
      private final Sasl sasl;
  
      private AuthenticationBroker authenticator;
  
      public AmqpAuthenticator(AmqpTransport transport, Sasl sasl, BrokerService brokerService) {
          this.brokerService = brokerService;
          this.transport = transport;
          this.sasl = sasl;
  
          sasl.setMechanisms(mechanisms);
          sasl.server();
      }
  
      /**
       * @return true if the SASL exchange has completed, regardless of success.
       */
      public boolean isDone() {
          return sasl.getOutcome() != Sasl.SaslOutcome.PN_SASL_NONE;
      }
  
      /**
       * @return the list of all SASL mechanisms that are supported currently.
       */
      public String[] getSupportedMechanisms() {
          return mechanisms;
      }
  
      public void processSaslExchange(ConnectionInfo connectionInfo) {
          if (sasl.getRemoteMechanisms().length > 0) {
  
              SaslMechanism mechanism = getSaslMechanism(sasl.getRemoteMechanisms());
              if (mechanism != null) {
                  LOG.debug("SASL [{}} Handshake started.", mechanism.getMechanismName());
  
                  mechanism.processSaslStep(sasl);
                  if (!mechanism.isFailed()) {
  
                      connectionInfo.setUserName(mechanism.getUsername());
                      connectionInfo.setPassword(mechanism.getPassword());
  
                      if (tryAuthenticate(connectionInfo, transport.getPeerCertificates())) {
                          sasl.done(Sasl.SaslOutcome.PN_SASL_OK);
                      } else {
                          sasl.done(Sasl.SaslOutcome.PN_SASL_AUTH);
                      }
  
                      LOG.debug("SASL [{}} Handshake complete.", mechanism.getMechanismName());
                  } else {
                      LOG.debug("SASL [{}} Handshake failed: {}", mechanism.getMechanismName(), mechanism.getFailureReason());
                      sasl.done(Sasl.SaslOutcome.PN_SASL_AUTH);
                  }
              } else {
                  LOG.info("SASL: could not find supported mechanism");
                  sasl.done(Sasl.SaslOutcome.PN_SASL_PERM);
              }
          }
      }
  
      //----- Internal implementation ------------------------------------------//
  
      private SaslMechanism getSaslMechanism(String[] remoteMechanisms) {
          String primary = remoteMechanisms[0];
  
          if (primary.equalsIgnoreCase("PLAIN")) {
              return new PlainMechanism();
          } else if (primary.equalsIgnoreCase("ANONYMOUS")) {
              return new AnonymousMechanism();
          }
  
          return null;
      }
  
      private boolean tryAuthenticate(ConnectionInfo info, X509Certificate[] peerCertificates) {
          try {
              return getAuthenticator().authenticate(info.getUserName(), info.getPassword(), peerCertificates) != null;
          } catch (Throwable error) {
              return false;
          }
      }
  
      private AuthenticationBroker getAuthenticator() {
          if (authenticator == null) {
              try {
                  authenticator = (AuthenticationBroker) brokerService.getBroker().getAdaptor(AuthenticationBroker.class);
              } catch (Exception e) {
                  LOG.debug("Failed to lookup AuthenticationBroker from Broker, will use a default Noop version.");
              }
  
              if (authenticator == null) {
                  authenticator = new DefaultAuthenticationBroker();
              }
          }
  
          return authenticator;
      }
  
      private class DefaultAuthenticationBroker implements AuthenticationBroker {
  
          @Override
          public SecurityContext authenticate(String username, String password, X509Certificate[] peerCertificates) throws SecurityException {
              return new SecurityContext(username) {
  
                  @Override
                  public Set<Principal> getPrincipals() {
                      return null;
                  }
              };
          }
      }
  }
  