1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package net.sf.michaelo.tomcat.realm;
17
18 import java.net.URI;
19 import java.net.URISyntaxException;
20 import java.security.Principal;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.Map;
27
28 import javax.naming.CompositeName;
29 import javax.naming.InvalidNameException;
30 import javax.naming.Name;
31 import javax.naming.NameParser;
32 import javax.naming.NamingEnumeration;
33 import javax.naming.NamingException;
34 import javax.naming.PartialResultException;
35 import javax.naming.ReferralException;
36 import javax.naming.directory.Attribute;
37 import javax.naming.directory.Attributes;
38 import javax.naming.directory.DirContext;
39 import javax.naming.directory.InitialDirContext;
40 import javax.naming.directory.SearchControls;
41 import javax.naming.directory.SearchResult;
42 import javax.naming.ldap.LdapName;
43 import javax.naming.ldap.ManageReferralControl;
44 import javax.naming.ldap.Rdn;
45 import javax.security.sasl.SaslClient;
46
47 import net.sf.michaelo.dirctxsrc.DirContextSource;
48 import net.sf.michaelo.tomcat.realm.mapper.SamAccountNameRfc2247Mapper;
49 import net.sf.michaelo.tomcat.realm.mapper.UserPrincipalNameSearchMapper;
50 import net.sf.michaelo.tomcat.realm.mapper.UsernameSearchMapper;
51 import net.sf.michaelo.tomcat.realm.mapper.UsernameSearchMapper.MappedValues;
52
53 import org.apache.catalina.LifecycleException;
54 import org.apache.catalina.Server;
55 import org.apache.catalina.realm.CombinedRealm;
56 import org.apache.commons.lang3.StringUtils;
57 import org.apache.naming.ContextBindings;
58 import org.apache.tomcat.util.collections.SynchronizedStack;
59 import org.ietf.jgss.GSSCredential;
60 import org.ietf.jgss.GSSName;
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181 public class ActiveDirectoryRealm extends ActiveDirectoryRealmBase {
182
183
184 protected static class DirContextConnection {
185 protected long lastBorrowTime;
186 protected DirContext context;
187 }
188
189 private static final UsernameSearchMapper[] USERNAME_SEARCH_MAPPERS = {
190 new SamAccountNameRfc2247Mapper(), new UserPrincipalNameSearchMapper() };
191
192 private static final String[] DEFAULT_USER_ATTRIBUTES = new String[] { "userAccountControl",
193 "memberOf", "objectSid;binary" };
194
195 private static final String[] DEFAULT_ROLE_ATTRIBUTES = new String[] { "groupType",
196 "objectSid;binary", "sIDHistory;binary" };
197
198 protected boolean localDirContextSource;
199 protected String dirContextSourceName;
200 protected String[] additionalAttributes;
201 protected int connectionPoolSize = 0;
202 protected long maxIdleTime = 900_000L;
203
204
205 protected SynchronizedStack<DirContextConnection> connectionPool;
206
207
208
209
210 protected static final String name = "ActiveDirectoryRealm";
211
212
213
214
215
216
217
218
219 public void setLocalDirContextSource(boolean localDirContextSource) {
220 this.localDirContextSource = localDirContextSource;
221 }
222
223
224
225
226
227
228
229 public void setDirContextSourceName(String dirContextSourceName) {
230 this.dirContextSourceName = dirContextSourceName;
231 }
232
233
234
235
236
237
238
239
240 public void setAdditionalAttributes(String additionalAttributes) {
241 this.additionalAttributes = additionalAttributes.split(",");
242 }
243
244
245
246
247
248
249
250 public void setConnectionPoolSize(int connectionPoolSize) {
251 this.connectionPoolSize = connectionPoolSize;
252 }
253
254
255
256
257
258
259
260
261 public void setMaxIdleTime(long maxIdleTime) {
262 this.maxIdleTime = maxIdleTime;
263 }
264
265 @Override
266 protected String getName() {
267 return name;
268 }
269
270 @Override
271 protected Principal getPrincipal(GSSName gssName, GSSCredential gssCredential) {
272 if (gssName.isAnonymous())
273 return new ActiveDirectoryPrincipal(gssName, Sid.ANONYMOUS_SID, gssCredential);
274
275 DirContextConnection connection = acquire();
276 if (connection.context == null)
277 return null;
278
279 try {
280 User user = getUser(connection.context, gssName);
281
282 if (user != null) {
283 List<String> roles = getRoles(connection.context, user);
284
285 return new ActiveDirectoryPrincipal(gssName, user.getSid(), roles, gssCredential,
286 user.getAdditionalAttributes());
287 }
288 } catch (NamingException e) {
289 logger.error(sm.getString("activeDirectoryRealm.principalSearchFailed", gssName), e);
290
291 close(connection);
292 } finally {
293 release(connection);
294 }
295
296 return null;
297 }
298
299 protected DirContextConnection acquire() {
300 if (logger.isDebugEnabled())
301 logger.debug(sm.getString("activeDirectoryRealm.acquire"));
302
303 DirContextConnection connection = null;
304
305 while (connection == null) {
306 connection = connectionPool.pop();
307
308 if (connection != null) {
309 long idleTime = System.currentTimeMillis() - connection.lastBorrowTime;
310
311 if (idleTime > maxIdleTime) {
312 if (logger.isDebugEnabled())
313 logger.debug(sm.getString("activeDirectoryRealm.exceedMaxIdleTime"));
314 close(connection);
315 connection = null;
316 } else {
317 boolean valid = validate(connection);
318 if (valid) {
319 if (logger.isDebugEnabled())
320 logger.debug(sm.getString("activeDirectoryRealm.reuse"));
321 } else {
322 close(connection);
323 connection = null;
324 }
325 }
326 } else {
327 connection = new DirContextConnection();
328 open(connection);
329 }
330 }
331
332 connection.lastBorrowTime = System.currentTimeMillis();
333
334 return connection;
335 }
336
337 protected boolean validate(DirContextConnection connection) {
338 if (logger.isDebugEnabled())
339 logger.debug(sm.getString("activeDirectoryRealm.validate"));
340
341 SearchControls controls = new SearchControls();
342 controls.setSearchScope(SearchControls.OBJECT_SCOPE);
343 controls.setCountLimit(1);
344 controls.setReturningAttributes(new String[] { "objectClass" });
345 controls.setTimeLimit(500);
346
347 NamingEnumeration<SearchResult> results = null;
348 try {
349 results = connection.context.search("", "objectclass=*", controls);
350
351 if (results.hasMore()) {
352 close(results);
353 return true;
354 }
355 } catch (NamingException e) {
356 logger.error(sm.getString("activeDirectoryRealm.validate.namingException"), e);
357
358 return false;
359 }
360
361 close(results);
362
363 return false;
364 }
365
366 protected void release(DirContextConnection connection) {
367 if (connection.context == null)
368 return;
369
370 if (logger.isDebugEnabled())
371 logger.debug(sm.getString("activeDirectoryRealm.release"));
372 if (!connectionPool.push(connection))
373 close(connection);
374 }
375
376 protected void open(DirContextConnection connection) {
377 try {
378 javax.naming.Context context = null;
379
380 if (localDirContextSource) {
381 context = ContextBindings.getClassLoader();
382 context = (javax.naming.Context) context.lookup("comp/env");
383 } else {
384 Server server = getServer();
385 context = server.getGlobalNamingContext();
386 }
387
388 if (logger.isDebugEnabled())
389 logger.debug(sm.getString("activeDirectoryRealm.open"));
390 DirContextSource contextSource = (DirContextSource) context
391 .lookup(dirContextSourceName);
392 connection.context = contextSource.getDirContext();
393 } catch (NamingException e) {
394 logger.error(sm.getString("activeDirectoryRealm.open.namingException"), e);
395 }
396 }
397
398 protected void close(DirContextConnection connection) {
399 if (connection.context == null)
400 return;
401
402 try {
403 if (logger.isDebugEnabled())
404 logger.debug(sm.getString("activeDirectoryRealm.close"));
405 connection.context.close();
406 } catch (NamingException e) {
407 logger.error(sm.getString("activeDirectoryRealm.close.namingException"), e);
408 }
409
410 connection.context = null;
411 }
412
413 protected void close(NamingEnumeration<?> results) {
414 if (results == null)
415 return;
416
417 try {
418 results.close();
419 } catch (NamingException e) {
420 ;
421 }
422 }
423
424 @Override
425 protected void startInternal() throws LifecycleException {
426 connectionPool = new SynchronizedStack<>(connectionPoolSize, connectionPoolSize);
427
428 DirContextConnection connection = acquire();
429 if (connection.context == null)
430 return;
431
432 try {
433 String referral = (String) connection.context.getEnvironment().get(DirContext.REFERRAL);
434
435 if ("follow".equals(referral))
436 logger.warn(sm.getString("activeDirectoryRealm.referralFollow"));
437 } catch (NamingException e) {
438 logger.error(sm.getString("activeDirectoryRealm.environmentFailed"), e);
439
440 close(connection);
441 } finally {
442 release(connection);
443 }
444
445 super.startInternal();
446 }
447
448 @Override
449 protected void stopInternal() throws LifecycleException {
450 super.stopInternal();
451
452 DirContextConnection connection = null;
453 while ((connection = connectionPool.pop()) != null)
454 close(connection);
455
456 connectionPool = null;
457 }
458
459 protected User getUser(DirContext context, GSSName gssName) throws NamingException {
460 String[] attributes = DEFAULT_USER_ATTRIBUTES;
461
462 if (additionalAttributes != null && additionalAttributes.length > 0) {
463 attributes = new String[DEFAULT_USER_ATTRIBUTES.length + additionalAttributes.length];
464 System.arraycopy(DEFAULT_USER_ATTRIBUTES, 0, attributes, 0,
465 DEFAULT_USER_ATTRIBUTES.length);
466 System.arraycopy(additionalAttributes, 0, attributes, DEFAULT_USER_ATTRIBUTES.length,
467 additionalAttributes.length);
468 }
469
470 SearchControls searchCtls = new SearchControls();
471 searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
472 searchCtls.setReturningAttributes(attributes);
473
474
475 String searchFilterPattern = "(&(|(sAMAccountType=805306368)(sAMAccountType=805306369))(%s={0}))";
476
477 String searchFilter;
478 Name searchBase = null;
479 String searchAttributeName;
480 String searchAttributeValue;
481
482 MappedValues mappedValues;
483 NamingEnumeration<SearchResult> results = null;
484 for (UsernameSearchMapper mapper : USERNAME_SEARCH_MAPPERS) {
485 String mapperClassName = mapper.getClass().getSimpleName();
486 mappedValues = mapper.map(context, gssName);
487
488 searchBase = getRelativeName(context, mappedValues.getSearchBase());
489 searchAttributeName = mappedValues.getSearchAttributeName();
490 searchAttributeValue = mappedValues.getSearchUsername();
491
492 searchFilter = String.format(searchFilterPattern, searchAttributeName);
493
494 if (logger.isDebugEnabled())
495 logger.debug(sm.getString("activeDirectoryRealm.usernameSearch",
496 searchAttributeValue, searchBase, searchAttributeName, mapperClassName));
497
498 try {
499 results = context.search(searchBase, searchFilter,
500 new Object[] { searchAttributeValue }, searchCtls);
501 } catch (ReferralException e) {
502 logger.warn(sm.getString("activeDirectoryRealm.user.referralException",
503 mapperClassName, e.getRemainingName(), e.getReferralInfo()));
504
505 continue;
506 }
507
508 try {
509 if (!results.hasMore()) {
510 if (logger.isDebugEnabled())
511 logger.debug(sm.getString("activeDirectoryRealm.userNotMapped", gssName,
512 mapperClassName));
513
514 close(results);
515 } else
516 break;
517 } catch (PartialResultException e) {
518 logger.debug(sm.getString("activeDirectoryRealm.user.partialResultException",
519 mapperClassName, e.getRemainingName()));
520
521 close(results);
522 }
523 }
524
525 if (results == null) {
526 logger.debug(sm.getString("activeDirectoryRealm.userNotFound", gssName));
527
528 return null;
529 }
530
531 SearchResult result = results.next();
532
533 if (results.hasMore()) {
534 logger.error(sm.getString("activeDirectoryRealm.duplicateUser", gssName));
535
536 close(results);
537 return null;
538 }
539
540 Attributes userAttributes = result.getAttributes();
541
542 int userAccountControl = Integer
543 .parseInt((String) userAttributes.get("userAccountControl").get());
544
545
546 if ((userAccountControl & 0x02) != 0) {
547 logger.warn(sm.getString("activeDirectoryRealm.userFoundButDisabled", gssName));
548
549 close(results);
550 return null;
551 }
552
553 Name dn = getDistinguishedName(context, searchBase, result);
554 byte[] sidBytes = (byte[]) userAttributes.get("objectSid;binary").get();
555 Sid sid = new Sid(sidBytes);
556
557 if (logger.isDebugEnabled())
558 logger.debug(sm.getString("activeDirectoryRealm.userFound", gssName, dn, sid));
559
560 Attribute memberOfAttr = userAttributes.get("memberOf");
561
562 List<String> roles = new LinkedList<String>();
563
564 if (memberOfAttr != null && memberOfAttr.size() > 0) {
565 NamingEnumeration<?> memberOfValues = memberOfAttr.getAll();
566
567 while (memberOfValues.hasMore())
568 roles.add((String) memberOfValues.next());
569
570 close(memberOfValues);
571 }
572
573 Map<String, Object> additionalAttributesMap = Collections.emptyMap();
574
575 if (additionalAttributes != null && additionalAttributes.length > 0) {
576 additionalAttributesMap = new HashMap<String, Object>();
577
578 for (String addAttr : additionalAttributes) {
579 Attribute attr = userAttributes.get(addAttr);
580
581 if (attr != null && attr.size() > 0) {
582 if (attr.size() > 1) {
583 List<Object> attrList = new ArrayList<Object>(attr.size());
584 NamingEnumeration<?> attrEnum = attr.getAll();
585
586 while (attrEnum.hasMore())
587 attrList.add(attrEnum.next());
588
589 close(attrEnum);
590
591 additionalAttributesMap.put(addAttr,
592 Collections.unmodifiableList(attrList));
593 } else
594 additionalAttributesMap.put(addAttr, attr.get());
595 }
596 }
597 }
598
599 close(results);
600 return new User(gssName, sid, roles, additionalAttributesMap);
601 }
602
603 protected List<String> getRoles(DirContext context, User user) throws NamingException {
604 List<String> roles = new LinkedList<String>();
605
606 if (logger.isDebugEnabled())
607 logger.debug(sm.getString("activeDirectoryRealm.retrievingRoles", user.getGssName()));
608
609 for (String role : user.getRoles()) {
610 Name roleRdn = getRelativeName(context, role);
611
612 Attributes roleAttributes = null;
613 try {
614 roleAttributes = context.getAttributes(roleRdn, DEFAULT_ROLE_ATTRIBUTES);
615 } catch (ReferralException e) {
616 logger.warn(sm.getString("activeDirectoryRealm.role.referralException", role,
617 e.getRemainingName(), e.getReferralInfo()));
618
619 continue;
620 } catch (PartialResultException e) {
621 logger.debug(sm.getString("activeDirectoryRealm.role.partialResultException", role,
622 e.getRemainingName()));
623
624 continue;
625 }
626
627 int groupType = Integer.parseInt((String) roleAttributes.get("groupType").get());
628
629
630
631 if ((groupType & Integer.MIN_VALUE) == 0) {
632 if (logger.isTraceEnabled())
633 logger.trace(
634 sm.getString("activeDirectoryRealm.skippingDistributionRole", role));
635
636 continue;
637 }
638
639 byte[] objectSidBytes = (byte[]) roleAttributes.get("objectSid;binary").get();
640 String sidString = new Sid(objectSidBytes).toString();
641
642 Attribute sidHistory = roleAttributes.get("sIDHistory;binary");
643 List<String> sidHistoryStrings = new LinkedList<String>();
644 if (sidHistory != null) {
645 NamingEnumeration<?> sidHistoryEnum = sidHistory.getAll();
646 while (sidHistoryEnum.hasMore()) {
647 byte[] sidHistoryBytes = (byte[]) sidHistoryEnum.next();
648 sidHistoryStrings.add(new Sid(sidHistoryBytes).toString());
649 }
650
651 close(sidHistoryEnum);
652 }
653
654 roles.add(sidString);
655 roles.addAll(sidHistoryStrings);
656
657 if (logger.isTraceEnabled()) {
658 if (sidHistoryStrings.isEmpty())
659 logger.trace(sm.getString("activeDirectoryRealm.foundRoleConverted", role,
660 sidString));
661 else
662 logger.trace(
663 sm.getString("activeDirectoryRealm.foundRoleConverted.withSidHistory",
664 role, sidString, sidHistoryStrings));
665 }
666 }
667
668 if (logger.isDebugEnabled())
669 logger.debug(sm.getString("activeDirectoryRealm.foundRolesCount", roles.size(),
670 user.getGssName()));
671 if (logger.isTraceEnabled())
672 logger.trace(sm.getString("activeDirectoryRealm.foundRoles", user.getGssName(), roles));
673
674 return roles;
675 }
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690 protected Name getDistinguishedName(DirContext context, Name baseName, SearchResult result)
691 throws NamingException {
692
693
694
695 String resultName = result.getName();
696 if (result.isRelative()) {
697 NameParser parser = context.getNameParser(StringUtils.EMPTY);
698 Name contextName = parser.parse(context.getNameInNamespace());
699
700
701 Name entryName = parser.parse(new CompositeName(resultName).get(0));
702
703 Name name = contextName.addAll(baseName);
704 return name.addAll(entryName);
705 } else {
706 String absoluteName = result.getName();
707 try {
708
709 NameParser parser = context.getNameParser(StringUtils.EMPTY);
710 URI userNameUri = new URI(resultName);
711 String pathComponent = userNameUri.getPath();
712
713 if (pathComponent.length() < 1) {
714 throw new InvalidNameException(
715 sm.getString("activeDirectoryRealm.unparseableName", absoluteName));
716 }
717 return parser.parse(pathComponent.substring(1));
718 } catch (URISyntaxException e) {
719 throw new InvalidNameException(
720 sm.getString("activeDirectoryRealm.unparseableName", absoluteName));
721 }
722 }
723 }
724
725 protected Name getRelativeName(DirContext context, String distinguishedName)
726 throws NamingException {
727 NameParser parser = context.getNameParser(StringUtils.EMPTY);
728 LdapName nameInNamespace = (LdapName) parser.parse(context.getNameInNamespace());
729 LdapName name = (LdapName) parser.parse(distinguishedName);
730
731 Rdn nameRdn;
732 Rdn nameInNamespaceRdn;
733
734 while (Math.min(name.size(), nameInNamespace.size()) != 0) {
735 nameRdn = name.getRdn(0);
736 nameInNamespaceRdn = nameInNamespace.getRdn(0);
737 if (nameRdn.equals(nameInNamespaceRdn)) {
738 name.remove(0);
739 nameInNamespace.remove(0);
740 } else
741 break;
742 }
743
744 int innerPosn;
745 while (Math.min(name.size(), nameInNamespace.size()) != 0) {
746 innerPosn = nameInNamespace.size() - 1;
747 nameRdn = name.getRdn(0);
748 nameInNamespaceRdn = nameInNamespace.getRdn(innerPosn);
749 if (nameRdn.equals(nameInNamespaceRdn)) {
750 name.remove(0);
751 nameInNamespace.remove(innerPosn);
752 } else
753 break;
754 }
755
756 return name;
757 }
758
759 protected static class User {
760
761 private final GSSName gssName;
762 private final Sid sid;
763 private final List<String> roles;
764 private final Map<String, Object> additionalAttributes;
765
766 public User(GSSName gssName, Sid sid, List<String> roles,
767 Map<String, Object> additionalAttributes) {
768 this.gssName = gssName;
769 this.sid = sid;
770 this.roles = roles;
771 this.additionalAttributes = additionalAttributes;
772 }
773
774 public GSSName getGssName() {
775 return gssName;
776 }
777
778 public Sid getSid() {
779 return sid;
780 }
781
782 public List<String> getRoles() {
783 return roles;
784 }
785
786 public Map<String, Object> getAdditionalAttributes() {
787 return additionalAttributes;
788 }
789
790 }
791
792 }