Package org.jboss.security.negotiation

Source Code of org.jboss.security.negotiation.AdvancedLdapLoginModule

/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.security.negotiation;

import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.acl.Group;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Map.Entry;

import javax.management.ObjectName;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

import org.jboss.security.SimpleGroup;
import org.jboss.security.auth.spi.AbstractServerLoginModule;
import org.jboss.security.negotiation.prototype.DecodeAction;

/**
* Another LDAP LoginModule to take into account requirements
* for different authentication mechanisms and full support
* for password-stacking set to useFirstPass.
*
* This is essentially a complete refactoring of the LdapExtLoginModule
* but with enough restructuring to separate out the three login steps: -
*  -1 Find the user
*  -2 Authenticate as the user
*  -3 Find the users roles
* Configuration should allow for any of the three actions to be
* skipped based on the requirements for the environment making
* use of this login module.
*
*
* @author darran.lofthouse@jboss.com
* @since 3rd July 2008
*/
public class AdvancedLdapLoginModule extends AbstractServerLoginModule
{

   /*
    * Configuration Option Constants
    */

   // Search Context Settings
   private static final String BIND_AUTHENTICATION = "bindAuthentication";

   private static final String BIND_DN = "bindDN";

   private static final String BIND_CREDENTIAL = "bindCredential";

   private static final String SECURITY_DOMAIN = "jaasSecurityDomain";

   // User Search Settings
   private static final String BASE_CTX_DN = "baseCtxDN";

   private static final String BASE_FILTER = "baseFilter";

   private static final String SEARCH_TIME_LIMIT = "searchTimeLimit";

   // Role Search Settings
   private static final String ROLES_CTS_DN = "rolesCtxDN";

   private static final String ROLE_FILTER = "roleFilter";

   private static final String RECURSE_ROLES = "recurseRoles";

   private static final String ROLE_ATTRIBUTE_ID = "roleAttributeID";

   private static final String ROLE_ATTRIBUTE_IS_DN = "roleAttributeIsDN";

   private static final String ROLE_NAME_ATTRIBUTE_ID = "roleNameAttributeID";

   private static final String ROLE_SEARCH_SCOPE = "searchScope";

   // Authentication Settings
   private static final String ALLOW_EMPTY_PASSWORD = "allowEmptyPassword";

   /*
    * Other Constants
    */

   private static final String AUTH_TYPE_GSSAPI = "GSSAPI";

   private static final String AUTH_TYPE_SIMPLE = "simple";

   private static final String DEFAULT_LDAP_CTX_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";

   private static final String DEFAULT_URL = "ldap://localhost:389";

   private static final String DEFAULT_SSL_URL = "ldap://localhost:686";

   private static final String PROTOCOL_SSL = "SSL";

   private static final String OBJECT_SCOPE = "OBJECT_SCOPE";

   private static final String ONELEVEL_SCOPE = "ONELEVEL_SCOPE";

   private static final String SUBTREE_SCOPE = "SUBTREE_SCOPE";

   /*
    * Configuration Options
    */
   // Search Context Settings
   protected String bindAuthentication;

   protected String bindDn;

   protected String bindCredential;

   protected String jaasSecurityDomain;

   // User Search Settings
   protected String baseCtxDN;

   protected String baseFilter;

   protected int searchTimeLimit = 10000;

   protected SearchControls userSearchControls;

   // Role Search Settings
   protected String rolesCtxDN;

   protected String roleFilter;

   protected boolean recurseRoles;

   protected SearchControls roleSearchControls;

   protected String roleAttributeID;

   protected boolean roleAttributeIsDN;

   protected String roleNameAttributeID;

   // Authentication Settings
   protected boolean allowEmptyPassword;

   /*
    * Module State
    */
   /** The login identity */
   private Principal identity;

   /** The proof of login identity */
   private char[] credential;

   private SimpleGroup userRoles = new SimpleGroup("Roles");

   private Set<String> processedRoleDNs = new HashSet<String>();

