KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > lenya > ac > ldap > LDAPUser


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  *
14  */

15
16 package org.apache.lenya.ac.ldap;
17
18 import java.io.File JavaDoc;
19 import java.io.FileInputStream JavaDoc;
20 import java.io.IOException JavaDoc;
21 import java.util.Hashtable JavaDoc;
22 import java.util.Properties JavaDoc;
23
24 import javax.naming.AuthenticationException JavaDoc;
25 import javax.naming.Context JavaDoc;
26 import javax.naming.NamingEnumeration JavaDoc;
27 import javax.naming.NamingException JavaDoc;
28 import javax.naming.directory.Attribute JavaDoc;
29 import javax.naming.directory.Attributes JavaDoc;
30 import javax.naming.directory.DirContext JavaDoc;
31 import javax.naming.directory.SearchControls JavaDoc;
32 import javax.naming.directory.SearchResult JavaDoc;
33 import javax.naming.ldap.InitialLdapContext JavaDoc;
34
35 import org.apache.avalon.framework.configuration.Configuration;
36 import org.apache.avalon.framework.configuration.ConfigurationException;
37 import org.apache.avalon.framework.configuration.DefaultConfiguration;
38 import org.apache.lenya.ac.AccessControlException;
39 import org.apache.lenya.ac.file.FileUser;
40 import org.apache.log4j.Category;
41 import org.apache.log4j.Logger;
42
43 import com.sun.jndi.ldap.LdapCtxFactory;
44
45 /**
46  * LDAP user.
47  * @version $Id: LDAPUser.java 190937 2005-06-16 15:44:10Z jwkaltz $
48  */

