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