   @Override
   public void initialize(Subject subject, CallbackHandler handler, Map sharedState, Map options)
   {
      super.initialize(subject, handler, sharedState, options);

      // Search Context Settings
      bindAuthentication = (String) options.get(BIND_AUTHENTICATION);
      bindDn = (String) options.get(BIND_DN);
      bindCredential = (String) options.get(BIND_CREDENTIAL);
      jaasSecurityDomain = (String) options.get(SECURITY_DOMAIN);

      // User Search Settings
      baseCtxDN = (String) options.get(BASE_CTX_DN);
      baseFilter = (String) options.get(BASE_FILTER);

      String temp = (String) options.get(SEARCH_TIME_LIMIT);
      if (temp != null)
      {
         try
         {
            searchTimeLimit = Integer.parseInt(temp);
         }
         catch (NumberFormatException e)
         {
            log.warn("Failed to parse: " + temp + ", using searchTimeLimit=" + searchTimeLimit);
         }
      }

      userSearchControls = new SearchControls();
      userSearchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
      userSearchControls.setReturningAttributes(new String[0]);
      userSearchControls.setTimeLimit(searchTimeLimit);

      rolesCtxDN = (String) options.get(ROLES_CTS_DN);
      roleFilter = (String) options.get(ROLE_FILTER);

      temp = (String) options.get(RECURSE_ROLES);
      recurseRoles = Boolean.parseBoolean(temp);

      int searchScope = SearchControls.SUBTREE_SCOPE;
      temp = (String) options.get(ROLE_SEARCH_SCOPE);
      if (OBJECT_SCOPE.equalsIgnoreCase(temp))
      {
         searchScope = SearchControls.OBJECT_SCOPE;
      }
      else if (ONELEVEL_SCOPE.equalsIgnoreCase(temp))
      {
         searchScope = SearchControls.ONELEVEL_SCOPE;
      }
      if (SUBTREE_SCOPE.equalsIgnoreCase(temp))
      {
         searchScope = SearchControls.SUBTREE_SCOPE;
      }

      roleSearchControls = new SearchControls();
      roleSearchControls.setSearchScope(searchScope);
      roleSearchControls.setReturningAttributes(new String[0]);
      roleSearchControls.setTimeLimit(searchTimeLimit);

      roleAttributeID = (String) options.get(ROLE_ATTRIBUTE_ID);

      temp = (String) options.get(ROLE_ATTRIBUTE_IS_DN);
      roleAttributeIsDN = Boolean.parseBoolean(temp);

      roleNameAttributeID = (String) options.get(ROLE_NAME_ATTRIBUTE_ID);

      temp = (String) options.get(ALLOW_EMPTY_PASSWORD);
      allowEmptyPassword = Boolean.parseBoolean(temp);

   }

   @Override
   public boolean login() throws LoginException
   {
      Object result = null;

      AuthorizeAction action = new AuthorizeAction();
      if (AUTH_TYPE_GSSAPI.equals(bindAuthentication))
      {
         log.trace("Using GSSAPI to connect to LDAP");
         LoginContext lc = new LoginContext(jaasSecurityDomain);
         lc.login();
         Subject serverSubject = lc.getSubject();

         if (log.isDebugEnabled())
         {
            log.debug("Subject = " + serverSubject);
            log.debug("Logged in '" + lc + "' LoginContext");
         }

         result = Subject.doAs(serverSubject, action);
         lc.logout();
      }
      else
      {
         result = action.run();
      }

      if (result instanceof LoginException)
      {
         throw (LoginException) result;
      }

      return ((Boolean) result).booleanValue();
   }

   @Override
   protected Principal getIdentity()
   {
      return identity;
   }

   @Override
   protected Group[] getRoleSets() throws LoginException
   {
      Group[] roleSets =
      {userRoles};
      return roleSets;
   }

   protected Boolean innerLogin() throws LoginException
   {
      // Obtain the username and password
      processIdentityAndCredential();
      log.trace("Identity - " + getIdentity().getName());
      // Initialise search ctx
      String bindCredential = this.bindCredential;
      if (AUTH_TYPE_GSSAPI.equals(bindAuthentication) == false)
      {
         if (jaasSecurityDomain != null && jaasSecurityDomain.length() > 0)
         {
            try
            {
               ObjectName serviceName = new ObjectName(jaasSecurityDomain);
               char[] tmp = DecodeAction.decode(bindCredential, serviceName);
               bindCredential = new String(tmp);
            }
            catch (Exception e)
            {
               LoginException le = new LoginException("Unabe to decode bindCredential");
               le.initCause(e);
               throw le;
            }
         }
      }

      LdapContext searchContext = null;

      try
      {
         searchContext = constructLdapContext(bindDn, bindCredential, bindAuthentication);
         log.debug("Obtained LdapContext");

         // Search for user in LDAP
         String userDN = findUserDN(searchContext);

         // If authentication required authenticate as user
         if (super.loginOk == false)
         {
            authenticate(userDN);
         }

         if (super.loginOk)
         {
            // Search for roles in LDAP
            rolesSearch(searchContext, userDN);
         }
      }
      finally
      {
         if (searchContext != null)
         {
            try
            {
               searchContext.close();
            }
            catch (NamingException e)
            {
               log.warn("Error closing context", e);
            }
         }
      }

      return Boolean.valueOf(super.loginOk);
   }

