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