49 public class LDAPUser extends FileUser {
50     private static Properties JavaDoc defaultProperties = null;
51     private static Category log = Logger.getLogger(LDAPUser.class);
52
53     public static final String JavaDoc LDAP_ID = "ldapid";
54     private static String JavaDoc LDAP_PROPERTIES_FILE = "ldap.properties";
55     private static String JavaDoc PROVIDER_URL_PROP = "provider-url";
56     private static String JavaDoc MGR_DN_PROP = "mgr-dn";
57     private static String JavaDoc MGR_PW_PROP = "mgr-pw";
58     private static String JavaDoc KEY_STORE_PROP = "key-store";
59     private static String JavaDoc SECURITY_PROTOCOL_PROP = "security-protocol";
60     private static String JavaDoc SECURITY_AUTHENTICATION_PROP = "security-authentication";
61     private static String JavaDoc USR_ATTR_PROP = "usr-attr";
62     private static String JavaDoc USR_ATTR_DEFAULT = "uid";
63     private static String JavaDoc USR_NAME_ATTR_PROP = "usr-name-attr";
64     private static String JavaDoc USR_NAME_ATTR_DEFAULT = "gecos";
65     private static String JavaDoc USR_BRANCH_PROP = "usr-branch";
66     private static String JavaDoc USR_BRANCH_DEFAULT = "ou=People";
67     private static String JavaDoc USR_AUTH_TYPE_PROP = "usr-authentication";
68     private static String JavaDoc USR_AUTH_TYPE_DEFAULT = "simple";
69     private static String JavaDoc BASE_DN_PROP = "base-dn";
70     private static String JavaDoc DOMAIN_NAME_PROP = "domain-name";
71
72     private String JavaDoc ldapId;
73     private String JavaDoc ldapName;
74
75     // deprecated: for backwards compatibility only !
76
private static String JavaDoc PARTIAL_USER_DN_PROP = "partial-user-dn";
77
78     /**
79      * Creates a new LDAPUser object.
80      */

81     public LDAPUser() {
82     }
83
84     /**
85      * Creates a new LDAPUser object.
86      * @param configurationDirectory The configuration directory.
87      */

88     public LDAPUser(File JavaDoc configurationDirectory) {
89         setConfigurationDirectory(configurationDirectory);
90     }
91
92     /**
93      * Create an LDAPUser
94      *
95      * @param configurationDirectory where the user will be attached to
96      * @param id user id of LDAPUser
97      * @param email of LDAPUser
98      * @param ldapId of LDAPUser
99      * @throws ConfigurationException if the properties could not be read
100      */

101     public LDAPUser(File JavaDoc configurationDirectory, String JavaDoc id, String JavaDoc email, String JavaDoc ldapId)
102             throws ConfigurationException {
103         super(configurationDirectory, id, null, email, null);
104         this.ldapId = ldapId;
105
106         initialize();
107     }
108
109     /**
110      * Create a new LDAPUser from a configuration
111      *
112      * @param config the <code>Configuration</code> specifying the user details
113      * @throws ConfigurationException if the user could not be instantiated
114      */

115     public void configure(Configuration config) throws ConfigurationException {
116         super.configure(config);
117         ldapId = config.getChild(LDAP_ID).getValue();
118
119         initialize();
120     }
121
122     /**
123      * Checks if a user exists.
124      *
125      * @param ldapId The LDAP id.
126      * @return A boolean value indicating whether the user is found in the directory
127      * @throws AccessControlException when an error occurs.
128      */

129     public boolean existsUser(String JavaDoc ldapId) throws AccessControlException {
130
131     if (log.isDebugEnabled())
132         log.debug("existsUser() checking id " + ldapId);
133
134         boolean exists = false;
135
136         try {
137             readProperties();
138         SearchResult JavaDoc entry = getDirectoryEntry(ldapId);
139
140         exists = (entry != null);
141
142         } catch (Exception JavaDoc e) {
143         if (log.isDebugEnabled())
144         log.debug("existsUser() for id " + ldapId + " got exception: " + e);
145             throw new AccessControlException("Exception during search: ", e);
146         }
147
148         return exists;
149     }
150
151     /**
152      * Initializes this user.
153      *
154      * The current (already authenticated) ldapId is queried in the directory,
155      * in order to retrieve additional information, such as the user name.
156      * In current implementation, only the user name is actually retrieved, but
157      * other attributes may be used in the future (such as groups ?)
158      *
159      * TODO: should the code be changed to not throw an exception when something
160      * goes wrong ? After all, it's only used to get additional info for display?
161      * This is a design decision, I'm not sure what's best.
162      *
163      * @throws ConfigurationException when something went wrong.
164      */

165     protected void initialize() throws ConfigurationException {
166         DirContext JavaDoc context = null;
167         try {
168         if (log.isDebugEnabled())
169         log.debug("initialize() getting entry ...");
170
171         SearchResult JavaDoc entry = getDirectoryEntry(ldapId);
172         StringBuffer JavaDoc name = new StringBuffer JavaDoc();
173
174         if (entry != null) {
175         /* users full name */
176         String JavaDoc usrNameAttr =
177             defaultProperties.getProperty(USR_NAME_ATTR_PROP, USR_NAME_ATTR_DEFAULT);
178
179         if (log.isDebugEnabled())
180             log.debug("initialize() got entry, going to look for attribute " + usrNameAttr + " in entry, which is: " + entry);
181         
182         Attributes JavaDoc attributes = entry.getAttributes();
183         if (attributes != null) {
184             Attribute JavaDoc userNames = attributes.get(usrNameAttr);
185             if (userNames != null) {
186             for (NamingEnumeration JavaDoc nenum = userNames.getAll(); nenum.hasMore(); nenum.next()) {
187                 name.append((String JavaDoc)userNames.get());
188             }
189             }
190         }
191         }
192         ldapName = name.toString();
193         if (log.isDebugEnabled())
194         log.debug("initialize() set name to " + ldapName);
195
196         } catch (Exception JavaDoc e) {
197             throw new ConfigurationException("Could not read properties", e);
198         } finally {
199             try {
200                 if (context != null) {
201                     close(context);
202                 }
203             } catch (NamingException JavaDoc e) {
204                 throw new ConfigurationException("Closing context failed: ", e);
205             }
206         }
207     }
208
209     /**
210      * @see org.apache.lenya.ac.file.FileUser#createConfiguration()
211      */

212     protected Configuration createConfiguration() {
213         DefaultConfiguration config = (DefaultConfiguration) super.createConfiguration();
214
215         // add ldap_id node
216
DefaultConfiguration child = new DefaultConfiguration(LDAP_ID);
217         child.setValue(ldapId);
218         config.addChild(child);
219
220         return config;
221     }
222
223     /**
224      * Get the ldap id
225      *
226      * @return the ldap id
227      */

228     public String JavaDoc getLdapId() {
229         return ldapId;
230     }
231
232     /**
233      * Set the ldap id
234      *
235      * @param string the new ldap id
236      */

237     public void setLdapId(String JavaDoc string) {
238         ldapId = string;
239     }
240
241     /**
242      * Authenticate a user against the directory.
243      *
244      * The principal to be authenticated is either constructed by use of the
245      * configured properties, or by lookup of this ID in the directory.
246      * This principal then attempts to authenticate against the directory with
247      * the provided password.
248      *
249      * @see org.apache.lenya.ac.User#authenticate(java.lang.String)
250      */

251     public boolean authenticate(String JavaDoc password) {
252
253     boolean authenticated = false;
254     String JavaDoc principal = "";
255     Context JavaDoc ctx = null;
256
257         try {
258         principal = getPrincipal();
259         
260         if (log.isDebugEnabled())
261         log.debug("Authenticating with principal [" + principal + "]");
262
263             ctx = bind(principal, password,
264                defaultProperties.getProperty(USR_AUTH_TYPE_PROP,
265                              USR_AUTH_TYPE_DEFAULT));
266             authenticated = true;
267             close(ctx);
268             if (log.isDebugEnabled()) {
269                 log.debug("Context closed.");
270             }
271         } catch (IOException JavaDoc e) {
272         log.warn("authenticate handling IOException, check your setup: " + e);
273         } catch (AuthenticationException JavaDoc e) {
274         log.info("authenticate failed for principal " + principal + ", exception " + e);
275         } catch (NamingException JavaDoc e) {
276             // log this failure
277
// StringWriter writer = new StringWriter();
278
// e.printStackTrace(new PrintWriter(writer));
279
if (log.isInfoEnabled()) {
280                 log.info("Bind for user " + principal + " to Ldap server failed: ", e);
281             }
282         }
283
284         return authenticated;
285
286     }
287
288     /**
289      * @see org.apache.lenya.ac.Item#getName()
290      */

291     public String JavaDoc getName() {
292         return ldapName;
293     }
294
295     /**
296      * LDAP Users fetch their name information from the LDAP server, so we don't store it locally.
297      * Since we only have read access we basically can't set the name, i.e. any request to change
298      * the name is ignored.
299      *
300      * @param string is ignored
301      */

302     public void setName(String JavaDoc string) {
303         // we do not have write access to LDAP, so we ignore
304
// change request to the name.
305
}
306
307     /**
308      * The LDAPUser doesn't store any passwords as they are handled by LDAP
309      *
310      * @param plainTextPassword is ignored
311      */

312     public void setPassword(String JavaDoc plainTextPassword) {
313         setEncryptedPassword(null);
314     }
315
316     /**
317      * The LDAPUser doesn't store any passwords as they are handled by LDAP
318      *
319      * @param encryptedPassword is ignored
320      */

321     protected void setEncryptedPassword(String JavaDoc encryptedPassword) {
322         encryptedPassword = null;
323     }
324
325     /**
326      * Connect to the LDAP server
327      *
328      * @param principal the principal string for the LDAP connection
329      * @param credentials the credentials for the LDAP connection
330      * @param authMethod the authentication method
331      * @return a <code>DirContext</code>
332      * @throws NamingException if there are problems establishing the Ldap connection
333      */

334     private DirContext JavaDoc bind(String JavaDoc principal, String JavaDoc credentials,
335                 String JavaDoc authMethod) throws NamingException JavaDoc {
336
337         log.info("Binding principal: [" + principal + "]");
338
339         Hashtable JavaDoc env = new Hashtable JavaDoc();
340
341         System.setProperty("javax.net.ssl.trustStore", getConfigurationDirectory()
342                 .getAbsolutePath()
343                 + File.separator + defaultProperties.getProperty(KEY_STORE_PROP));
344
345         env.put(Context.INITIAL_CONTEXT_FACTORY, LdapCtxFactory.class.getName());
346
347         String JavaDoc prop = defaultProperties.getProperty(PROVIDER_URL_PROP);
348         if (prop == null)
349             throw new RuntimeException JavaDoc("LDAP configuration error: property " +
350                                        PROVIDER_URL_PROP +
351                                        " is not set in property file " +
352                                        LDAP_PROPERTIES_FILE);
353         env.put(Context.PROVIDER_URL, prop);
354
355         prop = defaultProperties.getProperty(SECURITY_PROTOCOL_PROP);
356         if (prop == null)
357             throw new RuntimeException JavaDoc("LDAP configuration error: property " +
358                                        SECURITY_PROTOCOL_PROP +
359                                        " is not set in property file " +
360                                        LDAP_PROPERTIES_FILE);
361         env.put(Context.SECURITY_PROTOCOL, prop);
362
363         env.put(Context.SECURITY_AUTHENTICATION, authMethod);
364     if (authMethod != null && ! authMethod.equals("none")) {
365         env.put(Context.SECURITY_PRINCIPAL, principal);
366         env.put(Context.SECURITY_CREDENTIALS, credentials);
367     }
368
369         DirContext JavaDoc ctx = new InitialLdapContext JavaDoc(env, null);
370
371         log.info("Finished binding principal.");
372
373         return ctx;
374     }
375
376     /**
377      * Close the connection to the LDAP server
378      *
379      * @param ctx the context that was returned from the bind
380      * @throws NamingException if there is a problem communicating to the LDAP server
381      */

382     private void close(Context JavaDoc ctx) throws NamingException JavaDoc {
383         ctx.close();
384     }
385
386     /**
387      * Read the properties
388      *
389      * @throws IOException if the properties cannot be found.
390      */

391     private void readProperties() throws IOException JavaDoc {
392         // create and load default properties
393
File JavaDoc propertiesFile = new File JavaDoc(getConfigurationDirectory(), LDAP_PROPERTIES_FILE);
394
395         if (defaultProperties == null) {
396             defaultProperties = new Properties JavaDoc();
397
398             FileInputStream JavaDoc in = null;
399             try {
400                 in = new FileInputStream JavaDoc(propertiesFile);
401                 defaultProperties.load(in);
402             } finally {
403                 if (in != null) {
404                     in.close();
405                 }
406             }
407         }
408     }
409
410     /**
411      * Wrapping of the decision whether a recursive search is wanted or not.
412      * Implementation:
413      * If the USR_BRANCH_PROP is present, this is the new style of configuration
414      * (starting Lenya 1.2.2); if it has a value, then a specific branch is wanted:
415      * no recursive search. If the property is present, but has no value,
416      * search recursively.
417      */

418     private boolean isSubtreeSearch() {
419     boolean recurse = false;
420     String JavaDoc usrBranchProp = defaultProperties.getProperty(USR_BRANCH_PROP);
421     if (usrBranchProp != null)
422         if (usrBranchProp.trim().length() == 0)
423         recurse = true;
424     
425     return recurse;
426     }
427
428
429     private SearchResult JavaDoc getDirectoryEntry(String JavaDoc userId)
430     throws NamingException JavaDoc, IOException JavaDoc
431     {
432     DirContext JavaDoc context = null;
433     String JavaDoc searchFilter = "";
434     String JavaDoc objectName = "";
435     boolean recursiveSearch;
436     SearchResult JavaDoc result = null;
437     
438     try {
439             readProperties();
440         
441             context = bind(defaultProperties.getProperty(MGR_DN_PROP),
442                defaultProperties.getProperty(MGR_PW_PROP),
443                defaultProperties.getProperty(SECURITY_AUTHENTICATION_PROP));
444
445         // Get search information and user attribute from properties
446
// provide defaults if not present (backward compatibility)
447
String JavaDoc userAttribute =
448         defaultProperties.getProperty(USR_ATTR_PROP, USR_ATTR_DEFAULT);
449         searchFilter = "(" + userAttribute + "=" + userId + ")";
450         SearchControls JavaDoc scope = new SearchControls JavaDoc();
451         NamingEnumeration JavaDoc results;
452
453         recursiveSearch = isSubtreeSearch();
454         if (recursiveSearch) {
455         scope.setSearchScope(SearchControls.SUBTREE_SCOPE);
456         objectName = defaultProperties.getProperty(PROVIDER_URL_PROP);
457         }
458         else {
459         scope.setSearchScope(SearchControls.ONELEVEL_SCOPE);
460         objectName =
461             defaultProperties.getProperty(USR_BRANCH_PROP, USR_BRANCH_DEFAULT);
462         }
463     
464         if (log.isDebugEnabled())
465         log.debug("searching object " + objectName + " filtering with " + searchFilter + ", recursive search ? " + recursiveSearch);
466
467         results = context.search(objectName, searchFilter, scope);
468
469         if (results != null && results.hasMore()) {
470         result = (SearchResult JavaDoc)results.next();
471
472         // sanity check: if more than one entry is returned
473
// for a user-id, then the directory is probably flawed,
474
// so it would be nice to warn the administrator.
475
//
476
// This block is commented out for now, because of possible
477
// side-effects, such as unexpected exceptions.
478
// try {
479
// if (results.hasMore()) {
480
// log.warn("Found more than one entry in the directory for user " + userId + ". You probably should deactivate recursive searches. The first entry was used as a work-around.");
481
// }
482
// }
483
// catch (javax.naming.PartialResultException e) {
484
// if (log.isDebugEnabled())
485
// log.debug("Catching and ignoring PartialResultException, as this means LDAP server does not support our sanity check");
486
// }
487

488         }
489     }
490         catch (NamingException JavaDoc e) {
491         if (log.isDebugEnabled())
492         log.debug("NamingException caught when searching on objectName = " + objectName + " and searchFilter=" + searchFilter + ", this exception will be propagated: " + e);
493             throw e;
494         }
495     finally {
496             try {
497                 if (context != null) {
498                     close(context);
499                 }
500             } catch (NamingException JavaDoc e) {
501         log.warn("this should not happen: exception closing context " + e);
502             }
503         }
504     return result;
505     }
506
507     /**
508      * Encapsulation of the creation of a principal: we need to distinguish
509      * three cases, in order to support different modes of using a directory.
510      * The first is the use of a domain-name (requirement of MS Active Directory):
511      * if this property is set, this is used to construct the principal.
512      * The second case is where a user-id is somewhere in a domain, but not in a
513      * specific branch: in this case, a subtree search is performed to retrieve
514      * the complete path.
515      * The third case is where a specific branch of the directory is to be used;
516      * this is the case where usr-branch is set to a value. In this case, this branch
517      * is used to construct the principal.
518      */

519     private String JavaDoc getPrincipal() throws IOException JavaDoc, NamingException JavaDoc {
520
521     String JavaDoc principal;
522
523     // 1. Check if domain-name is to be supported
524
String JavaDoc domainProp = defaultProperties.getProperty(DOMAIN_NAME_PROP);
525     if (domainProp != null && domainProp.trim().length() > 0) {
526         principal = domainProp + "\\" + getLdapId();
527     }
528     else {
529         if (isSubtreeSearch()) {
530         // 2. Principal is constructed from directory entry
531
SearchResult JavaDoc entry = getDirectoryEntry(getLdapId());
532         principal = entry.getName();
533         if (entry.isRelative()) {
534             if (principal.length()>0){
535             principal = principal +","+ defaultProperties.getProperty(BASE_DN_PROP);
536             }
537         }
538         }
539         else {
540         // 3. Principal is constructed from properties
541
principal = constructPrincipal(getLdapId());
542         }
543     }
544
545     return principal;
546     }
547
548     /**
549      * Construct the principal for a user, by using the given userId along
550      * with the configured properties.
551      *
552      */

553     private String JavaDoc constructPrincipal(String JavaDoc userId) {
554     StringBuffer JavaDoc principal = new StringBuffer JavaDoc();
555     principal
556         .append(defaultProperties.getProperty(USR_ATTR_PROP, USR_ATTR_DEFAULT))
557         .append("=")
558         .append(userId)
559         .append(",");
560
561     String JavaDoc baseDn = defaultProperties.getProperty(BASE_DN_PROP);
562     if (baseDn != null && baseDn.length() > 0) {
563         // USR_BRANCH_PROP may be empty, so only append when not-empty
564
String JavaDoc usrBranch = defaultProperties.getProperty(USR_BRANCH_PROP);
565         if (usrBranch != null) {
566         if (usrBranch.trim().length() > 0)
567             principal.append(usrBranch).append(",");
568         }
569         else
570         principal.append(USR_BRANCH_DEFAULT).append(",");
571         
572         principal.append(defaultProperties.getProperty(BASE_DN_PROP));
573     }
574     else {
575         // try for backwards compatibility of ldap properties
576
log.warn("getPrincipal() read a deprecated format in ldap properties, please update");
577         principal.append(defaultProperties.getProperty(PARTIAL_USER_DN_PROP));
578     }
579
580     if (log.isDebugEnabled())
581         log.debug("getPrincipal() returning " + principal.toString());
582
583     return principal.toString();
584     }
585
586
587 }
588
Popular Tags