KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > derby > impl > jdbc > authentication > LDAPAuthenticationSchemeImpl


1 /*
2
3    Derby - Class org.apache.derby.impl.jdbc.authentication.LDAPAuthenticationSchemeImpl
4
5    Licensed to the Apache Software Foundation (ASF) under one or more
6    contributor license agreements. See the NOTICE file distributed with
7    this work for additional information regarding copyright ownership.
8    The ASF licenses this file to you under the Apache License, Version 2.0
9    (the "License"); you may not use this file except in compliance with
10    the License. You may obtain a copy of the License at
11
12       http://www.apache.org/licenses/LICENSE-2.0
13
14    Unless required by applicable law or agreed to in writing, software
15    distributed under the License is distributed on an "AS IS" BASIS,
16    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17    See the License for the specific language governing permissions and
18    limitations under the License.
19
20  */

21
22 package org.apache.derby.impl.jdbc.authentication;
23
24 import org.apache.derby.iapi.reference.MessageId;
25 import org.apache.derby.iapi.services.monitor.Monitor;
26 import org.apache.derby.iapi.error.StandardException;
27 import org.apache.derby.iapi.services.i18n.MessageService;
28 import org.apache.derby.iapi.jdbc.AuthenticationService;
29
30 import org.apache.derby.authentication.UserAuthenticator;
31
32 import org.apache.derby.iapi.services.sanity.SanityManager;
33 import org.apache.derby.iapi.util.StringUtil;
34
35 import javax.naming.*;
36 import javax.naming.directory.*;
37
38
39 import java.util.Properties JavaDoc;
40 import java.sql.SQLException JavaDoc;
41
42 /**
43  * This is the Cloudscape LDAP authentication scheme implementation.
44  *
45  * JNDI system/environment properties can be set at the database
46  * level as database properties. They will be picked-up and set in
47  * the JNDI initial context if any are found.
48  *
49  * We do connect first to the LDAP server in order to retrieve the
50  * user's distinguished name (DN) and then we reconnect and try to
51  * authenticate with the user's DN and passed-in password.
52  *
53  * In 2.0 release, we first connect to do a search (user full DN lookup).
54  * This initial lookup can be done through anonymous bind or using special
55  * LDAP search credentials that the user may have configured on the
56  * LDAP settings for the database or the system.
57  * It is a typical operation with LDAP servers where sometimes it is
58  * hard to tell/guess in advance a users' full DN's.
59  *
60  * NOTE: In a future release, we will cache/maintain the user DN within
61  * the the cloudscape database or system to avoid the initial lookup.
62  * Also note that LDAP search/retrieval operations are usually very fast.
63  *
64  * The default LDAP url is ldap:/// (ldap://localhost:389/)
65  *
66  * @see org.apache.derby.authentication.UserAuthenticator
67  *
68  */