   /**
    * Either retrieve existing values based on useFirstPass or use
    * CallBackHandler to obtain the values.
    */
   protected void processIdentityAndCredential() throws LoginException
   {
      if (super.login() == true)
      {
         Object username = sharedState.get("javax.security.auth.login.name");
         if (username instanceof Principal)
            identity = (Principal) username;
         else
         {
            String name = username.toString();
            try
            {
               identity = createIdentity(name);
            }
            catch (Exception e)
            {
               log.debug("Failed to create principal", e);
               throw new LoginException("Failed to create principal: " + e.getMessage());
            }
         }
         // We have no further use for a credential so no need to retrieve it.
      }
      else
      {
         try
         {
            NameCallback nc = new NameCallback("User name: ", "guest");
            PasswordCallback pc = new PasswordCallback("Password: ", false);
            Callback[] callbacks =
            {nc, pc};

            callbackHandler.handle(callbacks);
            String username = nc.getName();
            identity = createIdentity(username);
            credential = pc.getPassword();
            pc.clearPassword();
         }
         catch (Exception e)
         {
            LoginException le = new LoginException("Unable to obtain username/credential");
            le.initCause(e);
            throw le;
         }

      }
   }

   protected LdapContext constructLdapContext(String dn, Object credential, String authentication)
         throws LoginException
   {
      Properties env = new Properties();
      Iterator iter = options.entrySet().iterator();
      while (iter.hasNext())
      {
         Entry entry = (Entry) iter.next();
         env.put(entry.getKey(), entry.getValue());
      }

      // Set defaults for key values if they are missing
      String factoryName = env.getProperty(Context.INITIAL_CONTEXT_FACTORY);
      if (factoryName == null)
      {
         factoryName = DEFAULT_LDAP_CTX_FACTORY;
         env.setProperty(Context.INITIAL_CONTEXT_FACTORY, factoryName);
      }

      // If this method is called with an authentication type then use that.
      if (authentication != null && authentication.length() > 0)
      {
         env.setProperty(Context.SECURITY_AUTHENTICATION, authentication);
      }
      else
      {
         String authType = env.getProperty(Context.SECURITY_AUTHENTICATION);
         if (authType == null)
            env.setProperty(Context.SECURITY_AUTHENTICATION, AUTH_TYPE_SIMPLE);
      }

      String protocol = env.getProperty(Context.SECURITY_PROTOCOL);
      String providerURL = (String) options.get(Context.PROVIDER_URL);
      if (providerURL == null)
      {
         if (PROTOCOL_SSL.equals(protocol))
         {
            providerURL = DEFAULT_SSL_URL;
         }
         else
         {
            providerURL = DEFAULT_URL;
         }
         env.setProperty(Context.PROVIDER_URL, providerURL);
      }

      // Assume the caller of this method has checked the requirements for the principal and
      // credentials.
      if (dn != null)
         env.setProperty(Context.SECURITY_PRINCIPAL, dn);
      if (credential != null)
         env.put(Context.SECURITY_CREDENTIALS, credential);
      traceLdapEnv(env);
      try
      {
         return new InitialLdapContext(env, null);
      }
      catch (NamingException e)
      {
         LoginException le = new LoginException("Unable to create new InitialLdapContext");
         le.initCause(e);
         throw le;
      }
   }

   protected String findUserDN(LdapContext ctx) throws LoginException
   {

      if (baseCtxDN == null)
      {
         return getIdentity().getName();
      }

      try
      {
         NamingEnumeration results = null;

         Object[] filterArgs =
         {getIdentity().getName()};
         results = ctx.search(baseCtxDN, baseFilter, filterArgs, userSearchControls);
         if (results.hasMore() == false)
         {
            results.close();
            throw new LoginException("Search of baseDN(" + baseCtxDN + ") found no matches");
         }

         SearchResult sr = (SearchResult) results.next();
         String name = sr.getName();
         String userDN = null;
         if (sr.isRelative() == true)
            userDN = name + "," + baseCtxDN;
         else
            throw new LoginException("Can't follow referal for authentication: " + name);

         results.close();
         results = null;

         log.trace("findUserDN - " + userDN);
         return userDN;
      }
      catch (NamingException e)
      {
         LoginException le = new LoginException("Unable to find user DN");
         le.initCause(e);
         throw le;
      }
   }

   protected void authenticate(String userDN) throws LoginException
   {
      if (credential.length == 0)
      {
         if (allowEmptyPassword == false)
         {
            log.trace("Rejecting empty password.");
            return;
         }
      }

      try
      {
         LdapContext authContext = constructLdapContext(userDN, credential, null);
         authContext.close();
      }
      catch (NamingException ne)
      {
         log.debug("Authentication failed - " + ne.getMessage());
         LoginException le = new LoginException("Authentication failed");
         le.initCause(ne);
         throw le;
      }

      super.loginOk = true;
      if (getUseFirstPass() == true)
      { // Add the username and password to the shared state map
         sharedState.put("javax.security.auth.login.name", getIdentity().getName());
         sharedState.put("javax.security.auth.login.password", credential);
      }

   }

