KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > security > auth > spi > LdapLoginModule


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 JavaDoc;
10 import java.security.Principal JavaDoc;
11 import java.util.Iterator JavaDoc;
12 import java.util.Map.Entry;
13 import java.util.Properties JavaDoc;
14 import javax.naming.Context JavaDoc;
15 import javax.naming.NamingEnumeration JavaDoc;
16 import javax.naming.NamingException JavaDoc;
17 import javax.naming.directory.Attribute JavaDoc;
18 import javax.naming.directory.Attributes JavaDoc;
19 import javax.naming.directory.BasicAttributes JavaDoc;
20 import javax.naming.directory.SearchResult JavaDoc;
21 import javax.naming.ldap.InitialLdapContext JavaDoc;
22 import javax.security.auth.login.LoginException JavaDoc;
23
24 import org.jboss.security.SimpleGroup;
25 import org.jboss.security.SimplePrincipal;
26 import org.jboss.security.auth.spi.UsernamePasswordLoginModule;
27
28 /**
29  * An implementation of LoginModule that authenticates against an LDAP server
30  * using JNDI, based on the configuration properties.
31  * <p>
32  * The LoginModule options include whatever options your LDAP JNDI provider
33  * supports. Examples of standard property names are:
34  * <ul>
35  * <li><code>Context.INITIAL_CONTEXT_FACTORY = "java.naming.factory.initial"</code>
36  * <li><code>Context.SECURITY_PROTOCOL = "java.naming.security.protocol"</code>
37  * <li><code>Context.PROVIDER_URL = "java.naming.provider.url"</code>
38  * <li><code>Context.SECURITY_AUTHENTICATION = "java.naming.security.authentication"</code>
39  * </ul>
40  * <p>
41  * The Context.SECURITY_PRINCIPAL is set to the distinguished name of the user
42  * as obtained by the callback handler and the Context.SECURITY_CREDENTIALS
43  * property is either set to the String password or Object credential depending
44  * on the useObjectCredential option.
45  * <p>
46  * Additional module properties include:
47  * <ul>
48  * <li>principalDNPrefix, principalDNSuffix : A prefix and suffix to add to the
49  * username when forming the user distiguished name. This is useful if you
50  * prompt a user for a username and you don't want them to have to enter the
51  * fully distinguished name. Using this property and principalDNSuffix the
52  * userDN will be formed as:
53  * <pre>
54  * String userDN = principalDNPrefix + username + principalDNSuffix;
55  * </pre>
56  * <li>useObjectCredential : indicates that the credential should be obtained as
57  * an opaque Object using the <code>org.jboss.security.plugins.ObjectCallback</code> type
58  * of Callback rather than as a char[] password using a JAAS PasswordCallback.
59  * <li>rolesCtxDN : The fixed distinguished name to the context to search for user roles.
60  * <li>userRolesCtxDNAttributeName : The name of an attribute in the user
61  * object that contains the distinguished name to the context to search for
62  * user roles. This differs from rolesCtxDN in that the context to search for a
63  * user's roles can be unique for each user.
64  * <li>roleAttributeName : The name of the attribute that contains the user roles
65  * <li>uidAttributeName : The name of the attribute that in the object containing
66  * the user roles that corresponds to the userid. This is used to locate the
67  * user roles.
68  * <li>matchOnUserDN : A flag indicating if the search for user roles should match
69  * on the user's fully distinguished name. If false just the username is used
70  * as the match value. If true, the userDN is used as the match value.
71  * <li>allowEmptyPasswords : A flag indicating if empty(length==0) passwords
72  * should be passed to the ldap server. An empty password is treated as an
73  * anonymous login by some ldap servers and this may not be a desirable
74  * feature. Set this to false to reject empty passwords, true to have the ldap
75  * server validate the empty password. The default is true.
76  *
77  * <li>roleAttributeIsDN : A flag indicating whether the user's role attribute
78  * contains the fully distinguished name of a role object, or the users's role
79  * attribute contains the role name. If false, the role name is taken from the
80  * value of the user's role attribute. If true, the role attribute represents
81  * the distinguished name of a role object. The role name is taken from the
82  * value of the `roleNameAttributeId` attribute of the corresponding object. In
83  * certain directory schemas (e.g., Microsoft Active Directory), role (group)
84  * attributes in the user object are stored as DNs to role objects instead of
85  * as simple names, in which case, this property should be set to true.
86  * The default value of this property is false.
87  * <li>roleNameAttributeID : The name of the attribute of the role object which
88  * corresponds to the name of the role. If the `roleAttributeIsDN` property is
89  * set to true, this property is used to find the role object's name attribute.
90  * If the `roleAttributeIsDN` property is set to false, this property is ignored.
91  * </ul>
92  * A sample login config:
93  * <p>
94  <pre>
95  testLdap {
96  org.jboss.security.auth.spi.LdapLoginModule required
97  java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
98  java.naming.provider.url="ldap://ldaphost.jboss.org:1389/"
99  java.naming.security.authentication=simple
100  principalDNPrefix=uid=
101  uidAttributeID=userid
102  roleAttributeID=roleName
103  principalDNSuffix=,ou=People,o=jboss.org
104  rolesCtxDN=cn=JBossSX Tests,ou=Roles,o=jboss.org
105  };
106
107  testLdap2 {
108  org.jboss.security.auth.spi.LdapLoginModule required
109  java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
110  java.naming.provider.url="ldap://ldaphost.jboss.org:1389/"
111  java.naming.security.authentication=simple
112  principalDNPrefix=uid=
113  uidAttributeID=userid
114  roleAttributeID=roleName
115  principalDNSuffix=,ou=People,o=jboss.org
116  userRolesCtxDNAttributeName=ou=Roles,dc=user1,dc=com
117  };
118
119  testLdapToActiveDirectory {
120  org.jboss.security.auth.spi.LdapLoginModule required
121  java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
122  java.naming.provider.url="ldap://ldaphost.jboss.org:1389/"
123  java.naming.security.authentication=simple
124  rolesCtxDN=cn=Users,dc=ldaphost,dc=jboss,dc=org
125  uidAttributeID=userPrincipalName
126  roleAttributeID=memberOf
127  roleAttributeIsDN=true
128  roleNameAttributeID=name
129  };
130  </pre>
131  *
132  * @author Scott.Stark@jboss.org
133  * @version $Revision: 1.14 $
134  */

