]> git.argeo.org Git - lgpl/argeo-commons.git/blob - useradmin/AbstractUserDirectory.java
Prepare next development cycle
[lgpl/argeo-commons.git] / useradmin / AbstractUserDirectory.java
1 package org.argeo.osgi.useradmin;
2
3 import static org.argeo.util.naming.LdapAttrs.objectClass;
4 import static org.argeo.util.naming.LdapObjs.extensibleObject;
5 import static org.argeo.util.naming.LdapObjs.inetOrgPerson;
6 import static org.argeo.util.naming.LdapObjs.organizationalPerson;
7 import static org.argeo.util.naming.LdapObjs.person;
8 import static org.argeo.util.naming.LdapObjs.top;
9
10 import java.io.File;
11 import java.net.URI;
12 import java.net.URISyntaxException;
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.Dictionary;
16 import java.util.Enumeration;
17 import java.util.Hashtable;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.Optional;
21 import java.util.StringJoiner;
22
23 import javax.naming.InvalidNameException;
24 import javax.naming.NameNotFoundException;
25 import javax.naming.NamingEnumeration;
26 import javax.naming.NamingException;
27 import javax.naming.directory.Attribute;
28 import javax.naming.directory.Attributes;
29 import javax.naming.directory.BasicAttribute;
30 import javax.naming.directory.BasicAttributes;
31 import javax.naming.ldap.LdapName;
32 import javax.naming.ldap.Rdn;
33
34 import org.argeo.osgi.transaction.WorkControl;
35 import org.argeo.util.naming.LdapAttrs;
36 import org.argeo.util.naming.LdapObjs;
37 import org.osgi.framework.Filter;
38 import org.osgi.framework.FrameworkUtil;
39 import org.osgi.framework.InvalidSyntaxException;
40 import org.osgi.service.useradmin.Authorization;
41 import org.osgi.service.useradmin.Role;
42 import org.osgi.service.useradmin.User;
43 import org.osgi.service.useradmin.UserAdmin;
44
45 /** Base class for a {@link UserDirectory}. */
46 abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
47 static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name";
48 static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password";
49
50 private final Hashtable<String, Object> properties;
51 private final LdapName baseDn;
52 // private final LdapName userBaseDn, groupBaseDn;
53 private final Rdn userBaseRdn, groupBaseRdn, systemRoleBaseRdn;
54 private final String userObjectClass, groupObjectClass;
55
56 private final boolean readOnly;
57 private final boolean disabled;
58 private final String uri;
59
60 private UserAdmin externalRoles;
61 // private List<String> indexedUserProperties = Arrays
62 // .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(),
63 // LdapAttrs.cn.name() });
64
65 private final boolean scoped;
66
67 private String memberAttributeId = "member";
68 private List<String> credentialAttributeIds = Arrays
69 .asList(new String[] { LdapAttrs.userPassword.name(), LdapAttrs.authPassword.name() });
70
71 // Transaction
72 // private TransactionManager transactionManager;
73 private WorkControl transactionControl;
74 private WcXaResource xaResource = new WcXaResource(this);
75
76 private String forcedPassword;
77
78 AbstractUserDirectory(URI uriArg, Dictionary<String, ?> props, boolean scoped) {
79 this.scoped = scoped;
80 properties = new Hashtable<String, Object>();
81 for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
82 String key = keys.nextElement();
83 properties.put(key, props.get(key));
84 }
85
86 if (uriArg != null) {
87 uri = uriArg.toString();
88 // uri from properties is ignored
89 } else {
90 String uriStr = UserAdminConf.uri.getValue(properties);
91 if (uriStr == null)
92 uri = null;
93 else
94 uri = uriStr;
95 }
96
97 forcedPassword = UserAdminConf.forcedPassword.getValue(properties);
98
99 userObjectClass = UserAdminConf.userObjectClass.getValue(properties);
100 String userBase = UserAdminConf.userBase.getValue(properties);
101 groupObjectClass = UserAdminConf.groupObjectClass.getValue(properties);
102 String groupBase = UserAdminConf.groupBase.getValue(properties);
103 String systemRoleBase = UserAdminConf.systemRoleBase.getValue(properties);
104 try {
105 baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties));
106 userBaseRdn = new Rdn(userBase);
107 // userBaseDn = new LdapName(userBase + "," + baseDn);
108 groupBaseRdn = new Rdn(groupBase);
109 // groupBaseDn = new LdapName(groupBase + "," + baseDn);
110 systemRoleBaseRdn = new Rdn(systemRoleBase);
111 } catch (InvalidNameException e) {
112 throw new IllegalArgumentException("Badly formated base DN " + UserAdminConf.baseDn.getValue(properties),
113 e);
114 }
115
116 // read only
117 String readOnlyStr = UserAdminConf.readOnly.getValue(properties);
118 if (readOnlyStr == null) {
119 readOnly = readOnlyDefault(uri);
120 properties.put(UserAdminConf.readOnly.name(), Boolean.toString(readOnly));
121 } else
122 readOnly = Boolean.parseBoolean(readOnlyStr);
123
124 // disabled
125 String disabledStr = UserAdminConf.disabled.getValue(properties);
126 if (disabledStr != null)
127 disabled = Boolean.parseBoolean(disabledStr);
128 else
129 disabled = false;
130 }
131
132 /*
133 * ABSTRACT METHODS
134 */
135
136 /** Returns the groups this user is a direct member of. */
137 protected abstract List<LdapName> getDirectGroups(LdapName dn);
138
139 protected abstract Boolean daoHasRole(LdapName dn);
140
141 protected abstract DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException;
142
143 protected abstract List<DirectoryUser> doGetRoles(LdapName searchBase, Filter f, boolean deep);
144
145 protected abstract AbstractUserDirectory scope(User user);
146
147 protected abstract HierarchyUnit doGetHierarchyUnit(LdapName dn);
148
149 protected abstract Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly);
150
151 /*
152 * INITIALIZATION
153 */
154
155 public void init() {
156
157 }
158
159 public void destroy() {
160
161 }
162
163 /*
164 * PATHS
165 */
166
167 @Override
168 public String getContext() {
169 return getBaseDn().toString();
170 }
171
172 @Override
173 public String getName() {
174 return nameToSimple(getBaseDn(), ".");
175 }
176
177 @Override
178 public String getRolePath(Role role) {
179 return nameToRelativePath(((DirectoryUser) role).getDn());
180 }
181
182 @Override
183 public String getRoleSimpleName(Role role) {
184 LdapName dn = LdapNameUtils.toLdapName(role.getName());
185 String name = LdapNameUtils.getLastRdnValue(dn);
186 return name;
187 }
188
189 protected String nameToRelativePath(LdapName dn) {
190 LdapName name = LdapNameUtils.relativeName(getBaseDn(), dn);
191 return nameToSimple(name, "/");
192 }
193
194 protected String nameToSimple(LdapName name, String separator) {
195 StringJoiner path = new StringJoiner(separator);
196 for (int i = 0; i < name.size(); i++) {
197 path.add(name.getRdn(i).getValue().toString());
198 }
199 return path.toString();
200
201 }
202
203 protected LdapName pathToName(String path) {
204 try {
205 LdapName name = (LdapName) getBaseDn().clone();
206 String[] segments = path.split("/");
207 Rdn parentRdn = null;
208 for (String segment : segments) {
209 // TODO make attr names configurable ?
210 String attr = LdapAttrs.ou.name();
211 if (parentRdn != null) {
212 if (getUserBaseRdn().equals(parentRdn))
213 attr = LdapAttrs.uid.name();
214 else if (getGroupBaseRdn().equals(parentRdn))
215 attr = LdapAttrs.cn.name();
216 else if (getSystemRoleBaseRdn().equals(parentRdn))
217 attr = LdapAttrs.cn.name();
218 }
219 Rdn rdn = new Rdn(attr, segment);
220 name.add(rdn);
221 parentRdn = rdn;
222 }
223 return name;
224 } catch (InvalidNameException e) {
225 throw new IllegalStateException("Cannot get role " + path, e);
226 }
227
228 }
229
230 @Override
231 public Role getRoleByPath(String path) {
232 return doGetRole(pathToName(path));
233 }
234
235 @Override
236 public Optional<String> getRealm() {
237 Object realm = getProperties().get(UserAdminConf.realm.name());
238 if (realm == null)
239 return Optional.empty();
240 return Optional.of(realm.toString());
241 }
242
243 /*
244 * EDITION
245 */
246
247 protected boolean isEditing() {
248 return xaResource.wc() != null;
249 }
250
251 protected UserDirectoryWorkingCopy getWorkingCopy() {
252 UserDirectoryWorkingCopy wc = xaResource.wc();
253 if (wc == null)
254 return null;
255 return wc;
256 }
257
258 protected void checkEdit() {
259 if (xaResource.wc() == null) {
260 try {
261 transactionControl.getWorkContext().registerXAResource(xaResource, null);
262 } catch (Exception e) {
263 throw new IllegalStateException("Cannot enlist " + xaResource, e);
264 }
265 } else {
266 }
267 }
268
269 protected List<Role> getAllRoles(DirectoryUser user) {
270 List<Role> allRoles = new ArrayList<Role>();
271 if (user != null) {
272 collectRoles(user, allRoles);
273 allRoles.add(user);
274 } else
275 collectAnonymousRoles(allRoles);
276 return allRoles;
277 }
278
279 private void collectRoles(DirectoryUser user, List<Role> allRoles) {
280 Attributes attrs = user.getAttributes();
281 // TODO centralize attribute name
282 Attribute memberOf = attrs.get(LdapAttrs.memberOf.name());
283 // if user belongs to this directory, we only check meberOf
284 if (memberOf != null && user.getDn().startsWith(getBaseDn())) {
285 try {
286 NamingEnumeration<?> values = memberOf.getAll();
287 while (values.hasMore()) {
288 Object value = values.next();
289 LdapName groupDn = new LdapName(value.toString());
290 DirectoryUser group = doGetRole(groupDn);
291 if (group != null)
292 allRoles.add(group);
293 }
294 } catch (NamingException e) {
295 throw new IllegalStateException("Cannot get memberOf groups for " + user, e);
296 }
297 } else {
298 for (LdapName groupDn : getDirectGroups(user.getDn())) {
299 // TODO check for loops
300 DirectoryUser group = doGetRole(groupDn);
301 if (group != null) {
302 allRoles.add(group);
303 collectRoles(group, allRoles);
304 }
305 }
306 }
307 }
308
309 private void collectAnonymousRoles(List<Role> allRoles) {
310 // TODO gather anonymous roles
311 }
312
313 // USER ADMIN
314 @Override
315 public Role getRole(String name) {
316 return doGetRole(toLdapName(name));
317 }
318
319 protected DirectoryUser doGetRole(LdapName dn) {
320 UserDirectoryWorkingCopy wc = getWorkingCopy();
321 DirectoryUser user;
322 try {
323 user = daoGetRole(dn);
324 } catch (NameNotFoundException e) {
325 user = null;
326 }
327 if (wc != null) {
328 if (user == null && wc.getNewUsers().containsKey(dn))
329 user = wc.getNewUsers().get(dn);
330 else if (wc.getDeletedUsers().containsKey(dn))
331 user = null;
332 }
333 return user;
334 }
335
336 @Override
337 public Role[] getRoles(String filter) throws InvalidSyntaxException {
338 List<? extends Role> res = getRoles(getBaseDn(), filter, true);
339 return res.toArray(new Role[res.size()]);
340 }
341
342 List<DirectoryUser> getRoles(LdapName searchBase, String filter, boolean deep) throws InvalidSyntaxException {
343 UserDirectoryWorkingCopy wc = getWorkingCopy();
344 Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
345 List<DirectoryUser> res = doGetRoles(searchBase, f, deep);
346 if (wc != null) {
347 for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
348 DirectoryUser user = it.next();
349 LdapName dn = user.getDn();
350 if (wc.getDeletedUsers().containsKey(dn))
351 it.remove();
352 }
353 for (DirectoryUser user : wc.getNewUsers().values()) {
354 if (f == null || f.match(user.getProperties()))
355 res.add(user);
356 }
357 // no need to check modified users,
358 // since doGetRoles was already based on the modified attributes
359 }
360
361 // if non deep we also search users and groups
362 // if (!deep) {
363 // try {
364 // if (!(searchBase.endsWith(new LdapName(getUserBase()))
365 // || searchBase.endsWith(new LdapName(getGroupBase())))) {
366 // LdapName usersBase = (LdapName) ((LdapName) searchBase.clone()).add(getUserBase());
367 // res.addAll(getRoles(usersBase, filter, false));
368 // LdapName groupsBase = (LdapName) ((LdapName) searchBase.clone()).add(getGroupBase());
369 // res.addAll(getRoles(groupsBase, filter, false));
370 // }
371 // } catch (InvalidNameException e) {
372 // throw new IllegalStateException("Cannot search users and groups", e);
373 // }
374 // }
375 return res;
376 }
377
378 @Override
379 public User getUser(String key, String value) {
380 // TODO check value null or empty
381 List<DirectoryUser> collectedUsers = new ArrayList<DirectoryUser>();
382 if (key != null) {
383 doGetUser(key, value, collectedUsers);
384 } else {
385 throw new IllegalArgumentException("Key cannot be null");
386 }
387
388 if (collectedUsers.size() == 1) {
389 return collectedUsers.get(0);
390 } else if (collectedUsers.size() > 1) {
391 // log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" :
392 // "") + value);
393 }
394 return null;
395 }
396
397 protected void doGetUser(String key, String value, List<DirectoryUser> collectedUsers) {
398 try {
399 Filter f = FrameworkUtil.createFilter("(" + key + "=" + value + ")");
400 List<DirectoryUser> users = doGetRoles(getBaseDn(), f, true);
401 collectedUsers.addAll(users);
402 } catch (InvalidSyntaxException e) {
403 throw new IllegalArgumentException("Cannot get user with " + key + "=" + value, e);
404 }
405 }
406
407 @Override
408 public Authorization getAuthorization(User user) {
409 if (user == null || user instanceof DirectoryUser) {
410 return new LdifAuthorization(user, getAllRoles((DirectoryUser) user));
411 } else {
412 // bind
413 AbstractUserDirectory scopedUserAdmin = scope(user);
414 try {
415 DirectoryUser directoryUser = (DirectoryUser) scopedUserAdmin.getRole(user.getName());
416 if (directoryUser == null)
417 throw new IllegalStateException("No scoped user found for " + user);
418 LdifAuthorization authorization = new LdifAuthorization(directoryUser,
419 scopedUserAdmin.getAllRoles(directoryUser));
420 return authorization;
421 } finally {
422 scopedUserAdmin.destroy();
423 }
424 }
425 }
426
427 @Override
428 public Role createRole(String name, int type) {
429 checkEdit();
430 UserDirectoryWorkingCopy wc = getWorkingCopy();
431 LdapName dn = toLdapName(name);
432 if ((daoHasRole(dn) && !wc.getDeletedUsers().containsKey(dn)) || wc.getNewUsers().containsKey(dn))
433 throw new IllegalArgumentException("Already a role " + name);
434 BasicAttributes attrs = new BasicAttributes(true);
435 // attrs.put(LdifName.dn.name(), dn.toString());
436 Rdn nameRdn = dn.getRdn(dn.size() - 1);
437 // TODO deal with multiple attr RDN
438 attrs.put(nameRdn.getType(), nameRdn.getValue());
439 if (wc.getDeletedUsers().containsKey(dn)) {
440 wc.getDeletedUsers().remove(dn);
441 wc.getModifiedUsers().put(dn, attrs);
442 return getRole(name);
443 } else {
444 wc.getModifiedUsers().put(dn, attrs);
445 DirectoryUser newRole = newRole(dn, type, attrs);
446 wc.getNewUsers().put(dn, newRole);
447 return newRole;
448 }
449 }
450
451 protected DirectoryUser newRole(LdapName dn, int type, Attributes attrs) {
452 DirectoryUser newRole;
453 BasicAttribute objClass = new BasicAttribute(objectClass.name());
454 if (type == Role.USER) {
455 String userObjClass = newUserObjectClass(dn);
456 objClass.add(userObjClass);
457 if (inetOrgPerson.name().equals(userObjClass)) {
458 objClass.add(organizationalPerson.name());
459 objClass.add(person.name());
460 } else if (organizationalPerson.name().equals(userObjClass)) {
461 objClass.add(person.name());
462 }
463 objClass.add(top.name());
464 objClass.add(extensibleObject.name());
465 attrs.put(objClass);
466 newRole = newUser(dn, attrs);
467 } else if (type == Role.GROUP) {
468 String groupObjClass = getGroupObjectClass();
469 objClass.add(groupObjClass);
470 // objClass.add(LdifName.extensibleObject.name());
471 objClass.add(top.name());
472 attrs.put(objClass);
473 newRole = newGroup(dn, attrs);
474 } else
475 throw new IllegalArgumentException("Unsupported type " + type);
476 return newRole;
477 }
478
479 @Override
480 public boolean removeRole(String name) {
481 checkEdit();
482 UserDirectoryWorkingCopy wc = getWorkingCopy();
483 LdapName dn = toLdapName(name);
484 boolean actuallyDeleted;
485 if (daoHasRole(dn) || wc.getNewUsers().containsKey(dn)) {
486 DirectoryUser user = (DirectoryUser) getRole(name);
487 wc.getDeletedUsers().put(dn, user);
488 actuallyDeleted = true;
489 } else {// just removing from groups (e.g. system roles)
490 actuallyDeleted = false;
491 }
492 for (LdapName groupDn : getDirectGroups(dn)) {
493 DirectoryUser group = doGetRole(groupDn);
494 group.getAttributes().get(getMemberAttributeId()).remove(dn.toString());
495 }
496 return actuallyDeleted;
497 }
498
499 // TRANSACTION
500 protected void prepare(UserDirectoryWorkingCopy wc) {
501
502 }
503
504 protected void commit(UserDirectoryWorkingCopy wc) {
505
506 }
507
508 protected void rollback(UserDirectoryWorkingCopy wc) {
509
510 }
511
512 /*
513 * HIERARCHY
514 */
515 @Override
516 public HierarchyUnit getHierarchyUnit(String path) {
517 LdapName dn = pathToName(path);
518 return doGetHierarchyUnit(dn);
519 }
520
521 @Override
522 public HierarchyUnit getHierarchyUnit(Role role) {
523 LdapName dn = LdapNameUtils.toLdapName(role.getName());
524 LdapName huDn = LdapNameUtils.getParent(dn);
525 HierarchyUnit hierarchyUnit = doGetHierarchyUnit(huDn);
526 if (hierarchyUnit == null)
527 throw new IllegalStateException("No hierarchy unit found for " + role);
528 return hierarchyUnit;
529 }
530
531 @Override
532 public Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly) {
533 return doGetDirectHierarchyUnits(baseDn, functionalOnly);
534 }
535
536 /*
537 * ROLES CREATION
538 */
539 protected DirectoryUser newUser(LdapName name, Attributes attrs) {
540 // TODO support devices, applications, etc.
541 return new LdifUser.LdifPerson(this, name, attrs);
542 }
543
544 protected DirectoryGroup newGroup(LdapName name, Attributes attrs) {
545 if (LdapNameUtils.getParentRdn(name).equals(getSystemRoleBaseRdn()))
546 return new LdifGroup.LdifSystemPermissions(this, name, attrs);
547
548 if (hasObjectClass(attrs, LdapObjs.organization))
549 return new LdifGroup.LdifOrganization(this, name, attrs);
550 else
551 return new LdifGroup.LdifFunctionalGroup(this, name, attrs);
552
553 }
554
555 private boolean hasObjectClass(Attributes attrs, LdapObjs objectClass) {
556 try {
557 Attribute attr = attrs.get(LdapAttrs.objectClass.name());
558 NamingEnumeration<?> en = attr.getAll();
559 while (en.hasMore()) {
560 String v = en.next().toString();
561 if (v.equalsIgnoreCase(objectClass.name()))
562 return true;
563
564 }
565 return false;
566 } catch (NamingException e) {
567 throw new IllegalStateException("Cannot search for objectClass " + objectClass.name(), e);
568 }
569 }
570
571 // GETTERS
572 protected String getMemberAttributeId() {
573 return memberAttributeId;
574 }
575
576 protected List<String> getCredentialAttributeIds() {
577 return credentialAttributeIds;
578 }
579
580 protected String getUri() {
581 return uri;
582 }
583
584 private static boolean readOnlyDefault(String uriStr) {
585 if (uriStr == null)
586 return true;
587 /// TODO make it more generic
588 URI uri;
589 try {
590 uri = new URI(uriStr.split(" ")[0]);
591 } catch (URISyntaxException e) {
592 throw new IllegalArgumentException(e);
593 }
594 if (uri.getScheme() == null)
595 return false;// assume relative file to be writable
596 if (uri.getScheme().equals(UserAdminConf.SCHEME_FILE)) {
597 File file = new File(uri);
598 if (file.exists())
599 return !file.canWrite();
600 else
601 return !file.getParentFile().canWrite();
602 } else if (uri.getScheme().equals(UserAdminConf.SCHEME_LDAP)) {
603 if (uri.getAuthority() != null)// assume writable if authenticated
604 return false;
605 } else if (uri.getScheme().equals(UserAdminConf.SCHEME_OS)) {
606 return true;
607 }
608 return true;// read only by default
609 }
610
611 public boolean isReadOnly() {
612 return readOnly;
613 }
614
615 public boolean isDisabled() {
616 return disabled;
617 }
618
619 protected UserAdmin getExternalRoles() {
620 return externalRoles;
621 }
622
623 protected int roleType(LdapName dn) {
624 Rdn technicalRdn = LdapNameUtils.getParentRdn(dn);
625 if (getGroupBaseRdn().equals(technicalRdn) || getSystemRoleBaseRdn().equals(technicalRdn))
626 return Role.GROUP;
627 else if (userBaseRdn.equals(technicalRdn))
628 return Role.USER;
629 else
630 throw new IllegalArgumentException(
631 "Cannot dind role type, " + technicalRdn + " is not a technical RDN for " + dn);
632 }
633
634 /** dn can be null, in that case a default should be returned. */
635 public String getUserObjectClass() {
636 return userObjectClass;
637 }
638
639 Rdn getUserBaseRdn() {
640 return userBaseRdn;
641 }
642
643 protected String newUserObjectClass(LdapName dn) {
644 return getUserObjectClass();
645 }
646
647 public String getGroupObjectClass() {
648 return groupObjectClass;
649 }
650
651 Rdn getGroupBaseRdn() {
652 return groupBaseRdn;
653 }
654
655 Rdn getSystemRoleBaseRdn() {
656 return systemRoleBaseRdn;
657 }
658
659 LdapName getBaseDn() {
660 return (LdapName) baseDn.clone();
661 }
662
663 public Dictionary<String, Object> getProperties() {
664 return properties;
665 }
666
667 public Dictionary<String, Object> cloneProperties() {
668 return new Hashtable<>(properties);
669 }
670
671 public void setExternalRoles(UserAdmin externalRoles) {
672 this.externalRoles = externalRoles;
673 }
674
675 // public void setTransactionManager(TransactionManager transactionManager) {
676 // this.transactionManager = transactionManager;
677 // }
678
679 public String getForcedPassword() {
680 return forcedPassword;
681 }
682
683 public void setTransactionControl(WorkControl transactionControl) {
684 this.transactionControl = transactionControl;
685 }
686
687 public WcXaResource getXaResource() {
688 return xaResource;
689 }
690
691 public boolean isScoped() {
692 return scoped;
693 }
694
695 @Override
696 public int hashCode() {
697 return baseDn.hashCode();
698 }
699
700 @Override
701 public String toString() {
702 return "User Directory " + baseDn.toString();
703 }
704
705 /*
706 * STATIC UTILITIES
707 */
708 static LdapName toLdapName(String name) {
709 try {
710 return new LdapName(name);
711 } catch (InvalidNameException e) {
712 throw new IllegalArgumentException(name + " is not an LDAP name", e);
713 }
714 }
715 }