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