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.security.cert.CertificateParsingException;
22 import java.security.cert.X509Certificate;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.LinkedList;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.Map;
32 import java.util.concurrent.atomic.AtomicLong;
33
34 import javax.naming.CommunicationException;
35 import javax.naming.CompositeName;
36 import javax.naming.InvalidNameException;
37 import javax.naming.Name;
38 import javax.naming.NameParser;
39 import javax.naming.NamingEnumeration;
40 import javax.naming.NamingException;
41 import javax.naming.PartialResultException;
42 import javax.naming.ReferralException;
43 import javax.naming.ServiceUnavailableException;
44 import javax.naming.directory.Attribute;
45 import javax.naming.directory.Attributes;
46 import javax.naming.directory.DirContext;
47 import javax.naming.directory.SearchControls;
48 import javax.naming.directory.SearchResult;
49 import javax.naming.ldap.LdapName;
50 import javax.naming.ldap.ManageReferralControl;
51 import javax.naming.ldap.Rdn;
52 import javax.security.auth.x500.X500Principal;
53
54 import net.sf.michaelo.dirctxsrc.DirContextSource;
55 import net.sf.michaelo.tomcat.realm.asn1.OtherNameAsn1Parser;
56 import net.sf.michaelo.tomcat.realm.asn1.OtherNameParseResult;
57 import net.sf.michaelo.tomcat.realm.mapper.SamAccountNameRfc2247Mapper;
58 import net.sf.michaelo.tomcat.realm.mapper.UserPrincipalNameSearchMapper;
59 import net.sf.michaelo.tomcat.realm.mapper.UsernameSearchMapper;
60 import net.sf.michaelo.tomcat.realm.mapper.UsernameSearchMapper.MappedValues;
61
62 import org.apache.catalina.LifecycleException;
63 import org.apache.catalina.Server;
64 import org.apache.catalina.realm.CombinedRealm;
65 import org.apache.commons.lang3.StringUtils;
66 import org.apache.naming.ContextBindings;
67 import org.apache.tomcat.util.codec.binary.Base64;
68 import org.apache.tomcat.util.collections.SynchronizedStack;
69 import org.ietf.jgss.GSSCredential;
70 import org.ietf.jgss.GSSException;
71 import org.ietf.jgss.GSSManager;
72 import org.ietf.jgss.GSSName;
73 import org.ietf.jgss.Oid;
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207 public class ActiveDirectoryRealm extends ActiveDirectoryRealmBase {
208
209
210 protected static class DirContextConnection {
211 protected String id;
212 protected long lastBorrowTime;
213 protected DirContext context;
214 }
215
216 private static final AtomicLong COUNT = new AtomicLong(0);
217
218
219 private static final byte[] MS_UPN_OID_BYTES = { (byte) 0x2B, (byte) 0x06, (byte) 0x01, (byte) 0x04, (byte) 0x01,
220 (byte) 0x82, (byte) 0x37, (byte) 0x14, (byte) 0x02, (byte) 0x03 };
221
222 private static final long UF_ACCOUNT_DISABLE = 0x00000002L;
223 private static final long UF_NORMAL_ACCOUNT = 0x00000200L;
224 private static final long UF_WORKSTATION_TRUST_ACCOUNT = 0x00001000L;
225
226 private final static Oid MS_UPN;
227 private final static Oid KRB5_NT_PRINCIPAL;
228
229 private final static Map<String, String> X500_PRINCIPAL_OID_MAP = new HashMap<String, String>();
230
231 private static final UsernameSearchMapper[] USERNAME_SEARCH_MAPPERS = {
232 new SamAccountNameRfc2247Mapper(), new UserPrincipalNameSearchMapper() };
233
234 private static final String[] DEFAULT_USER_ATTRIBUTES = new String[] { "userAccountControl",
235 "memberOf", "objectSid;binary", "sAMAccountName" };
236
237 private static final String[] DEFAULT_ROLE_ATTRIBUTES = new String[] { "groupType" };
238
239 private static final String DEFAULT_ROLE_FORMAT = "sid";
240
241 private static final Map<String, String[]> ROLE_FORMAT_ATTRIBUTES = new HashMap<>();
242
243 static {
244 try {
245 MS_UPN = new Oid("1.3.6.1.4.1.311.20.2.3");
246 } catch (GSSException e) {
247 throw new IllegalStateException("Failed to create OID for MS_UPN");
248 }
249
250 try {
251 KRB5_NT_PRINCIPAL = new Oid("1.2.840.113554.1.2.2.1");
252 } catch (GSSException e) {
253 throw new IllegalStateException("Failed to create OID for KRB5_NT_PRINCIPAL");
254 }
255
256 X500_PRINCIPAL_OID_MAP.put("1.2.840.113549.1.9.1", "emailAddress");
257 X500_PRINCIPAL_OID_MAP.put("2.5.4.5", "serialNumber");
258
259 X500_PRINCIPAL_OID_MAP.put("2.5.4.4", "SN");
260
261 X500_PRINCIPAL_OID_MAP.put("2.5.4.42", "GN");
262
263 ROLE_FORMAT_ATTRIBUTES.put("sid", new String[] { "objectSid;binary", "sIDHistory;binary" });
264 ROLE_FORMAT_ATTRIBUTES.put("name", new String [] { "msDS-PrincipalName" } );
265 ROLE_FORMAT_ATTRIBUTES.put("nameEx", new String [] { "distinguishedName", "sAMAccountName" } );
266 }
267
268 protected boolean localDirContextSource;
269 protected String dirContextSourceName;
270
271 protected String[] attributes;
272 protected String[] additionalAttributes;
273
274 protected String[] roleFormats;
275 protected String[] roleAttributes;
276
277 protected boolean prependRoleFormat;
278
279 protected int connectionPoolSize = 0;
280 protected long maxIdleTime = 900_000L;
281
282
283 protected SynchronizedStack<DirContextConnection> connectionPool;
284
285
286
287
288 protected static final String name = "ActiveDirectoryRealm";
289
290 protected static String getNextConnectionId() {
291 return String.format("conn-%06d", COUNT.incrementAndGet());
292 }
293
294
295
296
297
298
299
300
301 public void setLocalDirContextSource(boolean localDirContextSource) {
302 this.localDirContextSource = localDirContextSource;
303 }
304
305
306
307
308
309
310
311 public void setDirContextSourceName(String dirContextSourceName) {
312 this.dirContextSourceName = dirContextSourceName;
313 }
314
315
316
317
318
319
320
321
322 public void setAdditionalAttributes(String additionalAttributes) {
323 this.additionalAttributes = additionalAttributes.split(",");
324
325 this.attributes = new String[DEFAULT_USER_ATTRIBUTES.length + this.additionalAttributes.length];
326 System.arraycopy(DEFAULT_USER_ATTRIBUTES, 0, this.attributes, 0,
327 DEFAULT_USER_ATTRIBUTES.length);
328 System.arraycopy(this.additionalAttributes, 0, this.attributes, DEFAULT_USER_ATTRIBUTES.length,
329 this.additionalAttributes.length);
330 }
331
332
333
334
335
336
337
338 public void setRoleFormats(String roleFormats) {
339 this.roleFormats = roleFormats.split(",");
340 List<String> attributes = new ArrayList<>(Arrays.asList(DEFAULT_ROLE_ATTRIBUTES));
341 for (String roleFormat : this.roleFormats) {
342 if (ROLE_FORMAT_ATTRIBUTES.get(roleFormat) != null)
343 attributes.addAll(Arrays.asList(ROLE_FORMAT_ATTRIBUTES.get(roleFormat)));
344 }
345
346 this.roleAttributes = attributes.toArray(new String[0]);
347 }
348
349
350
351
352
353
354
355 public void setPrependRoleFormat(boolean prependRoleFormat) {
356 this.prependRoleFormat = prependRoleFormat;
357 }
358
359
360
361
362
363
364
365 public void setConnectionPoolSize(int connectionPoolSize) {
366 this.connectionPoolSize = connectionPoolSize;
367 }
368
369
370
371
372
373
374
375
376 public void setMaxIdleTime(long maxIdleTime) {
377 this.maxIdleTime = maxIdleTime;
378 }
379
380 @Override
381 protected String getName() {
382 return name;
383 }
384
385 @Override
386 protected Principal getPrincipal(X509Certificate userCert) {
387 try {
388 Collection<List<?>> san = userCert.getSubjectAlternativeNames();
389 if (san == null || san.isEmpty())
390 return null;
391
392 String dn = userCert.getSubjectX500Principal().getName(X500Principal.RFC2253, X500_PRINCIPAL_OID_MAP);
393 for (List<?> sanField : san) {
394 Integer nameType = (Integer) sanField.get(0);
395
396 if (nameType == 0) {
397 byte[] otherName = (byte[]) sanField.get(1);
398 if (logger.isDebugEnabled())
399 logger.debug(sm.getString("activeDirectoryRealm.processingSanOtherName",
400 Base64.encodeBase64String(otherName), dn));
401 try {
402 OtherNameParseResult result = OtherNameAsn1Parser.parse(otherName);
403 if (Arrays.equals(result.getTypeId(), MS_UPN_OID_BYTES)) {
404 String upn = OtherNameAsn1Parser.parseUtf8String(result.getValue());
405 if (logger.isDebugEnabled())
406 logger.debug(sm.getString("activeDirectoryRealm.msUpnExtracted", upn, dn));
407
408 GSSName gssName = new StubGSSName(upn, MS_UPN);
409
410 return getPrincipal(gssName, null, true);
411 }
412 } catch (IllegalArgumentException | ArrayIndexOutOfBoundsException e) {
413 logger.warn(sm.getString("activeDirectoryRealm.sanOtherNameParsingFailed"), e);
414 }
415 }
416 }
417 } catch (CertificateParsingException e) {
418 logger.warn(sm.getString("activeDirectoryRealm.sanParsingFailed"), e);
419 }
420
421 return null;
422 }
423
424 @Override
425 protected Principal getPrincipal(GSSName gssName, GSSCredential gssCredential) {
426 if (gssName.isAnonymous())
427 return new ActiveDirectoryPrincipal(gssName, Sid.ANONYMOUS_SID, gssCredential);
428
429 return getPrincipal(gssName, gssCredential, true);
430 }
431
432 protected Principal getPrincipal(GSSName gssName, GSSCredential gssCredential, boolean retry) {
433 DirContextConnection connection = null;
434 try {
435 connection = acquire();
436
437 try {
438 User user = getUser(connection.context, gssName);
439
440 if (user != null) {
441 List<String> roles = getRoles(connection.context, user);
442
443 return new ActiveDirectoryPrincipal(user.getGssName(), user.getSid(), roles, gssCredential,
444 user.getAdditionalAttributes());
445 }
446 } catch (NamingException e) {
447
448
449 boolean canRetry = false;
450 if (e instanceof CommunicationException || e instanceof ServiceUnavailableException)
451 canRetry = true;
452 else {
453 String explanation = e.getExplanation();
454 if (explanation.equals("LDAP connection has been closed")
455 || explanation.startsWith("LDAP response read timed out, timeout used:"))
456 canRetry = true;
457 }
458
459 if (retry && canRetry) {
460 logger.error(sm.getString("activeDirectoryRealm.principalSearchFailed.retry", gssName), e);
461
462 close(connection);
463
464 return getPrincipal(gssName, gssCredential, false);
465 }
466
467 logger.error(sm.getString("activeDirectoryRealm.principalSearchFailed", gssName), e);
468
469 close(connection);
470 }
471 } catch (NamingException e) {
472 logger.error(sm.getString("activeDirectoryRealm.acquire.namingException"), e);
473 } finally {
474 release(connection);
475 }
476
477 return null;
478 }
479
480 protected DirContextConnection acquire() throws NamingException {
481 if (logger.isDebugEnabled())
482 logger.debug(sm.getString("activeDirectoryRealm.acquire"));
483
484 DirContextConnection connection = null;
485
486 while (connection == null) {
487 connection = connectionPool.pop();
488
489 if (connection != null) {
490 long idleTime = System.currentTimeMillis() - connection.lastBorrowTime;
491
492 if (idleTime > maxIdleTime) {
493 if (logger.isDebugEnabled())
494 logger.debug(sm.getString("activeDirectoryRealm.exceedMaxIdleTime", connection.id));
495 close(connection);
496 connection = null;
497 } else {
498 boolean valid = validate(connection);
499 if (valid) {
500 if (logger.isDebugEnabled())
501 logger.debug(sm.getString("activeDirectoryRealm.reuse", connection.id));
502 } else {
503 close(connection);
504 connection = null;
505 }
506 }
507 } else {
508 connection = new DirContextConnection();
509 open(connection);
510 }
511 }
512
513 connection.lastBorrowTime = System.currentTimeMillis();
514
515 if (logger.isDebugEnabled())
516 logger.debug(sm.getString("activeDirectoryRealm.acquired", connection.id));
517
518 return connection;
519 }
520
521 protected boolean validate(DirContextConnection connection) {
522 if (logger.isDebugEnabled())
523 logger.debug(sm.getString("activeDirectoryRealm.validate", connection.id));
524
525 SearchControls controls = new SearchControls();
526 controls.setSearchScope(SearchControls.OBJECT_SCOPE);
527 controls.setCountLimit(1);
528 controls.setReturningAttributes(new String[] { "objectClass" });
529
530
531 controls.setTimeLimit(500);
532
533 NamingEnumeration<SearchResult> results = null;
534 try {
535 results = connection.context.search("", "objectclass=*", controls);
536
537 if (results.hasMore())
538 return true;
539 } catch (NamingException e) {
540 logger.error(sm.getString("activeDirectoryRealm.validate.namingException", connection.id), e);
541 } finally {
542 close(results);
543 }
544
545 return false;
546 }
547
548 protected void release(DirContextConnection connection) {
549 if (connection == null)
550 return;
551
552 if (connection.context == null)
553 return;
554
555 if (logger.isDebugEnabled())
556 logger.debug(sm.getString("activeDirectoryRealm.release", connection.id));
557 if (!connectionPool.push(connection))
558 close(connection);
559 }
560
561 protected void open(DirContextConnection connection) throws NamingException {
562 javax.naming.Context context = null;
563
564 if (localDirContextSource) {
565 context = ContextBindings.getClassLoader();
566 context = (javax.naming.Context) context.lookup("comp/env");
567 } else {
568 Server server = getServer();
569 context = server.getGlobalNamingContext();
570 }
571
572 if (logger.isDebugEnabled())
573 logger.debug(sm.getString("activeDirectoryRealm.open"));
574 DirContextSource contextSource = (DirContextSource) context
575 .lookup(dirContextSourceName);
576 connection.context = contextSource.getDirContext();
577 connection.id = getNextConnectionId();
578 if (logger.isDebugEnabled())
579 logger.debug(sm.getString("activeDirectoryRealm.opened", connection.id));
580 }
581
582 protected void close(DirContextConnection connection) {
583 if (connection.context == null)
584 return;
585
586 try {
587 if (logger.isDebugEnabled())
588 logger.debug(sm.getString("activeDirectoryRealm.close", connection.id));
589 connection.context.close();
590 if (logger.isDebugEnabled())
591 logger.debug(sm.getString("activeDirectoryRealm.closed", connection.id));
592 } catch (NamingException e) {
593 logger.error(sm.getString("activeDirectoryRealm.close.namingException", connection.id), e);
594 }
595
596 connection.context = null;
597 }
598
599 protected void close(NamingEnumeration<?> results) {
600 if (results == null)
601 return;
602
603 try {
604 results.close();
605 } catch (NamingException e) {
606 ;
607 }
608 }
609
610 @Override
611 protected void initInternal() throws LifecycleException {
612 super.initInternal();
613
614 if (attributes == null)
615 attributes = DEFAULT_USER_ATTRIBUTES;
616
617 if (roleFormats == null)
618 setRoleFormats(DEFAULT_ROLE_FORMAT);
619 }
620
621 @Override
622 protected void startInternal() throws LifecycleException {
623 connectionPool = new SynchronizedStack<>(connectionPoolSize, connectionPoolSize);
624
625 DirContextConnection connection = null;
626 try {
627 connection = acquire();
628
629 try {
630 String referral = (String) connection.context.getEnvironment().get(DirContext.REFERRAL);
631
632 if ("follow".equals(referral))
633 logger.warn(sm.getString("activeDirectoryRealm.referralFollow"));
634 } catch (NamingException e) {
635 logger.error(sm.getString("activeDirectoryRealm.environmentFailed"), e);
636
637 close(connection);
638 }
639 } catch (NamingException e) {
640 logger.error(sm.getString("activeDirectoryRealm.acquire.namingException"), e);
641 } finally {
642 release(connection);
643 }
644
645 super.startInternal();
646 }
647
648 @Override
649 protected void stopInternal() throws LifecycleException {
650 super.stopInternal();
651
652 DirContextConnection connection = null;
653 while ((connection = connectionPool.pop()) != null)
654 close(connection);
655
656 connectionPool = null;
657 }
658
659 private Oid getStringNameType(GSSName gssName) {
660 try {
661 return gssName.getStringNameType();
662 } catch (GSSException e) {
663 return null;
664 }
665 }
666
667 private String toRealm(Name distinguishedName) {
668 LdapName dn = (LdapName) distinguishedName;
669
670 StringBuilder realm = new StringBuilder();
671 for(Rdn rdn : dn.getRdns())
672 if (rdn.getType().equalsIgnoreCase("DC"))
673 realm.insert(0, ((String) rdn.getValue()).toUpperCase(Locale.ROOT) + ".");
674
675 if (realm.length() > 0)
676 realm.deleteCharAt(realm.length() - 1);
677
678 return realm.toString();
679 }
680
681 protected User getUser(DirContext context, GSSName gssName) throws NamingException {
682 SearchControls searchCtls = new SearchControls();
683 searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
684 searchCtls.setReturningAttributes(attributes);
685
686 String searchFilter;
687 Name searchBase = null;
688 String searchAttributeName;
689 String searchAttributeValue;
690
691 MappedValues mappedValues;
692 NamingEnumeration<SearchResult> results = null;
693 for (UsernameSearchMapper mapper : USERNAME_SEARCH_MAPPERS) {
694 String mapperClassName = mapper.getClass().getSimpleName();
695
696 if (!mapper.supportsGssName(gssName)) {
697 if (logger.isDebugEnabled())
698 logger.debug(sm.getString("activeDirectoryRealm.nameTypeNotSupported", mapperClassName,
699 getStringNameType(gssName), gssName));
700
701 continue;
702 }
703
704 mappedValues = mapper.map(context, gssName);
705
706 searchBase = getRelativeName(context, mappedValues.getSearchBase());
707 searchAttributeName = mappedValues.getSearchAttributeName();
708 searchAttributeValue = mappedValues.getSearchUsername();
709
710 searchFilter = String.format("(%s={0})", searchAttributeName);
711
712 if (logger.isDebugEnabled())
713 logger.debug(sm.getString("activeDirectoryRealm.usernameSearch",
714 searchAttributeValue, searchBase, searchAttributeName, mapperClassName));
715
716 try {
717 results = context.search(searchBase, searchFilter,
718 new Object[] { searchAttributeValue }, searchCtls);
719 } catch (ReferralException e) {
720 logger.warn(sm.getString("activeDirectoryRealm.user.referralException",
721 mapperClassName, e.getRemainingName(), e.getReferralInfo()));
722
723 continue;
724 }
725
726 try {
727 if (!results.hasMore()) {
728 if (logger.isDebugEnabled())
729 logger.debug(sm.getString("activeDirectoryRealm.userNotFoundWithMapper", gssName,
730 mapperClassName));
731
732 close(results);
733 } else
734 break;
735 } catch (PartialResultException e) {
736 logger.debug(sm.getString("activeDirectoryRealm.user.partialResultException",
737 mapperClassName, e.getRemainingName()));
738
739 close(results);
740 }
741 }
742
743 if (results == null || !results.hasMore()) {
744 logger.debug(sm.getString("activeDirectoryRealm.userNotFound", gssName));
745
746 close(results);
747 return null;
748 }
749
750 SearchResult result = results.next();
751
752 try {
753 if (results.hasMore()) {
754 logger.error(sm.getString("activeDirectoryRealm.duplicateUser", gssName));
755
756 close(results);
757 return null;
758 }
759 } catch (ReferralException e) {
760 logger.warn(sm.getString("activeDirectoryRealm.duplicateUser.referralException", gssName,
761 e.getRemainingName(), e.getReferralInfo()));
762 } catch (PartialResultException e) {
763 logger.debug(sm.getString("activeDirectoryRealm.duplicateUser.partialResultException", gssName,
764 e.getRemainingName()));
765 }
766
767 close(results);
768
769 Attributes userAttributes = result.getAttributes();
770
771 long userAccountControl = Long
772 .parseLong((String) userAttributes.get("userAccountControl").get());
773
774 if ((userAccountControl & UF_ACCOUNT_DISABLE) != 0L) {
775 logger.warn(sm.getString("activeDirectoryRealm.userFoundButDisabled", gssName));
776 return null;
777 }
778
779 if ((userAccountControl & UF_NORMAL_ACCOUNT) == 0L && (userAccountControl & UF_WORKSTATION_TRUST_ACCOUNT) == 0L) {
780 logger.warn(sm.getString("activeDirectoryRealm.userFoundButNotSupported", gssName));
781 return null;
782 }
783
784 Name dn = getDistinguishedName(context, searchBase, result);
785 byte[] sidBytes = (byte[]) userAttributes.get("objectSid;binary").get();
786 Sid sid = new Sid(sidBytes);
787
788 if (logger.isDebugEnabled())
789 logger.debug(sm.getString("activeDirectoryRealm.userFound", gssName, dn, sid));
790
791 if (!KRB5_NT_PRINCIPAL.equals(getStringNameType(gssName))) {
792 String samAccountName = (String) userAttributes.get("sAMAccountName").get();
793 String realm = toRealm(dn);
794 String krb5Principal = samAccountName + "@" + realm;
795
796 if (logger.isTraceEnabled())
797 logger.trace(sm.getString("activeDirectoryRealm.canonicalizingUser", getStringNameType(gssName),
798 KRB5_NT_PRINCIPAL));
799
800 GSSName canonGssName = null;
801 try {
802 canonGssName = GSSManager.getInstance().createName(krb5Principal, KRB5_NT_PRINCIPAL);
803 } catch (GSSException e) {
804 logger.warn(sm.getString("activeDirectoryRealm.canonicalizeUserFailed", gssName));
805
806 return null;
807 }
808
809 if (logger.isDebugEnabled())
810 logger.debug(sm.getString("activeDirectoryRealm.userCanonicalized", canonGssName));
811
812 gssName = canonGssName;
813 }
814
815 Attribute memberOfAttr = userAttributes.get("memberOf");
816
817 List<String> memberOfs = new LinkedList<String>();
818
819 if (memberOfAttr != null && memberOfAttr.size() > 0) {
820 NamingEnumeration<?> memberOfValues = memberOfAttr.getAll();
821
822 while (memberOfValues.hasMore())
823 memberOfs.add((String) memberOfValues.next());
824
825 close(memberOfValues);
826 }
827
828 Map<String, Object> additionalAttributesMap = Collections.emptyMap();
829
830 if (additionalAttributes != null && additionalAttributes.length > 0) {
831 additionalAttributesMap = new HashMap<String, Object>();
832
833 for (String addAttr : additionalAttributes) {
834 Attribute attr = userAttributes.get(addAttr);
835
836 if (attr != null && attr.size() > 0) {
837 if (attr.size() > 1) {
838 List<Object> attrList = new ArrayList<Object>(attr.size());
839 NamingEnumeration<?> attrEnum = attr.getAll();
840
841 while (attrEnum.hasMore())
842 attrList.add(attrEnum.next());
843
844 close(attrEnum);
845
846 additionalAttributesMap.put(addAttr,
847 Collections.unmodifiableList(attrList));
848 } else
849 additionalAttributesMap.put(addAttr, attr.get());
850 }
851 }
852 }
853
854 return new User(gssName, sid, memberOfs, additionalAttributesMap);
855 }
856
857 protected List<String> getRoles(DirContext context, User user) throws NamingException {
858 List<String> roles = new LinkedList<String>();
859
860 if (logger.isDebugEnabled())
861 logger.debug(sm.getString("activeDirectoryRealm.retrievingRoles", user.getRoles().size(), user.getGssName()));
862
863 for (String role : user.getRoles()) {
864 Name roleRdn = getRelativeName(context, role);
865
866 Attributes roleAttributes = null;
867 try {
868 roleAttributes = context.getAttributes(roleRdn, this.roleAttributes);
869 } catch (ReferralException e) {
870 logger.warn(sm.getString("activeDirectoryRealm.role.referralException", role,
871 e.getRemainingName(), e.getReferralInfo()));
872
873 continue;
874 } catch (PartialResultException e) {
875 logger.debug(sm.getString("activeDirectoryRealm.role.partialResultException", role,
876 e.getRemainingName()));
877
878 continue;
879 }
880
881 int groupType = Integer.parseInt((String) roleAttributes.get("groupType").get());
882
883
884
885 if ((groupType & Integer.MIN_VALUE) == 0) {
886 if (logger.isTraceEnabled())
887 logger.trace(
888 sm.getString("activeDirectoryRealm.skippingDistributionRole", role));
889
890 continue;
891 }
892
893 for (String roleFormat: roleFormats) {
894
895 String roleFormatPrefix = prependRoleFormat ? roleFormat + ":" : "";
896
897 switch(roleFormat) {
898 case "sid":
899 byte[] objectSidBytes = (byte[]) roleAttributes.get("objectSid;binary").get();
900 String sidString = new Sid(objectSidBytes).toString();
901
902 roles.add(roleFormatPrefix + sidString);
903
904 Attribute sidHistory = roleAttributes.get("sIDHistory;binary");
905 List<String> sidHistoryStrings = new LinkedList<String>();
906 if (sidHistory != null) {
907 NamingEnumeration<?> sidHistoryEnum = sidHistory.getAll();
908 while (sidHistoryEnum.hasMore()) {
909 byte[] sidHistoryBytes = (byte[]) sidHistoryEnum.next();
910 String sidHistoryString = new Sid(sidHistoryBytes).toString();
911 sidHistoryStrings.add(sidHistoryString);
912
913 roles.add(roleFormatPrefix + sidHistoryString);
914 }
915
916 close(sidHistoryEnum);
917 }
918
919 if (logger.isTraceEnabled()) {
920 if (sidHistoryStrings.isEmpty())
921 logger.trace(sm.getString("activeDirectoryRealm.foundRoleSidConverted", role,
922 sidString));
923 else
924 logger.trace(
925 sm.getString("activeDirectoryRealm.foundRoleSidConverted.withSidHistory",
926 role, sidString, sidHistoryStrings));
927 }
928 break;
929 case "name":
930 String msDsPrincipalName = (String) roleAttributes.get("msDS-PrincipalName").get();
931
932 roles.add(roleFormatPrefix + msDsPrincipalName);
933
934 if (logger.isTraceEnabled())
935 logger.trace(sm.getString("activeDirectoryRealm.foundRoleNameConverted", role,
936 msDsPrincipalName));
937 break;
938 case "nameEx":
939 String distinguishedName = (String) roleAttributes.get("distinguishedName").get();
940 String samAccountName = (String) roleAttributes.get("sAMAccountName").get();
941
942 NameParser parser = context.getNameParser(StringUtils.EMPTY);
943 LdapName dn = (LdapName) parser.parse(distinguishedName);
944 String realm = toRealm(dn);
945 String nameEx = realm + "\\" + samAccountName;
946
947 roles.add(roleFormatPrefix + nameEx);
948
949 if (logger.isTraceEnabled())
950 logger.trace(sm.getString("activeDirectoryRealm.foundRoleNameExConverted", role,
951 nameEx));
952 break;
953 default:
954 throw new IllegalArgumentException("The role format '" + roleFormat + "' is invalid");
955 }
956 }
957 }
958
959 if (logger.isTraceEnabled())
960 logger.trace(sm.getString("activeDirectoryRealm.foundRoles", roles.size(), user.getGssName(), roles));
961 else if (logger.isDebugEnabled())
962 logger.debug(sm.getString("activeDirectoryRealm.foundRolesCount", roles.size(),
963 user.getGssName()));
964
965 return roles;
966 }
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981 protected Name getDistinguishedName(DirContext context, Name baseName, SearchResult result)
982 throws NamingException {
983
984
985
986 String resultName = result.getName();
987 if (result.isRelative()) {
988 NameParser parser = context.getNameParser(StringUtils.EMPTY);
989 Name contextName = parser.parse(context.getNameInNamespace());
990
991
992 Name entryName = parser.parse(new CompositeName(resultName).get(0));
993
994 Name name = contextName.addAll(baseName);
995 return name.addAll(entryName);
996 } else {
997 String absoluteName = result.getName();
998 try {
999
1000 NameParser parser = context.getNameParser(StringUtils.EMPTY);
1001 URI userNameUri = new URI(resultName);
1002 String pathComponent = userNameUri.getPath();
1003
1004 if (pathComponent.length() < 1) {
1005 throw new InvalidNameException(
1006 sm.getString("activeDirectoryRealm.unparseableName", absoluteName));
1007 }
1008 return parser.parse(pathComponent.substring(1));
1009 } catch (URISyntaxException e) {
1010 throw new InvalidNameException(
1011 sm.getString("activeDirectoryRealm.unparseableName", absoluteName));
1012 }
1013 }
1014 }
1015
1016 protected Name getRelativeName(DirContext context, String distinguishedName)
1017 throws NamingException {
1018 NameParser parser = context.getNameParser(StringUtils.EMPTY);
1019 LdapName nameInNamespace = (LdapName) parser.parse(context.getNameInNamespace());
1020 LdapName name = (LdapName) parser.parse(distinguishedName);
1021
1022 Rdn nameRdn;
1023 Rdn nameInNamespaceRdn;
1024
1025 while (Math.min(name.size(), nameInNamespace.size()) != 0) {
1026 nameRdn = name.getRdn(0);
1027 nameInNamespaceRdn = nameInNamespace.getRdn(0);
1028 if (nameRdn.equals(nameInNamespaceRdn)) {
1029 name.remove(0);
1030 nameInNamespace.remove(0);
1031 } else
1032 break;
1033 }
1034
1035 int innerPosn;
1036 while (Math.min(name.size(), nameInNamespace.size()) != 0) {
1037 innerPosn = nameInNamespace.size() - 1;
1038 nameRdn = name.getRdn(0);
1039 nameInNamespaceRdn = nameInNamespace.getRdn(innerPosn);
1040 if (nameRdn.equals(nameInNamespaceRdn)) {
1041 name.remove(0);
1042 nameInNamespace.remove(innerPosn);
1043 } else
1044 break;
1045 }
1046
1047 return name;
1048 }
1049
1050 protected static class User {
1051
1052 private final GSSName gssName;
1053 private final Sid sid;
1054 private final List<String> roles;
1055 private final Map<String, Object> additionalAttributes;
1056
1057 public User(GSSName gssName, Sid sid, List<String> roles,
1058 Map<String, Object> additionalAttributes) {
1059 this.gssName = gssName;
1060 this.sid = sid;
1061 this.roles = roles;
1062 this.additionalAttributes = additionalAttributes;
1063 }
1064
1065 public GSSName getGssName() {
1066 return gssName;
1067 }
1068
1069 public Sid getSid() {
1070 return sid;
1071 }
1072
1073 public List<String> getRoles() {
1074 return roles;
1075 }
1076
1077 public Map<String, Object> getAdditionalAttributes() {
1078 return additionalAttributes;
1079 }
1080
1081 }
1082
1083 }