135 public class LdapLoginModule extends UsernamePasswordLoginModule
136 {
137    private static final String JavaDoc USE_OBJECT_CREDENTIAL_OPT = "useObjectCredential";
138    private static final String JavaDoc PRINCIPAL_DN_PREFIX_OPT = "principalDNPrefix";
139    private static final String JavaDoc PRINCIPAL_DN_SUFFIX_OPT = "principalDNSuffix";
140    private static final String JavaDoc ROLES_CTX_DN_OPT = "rolesCtxDN";
141    private static final String JavaDoc USER_ROLES_CTX_DN_ATTRIBUTE_ID_OPT =
142       "userRolesCtxDNAttributeName";
143    private static final String JavaDoc UID_ATTRIBUTE_ID_OPT = "uidAttributeID";
144    private static final String JavaDoc ROLE_ATTRIBUTE_ID_OPT = "roleAttributeID";
145    private static final String JavaDoc MATCH_ON_USER_DN_OPT = "matchOnUserDN";
146    private static final String JavaDoc ROLE_ATTRIBUTE_IS_DN_OPT = "roleAttributeIsDN";
147    private static final String JavaDoc ROLE_NAME_ATTRIBUTE_ID_OPT = "roleNameAttributeID";
148
149    public LdapLoginModule()
150    {
151    }
152
153    private transient SimpleGroup userRoles = new SimpleGroup("Roles");
154
155    /** Overriden to return an empty password string as typically one cannot
156     obtain a user's password. We also override the validatePassword so
157     this is ok.
158     @return and empty password String
159     */

160    protected String JavaDoc getUsersPassword() throws LoginException JavaDoc
161    {
162       return "";
163    }
164
165    /** Overriden by subclasses to return the Groups that correspond to the
166     to the role sets assigned to the user. Subclasses should create at
167     least a Group named "Roles" that contains the roles assigned to the user.
168     A second common group is "CallerPrincipal" that provides the application
169     identity of the user rather than the security domain identity.
170     @return Group[] containing the sets of roles
171     */

172    protected Group JavaDoc[] getRoleSets() throws LoginException JavaDoc
173    {
174       Group JavaDoc[] roleSets = {userRoles};
175       return roleSets;
176    }
177
178    /** Validate the inputPassword by creating a ldap InitialContext with the
179     SECURITY_CREDENTIALS set to the password.
180
181     @param inputPassword the password to validate.
182     @param expectedPassword ignored
183     */

184    protected boolean validatePassword(String JavaDoc inputPassword, String JavaDoc expectedPassword)
185    {
186       boolean isValid = false;
187       if (inputPassword != null)
188       {
189          // See if this is an empty password that should be disallowed
190
if (inputPassword.length() == 0)
191          {
192             // Check for an allowEmptyPasswords option
193
boolean allowEmptyPasswords = true;
194             String JavaDoc flag = (String JavaDoc) options.get("allowEmptyPasswords");
195             if (flag != null)
196                allowEmptyPasswords = Boolean.valueOf(flag).booleanValue();
197             if (allowEmptyPasswords == false)
198             {
199                super.log.trace("Rejecting empty password due to allowEmptyPasswords");
200                return false;
201             }
202          }
203
204          try
205          {
206             // Validate the password by trying to create an initial context
207
String JavaDoc username = getUsername();
208             createLdapInitContext(username, inputPassword);
209             isValid = true;
210          }
211          catch (NamingException JavaDoc e)
212          {
213             super.log.debug("Failed to validate password", e);
214          }
215       }
216       return isValid;
217    }
218
219    private void createLdapInitContext(String JavaDoc username, Object JavaDoc credential) throws NamingException JavaDoc
220    {
221       Properties JavaDoc env = new Properties JavaDoc();
222       // Map all option into the JNDI InitialLdapContext env
223
Iterator JavaDoc iter = options.entrySet().iterator();
224       while (iter.hasNext())
225       {
226          Entry entry = (Entry) iter.next();
227          env.put(entry.getKey(), entry.getValue());
228       }
229       
230       // Set defaults for key values if they are missing
231
String JavaDoc factoryName = env.getProperty(Context.INITIAL_CONTEXT_FACTORY);
232       if (factoryName == null)
233       {
234          factoryName = "com.sun.jndi.ldap.LdapCtxFactory";
235          env.setProperty(Context.INITIAL_CONTEXT_FACTORY, factoryName);
236       }
237       String JavaDoc authType = env.getProperty(Context.SECURITY_AUTHENTICATION);
238       if (authType == null)
239          env.setProperty(Context.SECURITY_AUTHENTICATION, "simple");
240       String JavaDoc protocol = env.getProperty(Context.SECURITY_PROTOCOL);
241       String JavaDoc providerURL = (String JavaDoc) options.get(Context.PROVIDER_URL);
242       if (providerURL == null)
243          providerURL = "ldap://localhost:" + ((protocol != null && protocol.equals("ssl")) ? "636" : "389");
244
245       String JavaDoc principalDNPrefix = (String JavaDoc) options.get(PRINCIPAL_DN_PREFIX_OPT);
246       if (principalDNPrefix == null)
247          principalDNPrefix = "";
248       String JavaDoc principalDNSuffix = (String JavaDoc) options.get(PRINCIPAL_DN_SUFFIX_OPT);
249       if (principalDNSuffix == null)
250          principalDNSuffix = "";
251       String JavaDoc matchType = (String JavaDoc) options.get(MATCH_ON_USER_DN_OPT);
252       boolean matchOnUserDN = Boolean.valueOf(matchType).booleanValue();
253       String JavaDoc userDN = principalDNPrefix + username + principalDNSuffix;
254       env.setProperty(Context.PROVIDER_URL, providerURL);
255       env.setProperty(Context.SECURITY_PRINCIPAL, userDN);
256       env.put(Context.SECURITY_CREDENTIALS, credential);
257       super.log.trace("Logging into LDAP server, env=" + env);
258       InitialLdapContext JavaDoc ctx = new InitialLdapContext JavaDoc(env, null);
259       super.log.trace("Logged into LDAP server, " + ctx);
260       /* If a userRolesCtxDNAttributeName was speocified, see if there is a
261        user specific roles DN. If there is not, the default rolesCtxDN will
262        be used.
263        */

264       String JavaDoc rolesCtxDN = (String JavaDoc) options.get(ROLES_CTX_DN_OPT);
265       String JavaDoc userRolesCtxDNAttributeName = (String JavaDoc) options.get(USER_ROLES_CTX_DN_ATTRIBUTE_ID_OPT);
266       if (userRolesCtxDNAttributeName != null)
267       {
268          // Query the indicated attribute for the roles ctx DN to use
269
String JavaDoc[] returnAttribute = {userRolesCtxDNAttributeName};
270          try
271          {
272             Attributes JavaDoc result = ctx.getAttributes(userDN, returnAttribute);
273             if (result.get(userRolesCtxDNAttributeName) != null)
274             {
275                rolesCtxDN = result.get(userRolesCtxDNAttributeName).get().toString();
276                super.log.trace("Found user roles context DN: " + rolesCtxDN);
277             }
278          }
279          catch (NamingException JavaDoc e)
280          {
281             super.log.debug("Failed to query userRolesCtxDNAttributeName", e);
282          }
283       }
284
285       // Search for any roles associated with the user
286
if (rolesCtxDN != null)
287       {
288          String JavaDoc uidAttrName = (String JavaDoc) options.get(UID_ATTRIBUTE_ID_OPT);
289          if (uidAttrName == null)
290             uidAttrName = "uid";
291          String JavaDoc roleAttrName = (String JavaDoc) options.get(ROLE_ATTRIBUTE_ID_OPT);
292          if (roleAttrName == null)
293             roleAttrName = "roles";
294          BasicAttributes JavaDoc matchAttrs = new BasicAttributes JavaDoc(true);
295          if (matchOnUserDN == true)
296             matchAttrs.put(uidAttrName, userDN);
297          else
298             matchAttrs.put(uidAttrName, username);
299          String JavaDoc[] roleAttr = {roleAttrName};
300          // Is user's role attribute a DN or the role name
301
String JavaDoc roleAttributeIsDNOption = (String JavaDoc) options.get(ROLE_ATTRIBUTE_IS_DN_OPT);
302          boolean roleAttributeIsDN = Boolean.valueOf(roleAttributeIsDNOption).booleanValue();
303
304          // If user's role attribute is a DN, what is the role's name attribute
305
// Default to 'name' (Group name attribute in Active Directory)
306
String JavaDoc roleNameAttributeID = (String JavaDoc) options.get(ROLE_NAME_ATTRIBUTE_ID_OPT);
307          if (roleNameAttributeID == null)
308             roleNameAttributeID = "name";
309
310          try
311          {
312             NamingEnumeration JavaDoc answer = ctx.search(rolesCtxDN, matchAttrs, roleAttr);
313             while (answer.hasMore())
314             {
315                SearchResult JavaDoc sr = (SearchResult JavaDoc) answer.next();
316                Attributes JavaDoc attrs = sr.getAttributes();
317                Attribute JavaDoc roles = attrs.get(roleAttrName);
318                for (int r = 0; r < roles.size(); r++)
319                {
320                   Object JavaDoc value = roles.get(r);
321                   String JavaDoc roleName = null;
322                   if (roleAttributeIsDN == true)
323                   {
324                      // Query the roleDN location for the value of roleNameAttributeID
325
String JavaDoc roleDN = value.toString();
326                      String JavaDoc[] returnAttribute = {roleNameAttributeID};
327                      super.log.trace("Using roleDN: " + roleDN);
328                      try
329                      {
330                         Attributes JavaDoc result = ctx.getAttributes(roleDN, returnAttribute);
331                         if (result.get(roleNameAttributeID) != null)
332                         {
333                            roleName = result.get(roleNameAttributeID).get().toString();
334                         }
335                      }
336                      catch (NamingException JavaDoc e)
337                      {
338                         log.trace("Failed to query roleNameAttrName", e);
339                      }
340                   }
341                   else
342                   {
343                      // The role attribute value is the role name
344
roleName = value.toString();
345                   }
346
347                   if (roleName != null)
348                   {
349                      try
350                      {
351                         Principal JavaDoc p = super.createIdentity(roleName);
352                         log.trace("Assign user to role " + roleName);
353                         userRoles.addMember(p);
354                      }
355                      catch (Exception JavaDoc e)
356                      {
357                         log.debug("Failed to create principal: " + roleName, e);
358                      }
359                   }
360                }
361             }
362          }
363          catch (NamingException JavaDoc e)
364          {
365             log.trace("Failed to locate roles", e);
366          }
367       }
368       // Close the context to release the connection
369
ctx.close();
370    }
371 }
372
Popular Tags