Save This Page
Home » jboss-5.0.0.CR1-src » org » jboss » security » auth » spi » [javadoc | source]
    1   /*
    2    * JBoss, the OpenSource WebOS
    3    *
    4    * Distributable under LGPL license.
    5    * See terms of license at gnu.org.
    6    */
    7   package org.jboss.security.auth.spi;
    8   
    9   import java.security.acl.Group;
   10   import java.util.Iterator;
   11   import java.util.Map.Entry;
   12   import java.util.Properties;
   13   import javax.naming.Context;
   14   import javax.naming.NamingEnumeration;
   15   import javax.naming.NamingException;
   16   import javax.naming.directory.Attribute;
   17   import javax.naming.directory.Attributes;
   18   import javax.naming.directory.BasicAttributes;
   19   import javax.naming.directory.SearchResult;
   20   import javax.naming.ldap.InitialLdapContext;
   21   import javax.security.auth.login.LoginException;
   22   
   23   import org.jboss.security.SimpleGroup;
   24   import org.jboss.security.SimplePrincipal;
   25   import org.jboss.security.auth.spi.UsernamePasswordLoginModule;
   26   
   27   /**
   28    * An implementation of LoginModule that authenticates against an LDAP server
   29    * using JNDI, based on the configuration properties.
   30    * <p>
   31    * The LoginModule options include whatever options your LDAP JNDI provider
   32    * supports. Examples of standard property names are:
   33    * <ul>
   34    * <li><code>Context.INITIAL_CONTEXT_FACTORY = "java.naming.factory.initial"</code>
   35    * <li><code>Context.SECURITY_PROTOCOL = "java.naming.security.protocol"</code>
   36    * <li><code>Context.PROVIDER_URL = "java.naming.provider.url"</code>
   37    * <li><code>Context.SECURITY_AUTHENTICATION = "java.naming.security.authentication"</code>
   38    * </ul>
   39    * <p>
   40    * The Context.SECURITY_PRINCIPAL is set to the distinguished name of the user
   41    * as obtained by the callback handler and the Context.SECURITY_CREDENTIALS
   42    * property is either set to the String password or Object credential depending
   43    * on the useObjectCredential option.
   44    * <p>
   45    * Additional module properties include:
   46    * <ul>
   47    * <li>principalDNPrefix, principalDNSuffix : A prefix and suffix to add to the
   48    * username when forming the user distiguished name. This is useful if you
   49    * prompt a user for a username and you don't want them to have to enter the
   50    * fully distinguished name. Using this property and principalDNSuffix the
   51    * userDN will be formed as:
   52    * <pre>
   53    *    String userDN = principalDNPrefix + username + principalDNSuffix;
   54    * </pre>
   55    * <li>useObjectCredential : indicates that the credential should be obtained as
   56    * an opaque Object using the <code>org.jboss.security.plugins.ObjectCallback</code> type
   57    * of Callback rather than as a char[] password using a JAAS PasswordCallback.
   58    * <li>rolesCtxDN : The fixed distinguished name to the context to search for user roles.
   59    * <li>userRolesCtxDNAttributeName : The name of an attribute in the user
   60    * object that contains the distinguished name to the context to search for
   61    * user roles. This differs from rolesCtxDN in that the context to search for a
   62    * user's roles can be unique for each user.
   63    * <li>roleAttributeName : The name of the attribute that contains the user roles
   64    * <li>uidAttributeName : The name of the attribute that in the object containing
   65    * the user roles that corresponds to the userid. This is used to locate the
   66    * user roles.
   67    * <li>matchOnUserDN : A flag indicating if the search for user roles should match
   68    * on the user's fully distinguished name. If false just the username is used
   69    * as the match value. If true, the userDN is used as the match value.
   70    * <li>allowEmptyPasswords : A flag indicating if empty(length==0) passwords
   71    * should be passed to the ldap server. An empty password is treated as an
   72    * anonymous login by some ldap servers and this may not be a desirable
   73    * feature. Set this to false to reject empty passwords, true to have the ldap
   74    * server validate the empty password. The default is true.
   75    *
   76    * <li>roleAttributeIsDN : A flag indicating whether the user's role attribute
   77    * contains the fully distinguished name of a role object, or the users's role
   78    * attribute contains the role name. If false, the role name is taken from the
   79    * value of the user's role attribute. If true, the role attribute represents
   80    * the distinguished name of a role object.  The role name is taken from the
   81    * value of the `roleNameAttributeId` attribute of the corresponding object.  In
   82    * certain directory schemas (e.g., Microsoft Active Directory), role (group)
   83    * attributes in the user object are stored as DNs to role objects instead of
   84    * as simple names, in which case, this property should be set to true.
   85    * The default value of this property is false.
   86    * <li>roleNameAttributeID : The name of the attribute of the role object which
   87    * corresponds to the name of the role.  If the `roleAttributeIsDN` property is
   88    * set to true, this property is used to find the role object's name attribute.
   89    * If the `roleAttributeIsDN` property is set to false, this property is ignored.
   90    * </ul>
   91    * A sample login config:
   92    * <p>
   93   <pre>
   94   testLdap {
   95       org.jboss.security.auth.spi.LdapLoginModule required
   96         java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
   97         java.naming.provider.url="ldap://ldaphost.jboss.org:1389/"
   98         java.naming.security.authentication=simple
   99         principalDNPrefix=uid=
  100         uidAttributeID=userid
  101         roleAttributeID=roleName
  102         principalDNSuffix=,ou=People,o=jboss.org
  103         rolesCtxDN=cn=JBossSX Tests,ou=Roles,o=jboss.org
  104   };
  105   
  106   testLdap2 {
  107       org.jboss.security.auth.spi.LdapLoginModule required
  108         java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
  109         java.naming.provider.url="ldap://ldaphost.jboss.org:1389/"
  110         java.naming.security.authentication=simple
  111         principalDNPrefix=uid=
  112         uidAttributeID=userid
  113         roleAttributeID=roleName
  114         principalDNSuffix=,ou=People,o=jboss.org
  115         userRolesCtxDNAttributeName=ou=Roles,dc=user1,dc=com
  116   };
  117   
  118   testLdapToActiveDirectory {
  119      org.jboss.security.auth.spi.LdapLoginModule required
  120        java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
  121        java.naming.provider.url="ldap://ldaphost.jboss.org:1389/"
  122        java.naming.security.authentication=simple
  123        rolesCtxDN=cn=Users,dc=ldaphost,dc=jboss,dc=org
  124        uidAttributeID=userPrincipalName
  125        roleAttributeID=memberOf
  126        roleAttributeIsDN=true
  127        roleNameAttributeID=name
  128   };
  129    </pre>
  130    *
  131    * @author Scott.Stark@jboss.org
  132    * @version $Revision: 1.7.4.5 $
  133    */
  134   public class LdapLoginModule extends UsernamePasswordLoginModule
  135   {
  136      private static final String USE_OBJECT_CREDENTIAL_OPT = "useObjectCredential";
  137      private static final String PRINCIPAL_DN_PREFIX_OPT = "principalDNPrefix";
  138      private static final String PRINCIPAL_DN_SUFFIX_OPT = "principalDNSuffix";
  139      private static final String ROLES_CTX_DN_OPT = "rolesCtxDN";
  140      private static final String USER_ROLES_CTX_DN_ATTRIBUTE_ID_OPT =
  141         "userRolesCtxDNAttributeName";
  142      private static final String UID_ATTRIBUTE_ID_OPT = "uidAttributeID";
  143      private static final String ROLE_ATTRIBUTE_ID_OPT = "roleAttributeID";
  144      private static final String MATCH_ON_USER_DN_OPT = "matchOnUserDN";
  145      private static final String ROLE_ATTRIBUTE_IS_DN_OPT = "roleAttributeIsDN";
  146      private static final String ROLE_NAME_ATTRIBUTE_ID_OPT = "roleNameAttributeID";
  147   
  148      public LdapLoginModule()
  149      {
  150      }
  151      
  152      private transient SimpleGroup userRoles = new SimpleGroup("Roles");
  153   
  154      /** Overriden to return an empty password string as typically one cannot
  155       obtain a user's password. We also override the validatePassword so
  156       this is ok.
  157       @return and empty password String
  158       */
  159      protected String getUsersPassword() throws LoginException
  160      {
  161         return "";
  162      }
  163      /** Overriden by subclasses to return the Groups that correspond to the
  164        to the role sets assigned to the user. Subclasses should create at
  165        least a Group named "Roles" that contains the roles assigned to the user.
  166        A second common group is "CallerPrincipal" that provides the application
  167        identity of the user rather than the security domain identity.
  168      @return Group[] containing the sets of roles 
  169      */
  170      protected Group[] getRoleSets() throws LoginException
  171      {
  172         Group[] roleSets = {userRoles};
  173         return roleSets;
  174      }
  175   
  176      /** Validate the inputPassword by creating a ldap InitialContext with the
  177       SECURITY_CREDENTIALS set to the password.
  178   
  179       @param inputPassword the password to validate.
  180       @param expectedPassword ignored
  181       */
  182      protected boolean validatePassword(String inputPassword, String expectedPassword)
  183      {
  184         boolean isValid = false;
  185         if( inputPassword != null )
  186         {
  187            // See if this is an empty password that should be disallowed
  188            if( inputPassword.length() == 0 )
  189            {
  190               // Check for an allowEmptyPasswords option
  191               boolean allowEmptyPasswords = true;
  192               String flag = (String) options.get("allowEmptyPasswords");
  193               if( flag != null )
  194                  allowEmptyPasswords = Boolean.valueOf(flag).booleanValue();
  195               if( allowEmptyPasswords == false )
  196               {
  197                  super.log.trace("Rejecting empty password due to allowEmptyPasswords");
  198                  return false;
  199               }
  200            }
  201   
  202            try
  203            {
  204               // Validate the password by trying to create an initial context
  205               String username = getUsername();
  206               createLdapInitContext(username, inputPassword);
  207               isValid = true;
  208            }
  209            catch(NamingException e)
  210            {
  211               super.log.debug("Failed to validate password", e);
  212            }
  213         }
  214         return isValid;
  215      }
  216   
  217      private void createLdapInitContext(String username, Object credential) throws NamingException
  218      {
  219         Properties env = new Properties();
  220         // Map all option into the JNDI InitialLdapContext env
  221         Iterator iter = options.entrySet().iterator();
  222         while( iter.hasNext() )
  223         {
  224            Entry entry = (Entry) iter.next();
  225            env.put(entry.getKey(), entry.getValue());
  226         }
  227         
  228         // Set defaults for key values if they are missing
  229         String factoryName = env.getProperty(Context.INITIAL_CONTEXT_FACTORY);
  230         if( factoryName == null )
  231         {
  232            factoryName = "com.sun.jndi.ldap.LdapCtxFactory";
  233            env.setProperty(Context.INITIAL_CONTEXT_FACTORY, factoryName);
  234         }
  235         String authType = env.getProperty(Context.SECURITY_AUTHENTICATION);
  236         if( authType == null )
  237            env.setProperty(Context.SECURITY_AUTHENTICATION, "simple");
  238         String protocol = env.getProperty(Context.SECURITY_PROTOCOL);
  239         String providerURL = (String) options.get(Context.PROVIDER_URL);
  240         if( providerURL == null )
  241            providerURL = "ldap://localhost:" + ((protocol != null && protocol.equals("ssl")) ? "636" : "389");
  242   
  243         String principalDNPrefix = (String) options.get(PRINCIPAL_DN_PREFIX_OPT);
  244         if( principalDNPrefix == null )
  245            principalDNPrefix="";
  246         String principalDNSuffix = (String) options.get(PRINCIPAL_DN_SUFFIX_OPT);
  247         if( principalDNSuffix == null )
  248            principalDNSuffix="";
  249         String matchType = (String) options.get(MATCH_ON_USER_DN_OPT);
  250         boolean matchOnUserDN = Boolean.valueOf(matchType).booleanValue();
  251         String userDN = principalDNPrefix + username + principalDNSuffix;
  252         env.setProperty(Context.PROVIDER_URL, providerURL);
  253         env.setProperty(Context.SECURITY_PRINCIPAL, userDN);
  254         env.put(Context.SECURITY_CREDENTIALS, credential);
  255         super.log.trace("Logging into LDAP server, env="+env);
  256         InitialLdapContext ctx = new InitialLdapContext(env, null);
  257         super.log.trace("Logged into LDAP server, "+ctx);
  258         /* If a userRolesCtxDNAttributeName was speocified, see if there is a
  259          user specific roles DN. If there is not, the default rolesCtxDN will
  260          be used.
  261          */
  262         String rolesCtxDN = (String) options.get(ROLES_CTX_DN_OPT);
  263         String userRolesCtxDNAttributeName = (String) options.get(USER_ROLES_CTX_DN_ATTRIBUTE_ID_OPT);
  264         if( userRolesCtxDNAttributeName != null )
  265         {
  266            // Query the indicated attribute for the roles ctx DN to use
  267            String[] returnAttribute = {userRolesCtxDNAttributeName};
  268            try
  269            {
  270               Attributes result = ctx.getAttributes(userDN, returnAttribute);
  271               if (result.get(userRolesCtxDNAttributeName) != null)
  272               {
  273                  rolesCtxDN = result.get(userRolesCtxDNAttributeName).get().toString();
  274                  super.log.trace("Found user roles context DN: " + rolesCtxDN);
  275               }
  276            }
  277            catch(NamingException e)
  278            {
  279               super.log.debug("Failed to query userRolesCtxDNAttributeName", e);
  280            }
  281         }
  282   
  283         // Search for any roles associated with the user
  284         if( rolesCtxDN != null )
  285         {
  286            String uidAttrName = (String) options.get(UID_ATTRIBUTE_ID_OPT);
  287            if( uidAttrName == null )
  288               uidAttrName = "uid";
  289            String roleAttrName = (String) options.get(ROLE_ATTRIBUTE_ID_OPT);
  290            if( roleAttrName == null )
  291               roleAttrName = "roles";
  292            BasicAttributes matchAttrs = new BasicAttributes(true);
  293            if( matchOnUserDN == true )
  294               matchAttrs.put(uidAttrName, userDN);
  295            else
  296               matchAttrs.put(uidAttrName, username);
  297            String[] roleAttr = {roleAttrName};
  298            // Is user's role attribute a DN or the role name
  299            String roleAttributeIsDNOption = (String) options.get(ROLE_ATTRIBUTE_IS_DN_OPT);
  300            boolean roleAttributeIsDN = Boolean.valueOf(roleAttributeIsDNOption).booleanValue();
  301   
  302            // If user's role attribute is a DN, what is the role's name attribute
  303            // Default to 'name' (Group name attribute in Active Directory)
  304            String roleNameAttributeID = (String) options.get(ROLE_NAME_ATTRIBUTE_ID_OPT);
  305            if( roleNameAttributeID == null )
  306               roleNameAttributeID = "name";
  307   
  308            try
  309            {
  310               NamingEnumeration answer = ctx.search(rolesCtxDN, matchAttrs, roleAttr);
  311               while( answer.hasMore() )
  312               {
  313                  SearchResult sr = (SearchResult) answer.next();
  314                  Attributes attrs = sr.getAttributes();
  315                  Attribute roles = attrs.get(roleAttrName);
  316                  for(int r = 0; r < roles.size(); r ++)
  317                  {
  318                     Object value = roles.get(r);
  319                     String roleName = null;
  320                     if( roleAttributeIsDN == true )
  321                     {
  322                        // Query the roleDN location for the value of roleNameAttributeID
  323                        String roleDN = value.toString();
  324                        String[] returnAttribute = {roleNameAttributeID};
  325                        super.log.trace("Using roleDN: " + roleDN);
  326                        try
  327                        {
  328                            Attributes result = ctx.getAttributes(roleDN, returnAttribute);
  329                            if( result.get(roleNameAttributeID) != null )
  330                            {
  331                               roleName = result.get(roleNameAttributeID).get().toString();
  332                            }
  333                        }
  334                        catch(NamingException e)
  335                        {
  336                            super.log.trace("Failed to query roleNameAttrName", e);
  337                        }
  338                     }
  339                     else
  340                     {
  341                        // The role attribute value is the role name
  342                        roleName = value.toString();
  343                     }
  344   
  345                     if( roleName != null )
  346                     {
  347                       super.log.trace("Assign user to role " + roleName);
  348                       userRoles.addMember(new SimplePrincipal(roleName));
  349                     }
  350                  }
  351               }
  352            }
  353            catch(NamingException e)
  354            {
  355               log.trace("Failed to locate roles", e);
  356            }
  357         }
  358         // Close the context to release the connection
  359         ctx.close();
  360      }
  361   }

Save This Page
Home » jboss-5.0.0.CR1-src » org » jboss » security » auth » spi » [javadoc | source]