69
70 public final class LDAPAuthenticationSchemeImpl
71 extends JNDIAuthenticationSchemeBase
72 {
73     private static final String JavaDoc dfltLDAPURL = "ldap://";
74
75     private String JavaDoc searchBaseDN;
76
77     private String JavaDoc leftSearchFilter; // stick in uid in between
78
private String JavaDoc rightSearchFilter;
79     private boolean useUserPropertyAsDN;
80
81     // Search Auth DN & Password if anonymous search not allowed
82
private String JavaDoc searchAuthDN;
83     private String JavaDoc searchAuthPW;
84     // we only want the user's full DN in return
85
private static final String JavaDoc[] attrDN = {"dn"}; ;
86
87     //
88
// Cloudscape LDAP Configuration properties
89
//
90
private static final String JavaDoc LDAP_SEARCH_BASE =
91                                 "derby.authentication.ldap.searchBase";
92     private static final String JavaDoc LDAP_SEARCH_FILTER =
93                                 "derby.authentication.ldap.searchFilter";
94     private static final String JavaDoc LDAP_SEARCH_AUTH_DN =
95                                 "derby.authentication.ldap.searchAuthDN";
96     private static final String JavaDoc LDAP_SEARCH_AUTH_PW =
97                                 "derby.authentication.ldap.searchAuthPW";
98     private static final String JavaDoc LDAP_LOCAL_USER_DN =
99                                 "derby.user";
100     private static final String JavaDoc LDAP_SEARCH_FILTER_USERNAME =
101                                 "%USERNAME%";
102
103     public LDAPAuthenticationSchemeImpl(JNDIAuthenticationService as, Properties JavaDoc dbProperties) {
104
105         super(as, dbProperties);
106     }
107
108     /**
109      * Authenticate the passed-in user's credentials.
110      *
111      * We authenticate against a LDAP Server.
112      *
113      *
114      * @param userName The user's name used to connect to JBMS system
115      * @param userPassword The user's password used to connect to JBMS system
116      * @param databaseName The database which the user wants to connect to.
117      * @param info Additional jdbc connection info.
118      */

119     public boolean authenticateUser(String JavaDoc userName,
120                                  String JavaDoc userPassword,
121                                  String JavaDoc databaseName,
122                                  Properties JavaDoc info
123                                 )
124                                 throws java.sql.SQLException JavaDoc
125     {
126         if ( ((userName == null) || (userName.length() == 0)) ||
127              ((userPassword == null) || (userPassword.length() == 0)) )
128         {
129             // We don't tolerate 'guest' user for now as well as
130
// null password.
131
// If a null password is passed upon authenticating a user
132
// through LDAP, then the LDAP server might consider this as
133
// anonymous bind and therefore no authentication will be done
134
// at all.
135
return false;
136         }
137
138
139         Exception JavaDoc e;
140         try {
141             Properties JavaDoc env = (Properties JavaDoc) initDirContextEnv.clone();
142             String JavaDoc userDN = null;
143             //
144
// Retrieve the user's DN (Distinguished Name)
145
// If we're asked to look it up locally, do it first
146
// and if we don't find it, we go against the LDAP
147
// server for a look-up (search)
148
//
149
if (useUserPropertyAsDN)
150                 userDN =
151                     authenticationService.getProperty(
152                         org.apache.derby.iapi.reference.Property.USER_PROPERTY_PREFIX);
153
154             if (userDN == (String JavaDoc) null) {
155                 userDN = getDNFromUID(userName);
156             }
157         
158             if (SanityManager.DEBUG)
159             {
160                 if (SanityManager.DEBUG_ON(
161                         AuthenticationServiceBase.AuthenticationTrace)) {
162                     SanityManager.DEBUG(AuthenticationServiceBase.AuthenticationTrace,
163                     "User DN = ["+ userDN+"]\n");
164                 }
165             }
166
167             env.put(Context.SECURITY_PRINCIPAL, userDN);
168             env.put(Context.SECURITY_CREDENTIALS, userPassword);
169             
170             // Connect & authenticate (bind) to the LDAP server now
171

172             // it is happening right here
173
DirContext ctx = new InitialDirContext(env);
174
175             // if the above was successfull, then username and
176
// password must be correct
177
return true;
178
179         } catch (javax.naming.AuthenticationException JavaDoc jndiae) {
180             return false;
181
182         } catch (javax.naming.NameNotFoundException JavaDoc jndinnfe) {
183             return false;
184
185         } catch (javax.naming.NamingException JavaDoc jndine) {
186             e = jndine;
187         }
188
189         throw getLoginSQLException(e);
190     }
191
192     /**
193      * This method basically tests and sets default/expected JNDI properties
194      * for the JNDI provider scheme (here it is LDAP).
195      *
196      **/

197     protected void setJNDIProviderProperties()
198     {
199
200         // check if we're told to use a different initial context factory
201
if (initDirContextEnv.getProperty(
202                             Context.INITIAL_CONTEXT_FACTORY) == (String JavaDoc) null)
203         {
204             initDirContextEnv.put(Context.INITIAL_CONTEXT_FACTORY,
205                                       "com.sun.jndi.ldap.LdapCtxFactory");
206         }
207
208         // retrieve LDAP server name/port# and construct LDAP url
209
if (initDirContextEnv.getProperty(
210                             Context.PROVIDER_URL) == (String JavaDoc) null)
211         {
212             // Now we construct the LDAP url and expect to find the LDAP Server
213
// name.
214
//
215
String JavaDoc ldapServer = authenticationService.getProperty(
216                         org.apache.derby.iapi.reference.Property.AUTHENTICATION_SERVER_PARAMETER);
217
218             if (ldapServer == (String JavaDoc) null) {
219
220                 // we do expect a LDAP Server name to be configured
221
Monitor.logTextMessage(
222                     MessageId.AUTH_NO_LDAP_HOST_MENTIONED,
223                          org.apache.derby.iapi.reference.Property.AUTHENTICATION_SERVER_PARAMETER);
224
225                 this.providerURL = dfltLDAPURL + "/";
226
227             } else {
228
229                 if (ldapServer.startsWith(dfltLDAPURL) || ldapServer.startsWith("ldaps://") )
230                     this.providerURL = ldapServer;
231                 else if (ldapServer.startsWith("//"))
232                     this.providerURL = "ldap:" + ldapServer;
233                 else
234                     this.providerURL = dfltLDAPURL + ldapServer;
235             }
236             initDirContextEnv.put(Context.PROVIDER_URL, providerURL);
237         }
238
239         // check if we should we use a particular authentication method
240
// we assume the ldap server supports this authentication method
241
// (Netscape DS 3.1.1 does not support CRAM-MD5 for instance)
242
if (initDirContextEnv.getProperty(
243                             Context.SECURITY_AUTHENTICATION) == (String JavaDoc) null)
244         {
245             // set the default to be clear userName/Password as not of all the
246
// LDAP server(s) support CRAM-MD5 (especially ldap v2 ones)
247
// Netscape Directory Server 3.1.1 does not support CRAM-MD5
248
// (told by Sun JNDI engineering). Netscape DS 4.0 allows SASL
249
// plug-ins to be installed and that can be used as authentication
250
// method.
251
//
252
initDirContextEnv.put(Context.SECURITY_AUTHENTICATION,
253                                       "simple"
254                                       );
255         }
256
257         // Retrieve and set the search base (root) DN to use on the ldap
258
// server.
259
String JavaDoc ldapSearchBase =
260                     authenticationService.getProperty(LDAP_SEARCH_BASE);
261         if (ldapSearchBase != (String JavaDoc) null)
262             this.searchBaseDN = ldapSearchBase;
263         else
264             this.searchBaseDN = "";
265
266         // retrieve principal and credentials for the search bind as the
267
// user may not want to allow anonymous binds (for searches)
268
this.searchAuthDN =
269                     authenticationService.getProperty(LDAP_SEARCH_AUTH_DN);
270         this.searchAuthPW =
271                     authenticationService.getProperty(LDAP_SEARCH_AUTH_PW);
272
273         //
274
// Construct the LDAP search filter:
275
//
276
// If we were told to use a special search filther, we do so;
277
// otherwise we use our default search filter.
278
// The user may have set the search filter 3 different ways:
279
//
280
// - if %USERNAME% was found in the search filter, then we
281
// will substitute this with the passed-in uid at runtime.
282
//
283
// - if "derby.user" is the search filter value, then we
284
// will assume the user's DN can be found in the system or
285
// database property "derby.user.<uid>" . If the property
286
// does not exist, then we will do a normal lookup with our
287
// default search filter; otherwise we will perform an
288
// authenticated bind to the LDAP server using the found DN.
289
//
290
// - if neither of the 2 previous values were found, then we use
291
// our default search filter and we will substitute insert the
292
// uid passed at runtime into our default search filter.
293
//
294
String JavaDoc searchFilterProp =
295                     authenticationService.getProperty(LDAP_SEARCH_FILTER);
296         
297         if (searchFilterProp == (String JavaDoc) null)
298         {
299             // use our default search filter
300
this.leftSearchFilter = "(&(objectClass=inetOrgPerson)(uid=";
301             this.rightSearchFilter = "))";
302
303         } else if (StringUtil.SQLEqualsIgnoreCase(searchFilterProp,LDAP_LOCAL_USER_DN)) {
304
305             // use local user DN in derby.user.<uid>
306
this.leftSearchFilter = "(&(objectClass=inetOrgPerson)(uid=";
307             this.rightSearchFilter = "))";
308             this.useUserPropertyAsDN = true;
309
310         } else if (searchFilterProp.indexOf(
311                                     LDAP_SEARCH_FILTER_USERNAME) != -1) {
312
313             // user has set %USERNAME% in the search filter
314
this.leftSearchFilter = searchFilterProp.substring(0,
315                 searchFilterProp.indexOf(LDAP_SEARCH_FILTER_USERNAME));
316             this.rightSearchFilter = searchFilterProp.substring(
317                 searchFilterProp.indexOf(LDAP_SEARCH_FILTER_USERNAME)+
318                 (int) LDAP_SEARCH_FILTER_USERNAME.length());
319
320
321         } else { // add this search filter to ours
322

323             // complement this search predicate to ours
324
this.leftSearchFilter = "(&("+searchFilterProp+")"+
325                                     "(objectClass=inetOrgPerson)(uid=";
326             this.rightSearchFilter = "))";
327
328         }
329
330         if (SanityManager.DEBUG)
331         {
332             if (SanityManager.DEBUG_ON(
333                         AuthenticationServiceBase.AuthenticationTrace)) {
334
335                 java.io.PrintWriter JavaDoc iDbgStream =
336                     SanityManager.GET_DEBUG_STREAM();
337
338                 iDbgStream.println(
339                                 "\n\n+ LDAP Authentication Configuration:\n"+
340                                 " - provider URL ["+this.providerURL+"]\n"+
341                                 " - search base ["+this.searchBaseDN+"]\n"+
342                                 " - search filter to be [" +
343                                 this.leftSearchFilter + "<uid>" +
344                                 this.rightSearchFilter + "]\n" +
345                                 " - use local DN [" +
346                                 (useUserPropertyAsDN ? "true" : "false") +
347                                 "]\n"
348                                 );
349             }
350         }
351
352         if (SanityManager.DEBUG)
353         {
354             if (SanityManager.DEBUG_ON(
355                         AuthenticationServiceBase.AuthenticationTrace)) {
356                 try {
357                     initDirContextEnv.put("com.sun.naming.ldap.trace.ber",
358                                 new java.io.FileOutputStream JavaDoc("CloudLDAP.out"));
359                 } catch (java.io.IOException JavaDoc ie) {}
360             }
361         }
362     }
363
364     /**
365      * Search for the full user's DN in the LDAP server.
366      * LDAP server bind may or not be anonymous.
367      *
368      * If the admin does not want us to do anonymous bind/search, then we
369      * must have been given principal/credentials in order to successfully
370      * bind to perform the user's DN search.
371      *
372      * @exception NamingException if could not retrieve the user DN.
373      **/

374     private String JavaDoc getDNFromUID(String JavaDoc uid)
375         throws javax.naming.NamingException JavaDoc
376     {
377         //
378
// We bind to the LDAP server here
379
// Note that this bind might be anonymous (if anonymous searches
380
// are allowed in the LDAP server, or authenticated if we were
381
// told/configured to.
382
//
383
Properties JavaDoc env = null;
384         if (this.searchAuthDN != (String JavaDoc) null) {
385             env = (Properties JavaDoc) initDirContextEnv.clone();
386             env.put(Context.SECURITY_PRINCIPAL, this.searchAuthDN);
387             env.put(Context.SECURITY_CREDENTIALS, this.searchAuthPW);
388         }
389         else
390             env = initDirContextEnv;
391
392         DirContext ctx = new InitialDirContext(env);
393
394         // Construct Search Filter
395
SearchControls ctls = new SearchControls();
396         // Set-up a LDAP subtree search scope
397
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
398
399         // Just retrieve the DN
400
ctls.setReturningAttributes(attrDN);
401
402         String JavaDoc searchFilter =
403                         this.leftSearchFilter + uid + this.rightSearchFilter;
404         NamingEnumeration results =
405                         ctx.search(searchBaseDN, searchFilter, ctls);
406             
407         // If we did not find anything then login failed
408
if (results == null || !results.hasMore())
409             throw new NameNotFoundException();
410             
411         SearchResult result = (SearchResult)results.next();
412         
413         if (results.hasMore())
414         {
415             // This is a login failure as we cannot assume the first one
416
// is the valid one.
417
if (SanityManager.DEBUG)
418             {
419                 if (SanityManager.DEBUG_ON(
420                         AuthenticationServiceBase.AuthenticationTrace)) {
421
422                     java.io.PrintWriter JavaDoc iDbgStream =
423                         SanityManager.GET_DEBUG_STREAM();
424
425                     iDbgStream.println(
426                         " - LDAP Authentication request failure: "+
427                         "search filter [" + searchFilter + "]"+
428                         ", retrieve more than one occurence in "+
429                         "LDAP server [" + this.providerURL + "]");
430                 }
431             }
432             throw new NameNotFoundException();
433         }
434
435         NameParser parser = ctx.getNameParser(searchBaseDN);
436         Name userDN = parser.parse(searchBaseDN);
437
438         if (userDN == (Name) null)
439             // This should not happen in theory
440
throw new NameNotFoundException();
441         else
442             userDN.addAll(parser.parse(result.getName()));
443         
444         // Return the full user's DN
445
return userDN.toString();
446     }
447 }
448
Popular Tags