1 package org.mortbay.jetty.plus.jaas.spi;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import java.io.IOException;
18 import java.io.UnsupportedEncodingException;
19 import java.security.MessageDigest;
20 import java.security.NoSuchAlgorithmException;
21 import java.util.ArrayList;
22 import java.util.Hashtable;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Properties;
26
27 import javax.naming.AuthenticationException;
28 import javax.naming.Context;
29 import javax.naming.NamingEnumeration;
30 import javax.naming.NamingException;
31 import javax.naming.directory.Attribute;
32 import javax.naming.directory.Attributes;
33 import javax.naming.directory.DirContext;
34 import javax.naming.directory.InitialDirContext;
35 import javax.naming.directory.SearchControls;
36 import javax.naming.directory.SearchResult;
37 import javax.security.auth.Subject;
38 import javax.security.auth.callback.Callback;
39 import javax.security.auth.callback.CallbackHandler;
40 import javax.security.auth.callback.NameCallback;
41 import javax.security.auth.callback.UnsupportedCallbackException;
42 import javax.security.auth.login.LoginException;
43
44 import org.mortbay.jetty.plus.jaas.callback.ObjectCallback;
45 import org.mortbay.jetty.security.Credential;
46 import org.mortbay.log.Log;
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87 public class LdapLoginModule extends AbstractLoginModule
88 {
89
90
91
92 private String _hostname;
93
94
95
96
97 private int _port;
98
99
100
101
102 private String _authenticationMethod;
103
104
105
106
107 private String _contextFactory;
108
109
110
111
112 private String _bindDn;
113
114
115
116
117 private String _bindPassword;
118
119
120
121
122 private String _userObjectClass = "inetOrgPerson";
123
124
125
126
127 private String _userRdnAttribute = "uid";
128
129
130
131
132 private String _userIdAttribute = "cn";
133
134
135
136
137
138
139 private String _userPasswordAttribute = "userPassword";
140
141
142
143
144 private String _userBaseDn;
145
146
147
148
149 private String _roleBaseDn;
150
151
152
153
154 private String _roleObjectClass = "groupofuniquenames";
155
156
157
158
159 private String _roleMemberAttribute = "uniqueMember";
160
161
162
163
164 private String _roleNameAttribute = "roleName";
165
166
167
168
169
170
171 private boolean _forceBindingLogin = false;
172
173 private DirContext _rootContext;
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189 public UserInfo getUserInfo(String username) throws Exception
190 {
191 String pwdCredential = getUserCredentials(username);
192 pwdCredential = convertCredentialLdapToJetty(pwdCredential);
193
194
195
196
197 Credential credential = Credential.getCredential(pwdCredential);
198 List roles = getUserRoles(username);
199
200 return new UserInfo(username, credential, roles);
201 }
202
203
204 protected String doRFC2254Encoding(String inputString)
205 {
206 StringBuffer buf = new StringBuffer(inputString.length());
207 for (int i = 0; i < inputString.length(); i++)
208 {
209 char c = inputString.charAt(i);
210 switch (c)
211 {
212 case '\\':
213 buf.append("\\5c");
214 break;
215 case '*':
216 buf.append("\\2a");
217 break;
218 case '(':
219 buf.append("\\28");
220 break;
221 case ')':
222 buf.append("\\29");
223 break;
224 case '\0':
225 buf.append("\\00");
226 break;
227 default:
228 buf.append(c);
229 break;
230 }
231 }
232 return buf.toString();
233 }
234
235
236
237
238
239
240
241
242
243
244
245 private String getUserCredentials(String username) throws LoginException
246 {
247
248 String ldapCredential = null;
249
250 SearchControls ctls = new SearchControls();
251
252 ctls.setCountLimit(1);
253
254 ctls.setDerefLinkFlag(true);
255 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
256
257 String filter = "(&(objectClass=" + _userObjectClass + ")(" + _userIdAttribute + "=" + username + "))";
258
259 Log.debug("Searching for users with filter: \'" + filter + "\'" + " from base dn: " + _userBaseDn);
260
261 try
262 {
263
264 NamingEnumeration<SearchResult> results = _rootContext.search(_userBaseDn, filter, ctls);
265
266 Log.debug("Found user?: " + results.hasMoreElements());
267
268 if (results.hasMoreElements())
269 {
270 SearchResult result = results.nextElement();
271
272 Attributes attributes = result.getAttributes();
273
274 Attribute attribute = attributes.get(_userPasswordAttribute);
275 if (attribute != null)
276 {
277 try
278 {
279 byte[] value = (byte[]) attribute.get();
280
281 ldapCredential = new String(value);
282 }
283 catch (NamingException e)
284 {
285 Log.debug("no password available under attribute: " + _userPasswordAttribute);
286 }
287
288 }
289 }
290 else
291 {
292 throw new LoginException("User not found.");
293 }
294 }
295 catch (NamingException e)
296 {
297 throw new LoginException("Root context binding failure.");
298 }
299
300 Log.debug("user cred is: " + ldapCredential);
301
302 return ldapCredential;
303 }
304
305
306
307
308
309
310
311
312
313
314 private List getUserRoles(String username) throws LoginException
315 {
316 ArrayList roleList = new ArrayList();
317
318 SearchControls ctls = new SearchControls();
319
320 ctls.setDerefLinkFlag(true);
321 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
322
323 if (_roleBaseDn != null)
324 {
325 String userDn = _userRdnAttribute + "=" + username + "," + _userBaseDn;
326 String filter = "(&(objectClass=" + _roleObjectClass + ")(" + _roleMemberAttribute + "=" + userDn + "))";
327
328 try
329 {
330 NamingEnumeration<SearchResult> results = _rootContext.search(_roleBaseDn, filter, ctls);
331
332 Log.debug("Found user roles?: " + results.hasMoreElements());
333
334 while (results.hasMoreElements())
335 {
336 SearchResult result = results.nextElement();
337
338 Attributes attributes = result.getAttributes();
339
340 if (attributes != null)
341 {
342 Attribute roleAttribute = attributes.get(_roleNameAttribute);
343
344 if (roleAttribute != null)
345 {
346 NamingEnumeration roles = roleAttribute.getAll();
347 while (roles.hasMore())
348 {
349 String roleName = (String) roles.next();
350 roleList.add(roleName);
351 }
352 }
353 }
354 }
355 }
356 catch (NamingException e)
357 {
358 throw new LoginException("error obtaining roles for " + username);
359 }
360 }
361
362 return roleList;
363 }
364
365
366
367
368
369
370
371
372
373
374
375
376 public boolean login() throws LoginException
377 {
378 try
379 {
380 if (getCallbackHandler() == null)
381 {
382 throw new LoginException("No callback handler");
383 }
384
385 Callback[] callbacks = configureCallbacks();
386 getCallbackHandler().handle(callbacks);
387
388 String webUserName = ((NameCallback) callbacks[0]).getName();
389 Object webCredential = ((ObjectCallback) callbacks[1]).getObject();
390
391 if ((webUserName == null) || (webCredential == null))
392 {
393 setAuthenticated(false);
394 return isAuthenticated();
395 }
396
397 UserInfo userInfo = getUserInfo(webUserName);
398
399 setCurrentUser(new JAASUserInfo(userInfo));
400
401
402 if (userInfo.getCredential() == null || _forceBindingLogin)
403 {
404 return bindingLogin(webUserName, webCredential);
405 }
406 else
407 {
408 if (webCredential instanceof String)
409 {
410 return credentialLogin(Credential.getCredential((String) webCredential));
411 }
412 else
413 {
414 return credentialLogin(webCredential);
415 }
416 }
417
418 }
419 catch (UnsupportedCallbackException e)
420 {
421 throw new LoginException("Error obtaining callback information.");
422 }
423 catch (IOException e)
424 {
425 throw new LoginException("IO Error performing login.");
426 }
427 catch (Exception e)
428 {
429 throw new LoginException("Error obtaining user info.");
430 }
431 }
432
433
434
435
436
437
438
439
440
441 protected boolean credentialLogin(Object webCredential) throws LoginException
442 {
443 setAuthenticated(getCurrentUser().checkCredential(webCredential));
444 return isAuthenticated();
445 }
446
447
448
449
450
451
452
453
454
455
456
457
458
459 protected boolean bindingLogin(String username, Object password) throws LoginException
460 {
461 SearchControls ctls = new SearchControls();
462
463 ctls.setCountLimit(1);
464
465 ctls.setDerefLinkFlag(true);
466 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
467
468 String filter = "(&(objectClass=" + _userObjectClass + ")(" + _userIdAttribute + "=" + username + "))";
469
470 Log.info("Searching for users with filter: \'" + filter + "\'" + " from base dn: " + _userBaseDn);
471
472 try
473 {
474
475 NamingEnumeration<SearchResult> results = _rootContext.search(_userBaseDn, filter, ctls);
476
477 Log.info("Found user?: " + results.hasMoreElements());
478
479 if (results.hasMoreElements())
480 {
481 SearchResult result = results.nextElement();
482
483 String userDn = result.getNameInNamespace();
484
485 Log.info("Attempting Authenication: + " + userDn);
486
487 Hashtable environment = getEnvironment();
488 environment.put(Context.SECURITY_PRINCIPAL, userDn);
489 environment.put(Context.SECURITY_CREDENTIALS, password);
490
491 try
492 {
493 DirContext userContext = new InitialDirContext( environment );
494
495 }
496 catch (AuthenticationException e)
497 {
498 Log.info("Authentication failed for: " + userDn);
499 throw new LoginException();
500 }
501 catch (NamingException ne)
502 {
503 throw new LoginException("Context binding failure.");
504 }
505
506 setAuthenticated(true);
507
508 return true;
509 }
510 else
511 {
512 throw new LoginException("User not found.");
513 }
514 }
515 catch (NamingException e)
516 {
517 throw new LoginException("Context binding failure.");
518 }
519
520 }
521
522
523
524
525
526
527
528
529
530
531
532 public void initialize(Subject subject,
533 CallbackHandler callbackHandler,
534 Map sharedState,
535 Map options)
536 {
537
538 super.initialize(subject, callbackHandler, sharedState, options);
539
540 _hostname = (String) options.get("hostname");
541 _port = Integer.parseInt((String) options.get("port"));
542 _contextFactory = (String) options.get("contextFactory");
543 _bindDn = (String) options.get("bindDn");
544 _bindPassword = (String) options.get("bindPassword");
545 _authenticationMethod = (String) options.get("authenticationMethod");
546
547 _userBaseDn = (String) options.get("userBaseDn");
548
549 _roleBaseDn = (String) options.get("roleBaseDn");
550
551 if (options.containsKey("forceBindingLogin"))
552 {
553 _forceBindingLogin = Boolean.parseBoolean((String) options.get("forceBindingLogin"));
554 }
555
556 if (options.containsKey("userObjectClass"))
557 {
558 _userObjectClass = (String) options.get("userObjectClass");
559 }
560
561 if (options.containsKey("userRdnAttribute"))
562 {
563 _userRdnAttribute = (String) options.get("userRdnAttribute");
564 }
565
566 if (options.containsKey("userIdAttribute"))
567 {
568 _userIdAttribute = (String) options.get("userIdAttribute");
569 }
570
571 if (options.containsKey("userPasswordAttribute"))
572 {
573 _userPasswordAttribute = (String) options.get("userPasswordAttribute");
574 }
575
576 if (options.containsKey("roleObjectClass"))
577 {
578 _roleObjectClass = (String) options.get("roleObjectClass");
579 }
580 if (options.containsKey("roleMemberAttribute"))
581 {
582 _roleMemberAttribute = (String) options.get("roleMemberAttribute");
583 }
584 if (options.containsKey("roleNameAttribute"))
585 {
586 _roleNameAttribute = (String) options.get("roleNameAttribute");
587 }
588
589 try
590 {
591 _rootContext = new InitialDirContext(getEnvironment());
592 }
593 catch (NamingException ex)
594 {
595 throw new IllegalStateException("Unable to establish root context", ex);
596 }
597
598 }
599
600
601
602
603
604
605 public Hashtable<Object, Object> getEnvironment()
606 {
607 Properties env = new Properties();
608
609 env.put(Context.INITIAL_CONTEXT_FACTORY, _contextFactory);
610
611 if (_hostname != null)
612 {
613 if (_port != 0)
614 {
615 env.put(Context.PROVIDER_URL, "ldap://" + _hostname + ":" + _port + "/");
616 }
617 else
618 {
619 env.put(Context.PROVIDER_URL, "ldap://" + _hostname + "/");
620 }
621 }
622
623 if (_authenticationMethod != null)
624 {
625 env.put(Context.SECURITY_AUTHENTICATION, _authenticationMethod);
626 }
627
628 if (_bindDn != null)
629 {
630 env.put(Context.SECURITY_PRINCIPAL, _bindDn);
631 }
632
633 if (_bindPassword != null)
634 {
635 env.put(Context.SECURITY_CREDENTIALS, _bindPassword);
636 }
637
638 return env;
639 }
640
641 public static String convertCredentialJettyToLdap( String encryptedPassword )
642 {
643 if ( encryptedPassword.toUpperCase().startsWith( "MD5:" ) )
644 {
645 String epwd = encryptedPassword.substring( "MD5:".length(), encryptedPassword.length() );
646 return "{MD5}" + epwd;
647 }
648 else if ( encryptedPassword.toUpperCase().startsWith( "CRYPT:" ) )
649 {
650 String epwd = encryptedPassword.substring( "CRYPT:".length(), encryptedPassword.length() );
651 return "{CRYPT}" + epwd;
652 }
653 else
654 {
655 return encryptedPassword;
656 }
657 }
658
659 public static String convertCredentialLdapToJetty( String encryptedPassword )
660 {
661 if ( encryptedPassword.toUpperCase().startsWith( "{MD5}" ) )
662 {
663 String epwd = encryptedPassword.substring( "{MD5}".length(), encryptedPassword.length() );
664 return "MD5:" + epwd;
665 }
666 else if ( encryptedPassword.toUpperCase().startsWith( "{CRYPT}" ) )
667 {
668 String epwd = encryptedPassword.substring( "{CRYPT}".length(), encryptedPassword.length() );
669 return "CRYPT:" + epwd;
670 }
671 else
672 {
673 return encryptedPassword;
674 }
675 }
676
677 public static byte[] digestMD5(String pwd) throws LoginException
678 {
679 MessageDigest md;
680
681 byte[] barray;
682 try
683 {
684 md = MessageDigest.getInstance("MD5");
685 barray = pwd.getBytes("ISO-8859-1");
686 }
687 catch (UnsupportedEncodingException e)
688 {
689 throw new LoginException();
690 }
691 catch (NoSuchAlgorithmException e1)
692 {
693 throw new LoginException();
694 }
695 for (int i = 0; i < barray.length; i++)
696 {
697 md.update(barray[i]);
698 }
699 String mdString = md.toString();
700
701 return md.digest();
702
703 }
704 }