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.jaas;
  
  import java.io.IOException;
  import java.security.Principal;
  import java.security.cert.X509Certificate;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Set;
  
  import javax.security.auth.Subject;
  import javax.security.auth.callback.Callback;
  import javax.security.auth.callback.CallbackHandler;
  import javax.security.auth.callback.UnsupportedCallbackException;
  import javax.security.auth.login.FailedLoginException;
  import javax.security.auth.login.LoginException;
  import javax.security.auth.spi.LoginModule;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * A LoginModule that allows for authentication based on SSL certificates.
   * Allows for subclasses to define methods used to verify user certificates and
   * find user groups. Uses CertificateCallbacks to retrieve certificates.
   * 
   * @author sepandm@gmail.com (Sepand)
   */
  public abstract class CertificateLoginModule extends PropertiesLoader implements LoginModule {
  
      private static final Logger LOG = LoggerFactory.getLogger(CertificateLoginModule.class);
  
      private CallbackHandler callbackHandler;
      private Subject subject;
  
      private String username;
      private Set<Principal> principals = new HashSet<Principal>();
  
      /** the authentication status*/
      private boolean succeeded = false;
      private boolean commitSucceeded = false;
  
      /**
       * Overriding to allow for proper initialization. Standard JAAS.
       */
      @Override
      public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
          this.subject = subject;
          this.callbackHandler = callbackHandler;
          init(options);
      }
  
      /**
       * Overriding to allow for certificate-based login. Standard JAAS.
       */
      @Override
      public boolean login() throws LoginException {
          Callback[] callbacks = new Callback[1];
  
          callbacks[0] = new CertificateCallback();
          try {
              callbackHandler.handle(callbacks);
          } catch (IOException ioe) {
              throw new LoginException(ioe.getMessage());
          } catch (UnsupportedCallbackException uce) {
              throw new LoginException(uce.getMessage() + " Unable to obtain client certificates.");
          }
          X509Certificate[] certificates = ((CertificateCallback)callbacks[0]).getCertificates();
  
          username = getUserNameForCertificates(certificates);
          if (username == null) {
              throw new FailedLoginException("No user for client certificate: " + getDistinguishedName(certificates));
          }
  
          if (debug) {
              LOG.debug("Certificate for user: " + username);
          }
          succeeded = true;
          return true;
      }
  
      /**
       * Overriding to complete login process. Standard JAAS.
       */
      @Override
      public boolean commit() throws LoginException {
          if (debug) {
              LOG.debug("commit");
          }
  
          if (!succeeded) {
              clear();
              return false;
          }
  
          principals.add(new UserPrincipal(username));
  
          for (String group : getUserGroups(username)) {
               principals.add(new GroupPrincipal(group));
          }
  
          subject.getPrincipals().addAll(principals);
  
          username = null;
          commitSucceeded = true;
          return true;
      }
  
      /**
       * Standard JAAS override.
       */
      @Override
      public boolean abort() throws LoginException {
          if (debug) {
              LOG.debug("abort");
          }
          if (!succeeded) {
              return false;
          } else if (succeeded && commitSucceeded) {
              // we succeeded, but another required module failed
              logout();
          } else {
              // our commit failed
              clear();
              succeeded = false;
          }
          return true;
      }
  
      /**
       * Standard JAAS override.
       */
      @Override
      public boolean logout() {
          subject.getPrincipals().removeAll(principals);
          clear();
  
          if (debug) {
              LOG.debug("logout");
          }
  
          succeeded = false;
          commitSucceeded = false;
          return true;
      }
  
      /**
       * Helper method.
       */
      private void clear() {
          username = null;
          principals.clear();
      }
  
      /**
       * Should return a unique name corresponding to the certificates given. The
       * name returned will be used to look up access levels as well as group
       * associations.
       * 
       * @param certs The distinguished name.
       * @return The unique name if the certificate is recognized, null otherwise.
       */
      protected abstract String getUserNameForCertificates(final X509Certificate[] certs) throws LoginException;
  
      /**
       * Should return a set of the groups this user belongs to. The groups
       * returned will be added to the user's credentials.
       * 
       * @param username The username of the client. This is the same name that
       *                getUserNameForDn returned for the user's DN.
       * @return A Set of the names of the groups this user belongs to.
       */
      protected abstract Set<String> getUserGroups(final String username) throws LoginException;
  
      protected String getDistinguishedName(final X509Certificate[] certs) {
          if (certs != null && certs.length > 0 && certs[0] != null) {
              return certs[0].getSubjectDN().getName();
          } else {
              return null;
          }
      }
  
  }
  