   protected void rolesSearch(LdapContext searchContext, String dn) throws LoginException
   {
      Object[] filterArgs =
      {getIdentity().getName(), dn};

      NamingEnumeration results = null;
      try
      {
         log.trace("rolesCtxDN=" + rolesCtxDN + " roleFilter=" + roleFilter + " filterArgs[0]=" + filterArgs[0]
               + " filterArgs[1]=" + filterArgs[1]);

         if (roleFilter != null && roleFilter.length() > 0)
         {
            results = searchContext.search(rolesCtxDN, roleFilter, filterArgs, roleSearchControls);
            while (results.hasMore())
            {
               SearchResult sr = (SearchResult) results.next();
               String resultDN = canonicalize(sr.getName());

               obtainRole(searchContext, resultDN);
            }
         }
         else
         {
            obtainRole(searchContext, dn);
         }

      }
      catch (NamingException e)
      {
         LoginException le = new LoginException("Error finding roles");
         le.initCause(e);
         throw le;
      }
      finally
      {
         if (results != null)
         {
            try
            {
               results.close();
            }
            catch (NamingException e)
            {
               log.warn("Problem closing results", e);
            }
         }
      }

   }

   protected void obtainRole(LdapContext searchContext, String dn) throws NamingException, LoginException
   {
      log.trace("rolesSearch resultDN = " + dn);

      String[] attrNames =
      {roleAttributeID};

      Attributes result = searchContext.getAttributes(dn, attrNames);
      if (result != null && result.size() > 0)
      {
         Attribute roles = result.get(roleAttributeID);
         for (int n = 0; n < roles.size(); n++)
         {
            String roleName = (String) roles.get(n);
            if (roleAttributeIsDN)
            {
               // Query the roleDN location for the value of roleNameAttributeID
               String roleDN = roleName;
               String[] returnAttribute =
               {roleNameAttributeID};
               log.trace("Using roleDN: " + roleDN);
               try
               {
                  Attributes result2 = searchContext.getAttributes(roleDN, returnAttribute);
                  Attribute roles2 = result2.get(roleNameAttributeID);
                  if (roles2 != null)
                  {
                     for (int m = 0; m < roles2.size(); m++)
                     {
                        roleName = (String) roles2.get(m);
                        addRole(roleName);
                     }
                  }
               }
               catch (NamingException e)
               {
                  log.trace("Failed to query roleNameAttrName", e);
               }

               if (recurseRoles)
               {
                  if (processedRoleDNs.contains(roleDN) == false)
                  {
                     processedRoleDNs.add(roleDN);
                     log.trace("Recursive search for '" + roleDN + "'");
                     rolesSearch(searchContext, roleDN);
                  }
                  else
                  {
                     log.trace("Already visited role '" + roleDN + "' ending recursion.");
                  }
               }
            }
            else
            {
               // The role attribute value is the role name
               addRole(roleName);
            }
         }
      }
   }

   protected void traceLdapEnv(Properties env)
   {
      if (log.isTraceEnabled())
      {
         Properties tmp = new Properties();
         tmp.putAll(env);
         String credentials = tmp.getProperty(Context.SECURITY_CREDENTIALS);
         if (credentials != null && credentials.length() > 0)
            tmp.setProperty(Context.SECURITY_CREDENTIALS, "***");
         log.trace("Logging into LDAP server, env=" + tmp.toString());
      }
   }

   private String canonicalize(String searchResult)
   {
      String result = searchResult;
      int len = searchResult.length();

      if (searchResult.endsWith("\""))
      {
         result = searchResult.substring(0, len - 1) + "," + rolesCtxDN + "\"";
      }
      else
      {
         result = searchResult + "," + rolesCtxDN;
      }
      return result;
   }

   private void addRole(String roleName)
   {
      if (roleName != null)
      {
         try
         {
            Principal p = super.createIdentity(roleName);
            if (log.isTraceEnabled())
               log.trace("Assign user '" + getIdentity().getName() + "' to role " + roleName);
            userRoles.addMember(p);
         }
         catch (Exception e)
         {
            log.debug("Failed to create principal: " + roleName, e);
         }
      }
   }

   private class AuthorizeAction implements PrivilegedAction<Object>
   {

      public Object run()
      {
         try
         {
            return innerLogin();
         }
         catch (LoginException e)
         {
            return e;
         }
      }

   }

}
TOP

Related Classes of org.jboss.security.negotiation.AdvancedLdapLoginModule

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.