1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.ldap;
17
18 import static org.codelibs.core.stream.StreamUtil.stream;
19
20 import java.util.ArrayList;
21 import java.util.Base64;
22 import java.util.Collections;
23 import java.util.HashSet;
24 import java.util.Hashtable;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.function.BiConsumer;
30 import java.util.function.Consumer;
31 import java.util.function.Supplier;
32 import java.util.stream.Collectors;
33 import java.util.stream.Stream;
34
35 import javax.naming.Context;
36 import javax.naming.NamingEnumeration;
37 import javax.naming.NamingException;
38 import javax.naming.directory.Attribute;
39 import javax.naming.directory.Attributes;
40 import javax.naming.directory.BasicAttribute;
41 import javax.naming.directory.BasicAttributes;
42 import javax.naming.directory.DirContext;
43 import javax.naming.directory.InitialDirContext;
44 import javax.naming.directory.ModificationItem;
45 import javax.naming.directory.SearchControls;
46 import javax.naming.directory.SearchResult;
47
48 import org.apache.logging.log4j.LogManager;
49 import org.apache.logging.log4j.Logger;
50 import org.codelibs.core.lang.StringUtil;
51 import org.codelibs.core.timer.TimeoutManager;
52 import org.codelibs.fess.Constants;
53 import org.codelibs.fess.entity.FessUser;
54 import org.codelibs.fess.exception.LdapConfigurationException;
55 import org.codelibs.fess.exception.LdapOperationException;
56 import org.codelibs.fess.helper.SystemHelper;
57 import org.codelibs.fess.mylasta.direction.FessConfig;
58 import org.codelibs.fess.opensearch.user.exentity.Group;
59 import org.codelibs.fess.opensearch.user.exentity.Role;
60 import org.codelibs.fess.opensearch.user.exentity.User;
61 import org.codelibs.fess.util.ComponentUtil;
62 import org.codelibs.fess.util.OptionalUtil;
63 import org.dbflute.optional.OptionalEntity;
64 import org.dbflute.util.DfTypeUtil;
65
66 import jakarta.annotation.PostConstruct;
67
68
69
70
71 public class LdapManager {
72 private static final Logger logger = LogManager.getLogger(LdapManager.class);
73
74
75 protected ThreadLocal<DirContextHolder> contextLocal = new ThreadLocal<>();
76
77
78 protected volatile boolean isBind = false;
79
80
81 protected FessConfig fessConfig;
82
83
84
85
86 public LdapManager() {
87
88 }
89
90
91
92
93 @PostConstruct
94 public void init() {
95 if (logger.isDebugEnabled()) {
96 logger.debug("Initializing {}", this.getClass().getSimpleName());
97 }
98 fessConfig = ComponentUtil.getFessConfig();
99 }
100
101
102
103
104
105
106
107
108
109
110
111 protected Hashtable<String, String> createEnvironment(final String initialContextFactory, final String securityAuthentication,
112 final String providerUrl, final String principal, final String credntials) {
113 final Hashtable<String, String> env = new Hashtable<>();
114 putEnv(env, Context.INITIAL_CONTEXT_FACTORY, initialContextFactory);
115 putEnv(env, Context.SECURITY_AUTHENTICATION, securityAuthentication);
116 putEnv(env, Context.PROVIDER_URL, providerUrl);
117 putEnv(env, Context.SECURITY_PRINCIPAL, principal);
118 putEnv(env, Context.SECURITY_CREDENTIALS, credntials);
119 if (providerUrl != null && providerUrl.startsWith("ldaps://")) {
120 putEnv(env, Context.SECURITY_PROTOCOL, "ssl");
121 }
122 return env;
123 }
124
125
126
127
128
129
130
131
132 protected void putEnv(final Hashtable<String, String> env, final String key, final String value) {
133 if (value == null) {
134 throw new LdapConfigurationException(key + " is null.");
135 }
136 env.put(key, value);
137 }
138
139
140
141
142
143
144 protected Hashtable<String, String> createAdminEnv() {
145 return createEnvironment(
146 fessConfig.getLdapInitialContextFactory(),
147 fessConfig.getLdapSecurityAuthentication(), fessConfig.getLdapProviderUrl(),
148 fessConfig.getLdapAdminSecurityPrincipal(),
149 fessConfig.getLdapAdminSecurityCredentials());
150 }
151
152
153
154
155
156
157
158
159 protected Hashtable<String, String> createSearchEnv(final String username, final String password) {
160 return createEnvironment(
161 fessConfig.getLdapInitialContextFactory(),
162 fessConfig.getLdapSecurityAuthentication(),
163 fessConfig.getLdapProviderUrl(),
164 fessConfig.getLdapSecurityPrincipal(username), password);
165 }
166
167
168
169
170
171
172 protected Hashtable<String, String> createSearchEnv() {
173 return createEnvironment(
174 fessConfig.getLdapInitialContextFactory(),
175 fessConfig.getLdapSecurityAuthentication(), fessConfig.getLdapProviderUrl(),
176 fessConfig.getLdapAdminSecurityPrincipal(),
177 fessConfig.getLdapAdminSecurityCredentials());
178 }
179
180
181
182
183 public void updateConfig() {
184 isBind = false;
185 }
186
187
188
189
190
191
192 protected boolean validate() {
193 if (!isBind) {
194 if (fessConfig.getLdapAdminSecurityPrincipal() == null || fessConfig.getLdapAdminSecurityCredentials() == null) {
195
196 return !fessConfig.isLdapAuthValidation();
197 }
198 try {
199 final Hashtable<String, String> env = createAdminEnv();
200 try (DirContextHolder holder = getDirContext(() -> env)) {
201 final DirContext context = holder.get();
202 if (logger.isDebugEnabled()) {
203 logger.debug("Logged in as Bind DN. {}", context);
204 }
205 isBind = true;
206 }
207 } catch (final LdapConfigurationException e) {
208 logger.warn("LDAP configuration error: {}", e.getMessage(), e);
209 } catch (final LdapOperationException e) {
210 logger.warn("LDAP connection failed: {}", e.getMessage(), e);
211 } catch (final Exception e) {
212 logger.warn("Unexpected error during LDAP validation: {}", e.getMessage(), e);
213 }
214 }
215 return isBind;
216 }
217
218
219
220
221
222
223
224
225 public OptionalEntity<FessUser> login(final String username, final String password) {
226
227 if (StringUtil.isBlank(username) || StringUtil.isBlank(password)) {
228 if (logger.isDebugEnabled()) {
229 logger.debug("Login failed: username or password is blank");
230 }
231 return OptionalEntity.empty();
232 }
233
234 if (StringUtil.isBlank(fessConfig.getLdapProviderUrl()) || !validate()) {
235 return OptionalEntity.empty();
236 }
237
238 try {
239 final Hashtable<String, String> env = createSearchEnv(username, password);
240 try (DirContextHolder holder = getDirContext(() -> env)) {
241 final DirContext context = holder.get();
242 final LdapUser ldapUser = createLdapUser(username, env);
243 if (!allowEmptyGroupAndRole(ldapUser)) {
244 if (logger.isDebugEnabled()) {
245 logger.debug("Login failed. No permissions. {}", context);
246 }
247 return OptionalEntity.empty();
248 }
249 if (logger.isDebugEnabled()) {
250 logger.debug("Logged in. {}", context);
251 }
252 return OptionalEntity.of(ldapUser);
253 }
254 } catch (final LdapOperationException e) {
255 logger.debug("LDAP operation failed during login for user: {}", username, e);
256 } catch (final Exception e) {
257 logger.debug("Login failed for user: {}", username, e);
258 }
259 return OptionalEntity.empty();
260 }
261
262
263
264
265
266
267
268 public OptionalEntity<FessUser> login(final String username) {
269
270 if (StringUtil.isBlank(username)) {
271 if (logger.isDebugEnabled()) {
272 logger.debug("Login failed: username is blank");
273 }
274 return OptionalEntity.empty();
275 }
276
277 try {
278 final Hashtable<String, String> env = createSearchEnv();
279 try (DirContextHolder holder = getDirContext(() -> env)) {
280 final DirContext context = holder.get();
281 final LdapUser ldapUser = createLdapUser(username, env);
282 if (!allowEmptyGroupAndRole(ldapUser)) {
283 if (logger.isDebugEnabled()) {
284 logger.debug("Login failed. No permissions. {}", context);
285 }
286 return OptionalEntity.empty();
287 }
288 if (logger.isDebugEnabled()) {
289 logger.debug("Logged in. {}", context);
290 }
291 return OptionalEntity.of(ldapUser);
292 }
293 } catch (final LdapOperationException e) {
294 logger.debug("LDAP operation failed during login for user: {}", username, e);
295 } catch (final Exception e) {
296 logger.debug("Login failed for user: {}", username, e);
297 }
298 return OptionalEntity.empty();
299 }
300
301
302
303
304
305
306
307 protected boolean allowEmptyGroupAndRole(final LdapUser ldapUser) {
308 if (fessConfig.isLdapAllowEmptyPermission()) {
309 return true;
310 }
311
312 final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
313 for (final String permission : ldapUser.getPermissions()) {
314 if (!systemHelper.isUserPermission(permission)) {
315 return true;
316 }
317 }
318 return false;
319 }
320
321
322
323
324
325
326
327
328 protected LdapUser createLdapUser(final String username, final Hashtable<String, String> env) {
329 return new LdapUser(env, username);
330 }
331
332
333
334
335
336
337
338
339
340
341
342 public String[] getRoles(final LdapUser ldapUser, final String bindDn, final String accountFilter, final String groupFilter,
343 final Consumer<String[]> lazyLoading) {
344 final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
345 final Set<String> roleSet = new HashSet<>();
346
347 if (fessConfig.isLdapRoleSearchUserEnabled()) {
348 roleSet.add(normalizePermissionName(systemHelper.getSearchRoleByUser(ldapUser.getName())));
349 }
350
351
352
353 final String filter = String.format(accountFilter, escapeLDAPSearchFilter(ldapUser.getName()));
354 if (logger.isDebugEnabled()) {
355 logger.debug("Account filter: {}", filter);
356 }
357 final Set<String> subRoleSet = new HashSet<>();
358 final Set<String> sAMAccountGroupNameSet = new HashSet<>();
359 search(bindDn, filter, new String[] { fessConfig.getLdapMemberofAttribute() }, () -> ldapUser.getEnvironment(), result -> {
360 processSearchRoles(result, entryDn -> {
361 final String roleName = getSearchRoleName(entryDn);
362 final String roleType = updateSearchRoles(roleSet, entryDn, roleName);
363 if (fessConfig.getRoleSearchGroupPrefix().equals(roleType) && fessConfig.isLdapSamaccountnameGroup()) {
364 sAMAccountGroupNameSet.add(roleName);
365 }
366 if (StringUtil.isNotBlank(groupFilter)) {
367 subRoleSet.add(entryDn);
368 }
369 });
370 });
371
372 if (logger.isDebugEnabled()) {
373 logger.debug("Roles: {}", roleSet);
374 }
375 final String[] roles = roleSet.toArray(new String[roleSet.size()]);
376
377 if (!subRoleSet.isEmpty()) {
378 TimeoutManager.getInstance().addTimeoutTarget(() -> {
379 sAMAccountGroupNameSet.stream().forEach(groupName -> {
380 getSAMAccountGroupName(bindDn, groupName).ifPresent(sAMAccountGroupName -> {
381 roleSet.add(systemHelper.getSearchRoleByGroup(normalizePermissionName(sAMAccountGroupName)));
382 });
383 });
384 processSubRoles(ldapUser, bindDn, subRoleSet, groupFilter, roleSet);
385 if (logger.isDebugEnabled()) {
386 logger.debug("Roles (lazy loading): {}", roleSet);
387 }
388 lazyLoading.accept(roleSet.toArray(new String[roleSet.size()]));
389 }, 0, false);
390 }
391
392 return roles;
393 }
394
395
396
397
398
399
400
401
402 protected OptionalEntity<String> getSAMAccountGroupName(final String bindDn, final String groupName) {
403
404 if (StringUtil.isBlank(bindDn) || StringUtil.isBlank(groupName)) {
405 if (logger.isDebugEnabled()) {
406 logger.debug("bindDn or groupName is blank: bindDn={}, groupName={}", bindDn, groupName);
407 }
408 return OptionalEntity.empty();
409 }
410
411 final Hashtable<String, String> env = createSearchEnv();
412 try (DirContextHolder holder = getDirContext(() -> env)) {
413 final DirContext context = holder.get();
414 final SearchControls searchControls = new SearchControls();
415 searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
416 if (logger.isDebugEnabled()) {
417 logger.debug("Searching for sAMAccountName of group: {} on {}", groupName, bindDn);
418 }
419 final NamingEnumeration<SearchResult> results =
420 context.search(bindDn, "(name=" + escapeLDAPSearchFilter(groupName) + ")", searchControls);
421 if (results.hasMore()) {
422 final SearchResult searchResult = results.next();
423 final Attribute attribute = searchResult.getAttributes().get("sAMAccountName");
424 if (logger.isDebugEnabled()) {
425 logger.debug("sAMAccountName: {}", attribute);
426 }
427 if (attribute != null && attribute.get() instanceof String sAMAccountName) {
428 return OptionalEntity.of(sAMAccountName);
429 }
430 }
431 } catch (final NamingException e) {
432 logger.warn("LDAP naming exception while getting sAMAccountName for group: {}", groupName, e);
433 } catch (final Exception e) {
434 logger.warn("Unexpected exception while getting sAMAccountName for group: {}", groupName, e);
435 }
436 return OptionalEntity.empty();
437 }
438
439
440
441
442
443
444
445
446
447
448 protected void processSubRoles(final LdapUser ldapUser, final String bindDn, final Set<String> subRoleSet, final String groupFilter,
449 final Set<String> roleSet) {
450
451 if (subRoleSet.isEmpty()) {
452 return;
453 }
454 String filter = subRoleSet.stream().map(s -> String.format(groupFilter, s)).collect(Collectors.joining());
455 if (subRoleSet.size() > 1) {
456 filter = "(|" + filter + ")";
457 }
458
459 if (logger.isDebugEnabled()) {
460 logger.debug("Group filter: {}", filter);
461 }
462 final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
463 search(bindDn, filter, null, () -> ldapUser.getEnvironment(), result -> {
464 for (final SearchResult srcrslt : result) {
465 final String groupDn = srcrslt.getNameInNamespace();
466 if (logger.isDebugEnabled()) {
467 logger.debug("Group DN: {}", groupDn);
468 }
469 final String groupName = getSearchRoleName(groupDn);
470 final String roleType = updateSearchRoles(roleSet, groupDn, groupName);
471 if (fessConfig.getRoleSearchGroupPrefix().equals(roleType) && fessConfig.isLdapSamaccountnameGroup()) {
472 getSAMAccountGroupName(bindDn, groupName).ifPresent(sAMAccountGroupName -> {
473 roleSet.add(systemHelper.getSearchRoleByGroup(normalizePermissionName(sAMAccountGroupName)));
474 });
475 }
476 }
477 });
478 }
479
480
481
482
483
484
485
486
487
488 protected String updateSearchRoles(final Set<String> roleSet, final String entryDn, final String name) {
489 if (StringUtil.isNotBlank(name)) {
490 final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
491 final boolean isRole = entryDn.toLowerCase(Locale.ROOT).indexOf("ou=role") != -1;
492 if (isRole) {
493 if (fessConfig.isLdapRoleSearchRoleEnabled()) {
494 roleSet.add(systemHelper.getSearchRoleByRole(normalizePermissionName(name)));
495 return fessConfig.getRoleSearchRolePrefix();
496 }
497 } else if (fessConfig.isLdapRoleSearchGroupEnabled()) {
498 roleSet.add(systemHelper.getSearchRoleByGroup(normalizePermissionName(name)));
499 return fessConfig.getRoleSearchGroupPrefix();
500 }
501 }
502 return null;
503 }
504
505
506
507
508
509
510
511
512
513 @Deprecated
514 protected String escapeLDAPSearchFilter(final String filter) {
515 return LdapUtil.escapeValue(filter);
516 }
517
518
519
520
521
522
523
524 public String normalizePermissionName(final String name) {
525 if (fessConfig.isLdapLowercasePermissionName()) {
526 return name.toLowerCase(Locale.ROOT);
527 }
528 return name;
529 }
530
531
532
533
534
535
536
537
538 protected void processSearchRoles(final List<SearchResult> result, final BiConsumer<String, String> consumer) throws NamingException {
539 processSearchRoles(result, entryDn -> {
540 final String name = getSearchRoleName(entryDn);
541 if (name != null) {
542 consumer.accept(entryDn, name);
543 }
544 });
545 }
546
547
548
549
550
551
552
553
554 protected void processSearchRoles(final List<SearchResult> result, final Consumer<String> consumer) throws NamingException {
555 for (final SearchResult srcrslt : result) {
556 final Attributes attrs = srcrslt.getAttributes();
557
558
559 final Attribute attr = attrs.get(fessConfig.getLdapMemberofAttribute());
560 if (attr == null) {
561 continue;
562 }
563
564 for (int i = 0; i < attr.size(); i++) {
565 final Object attrValue = attr.get(i);
566 if (attrValue != null) {
567 final String entryDn = attrValue.toString();
568
569 if (logger.isDebugEnabled()) {
570 logger.debug("entryDn: {}", entryDn);
571 }
572 consumer.accept(entryDn);
573 }
574 }
575 }
576 }
577
578
579
580
581
582
583
584 protected String getSearchRoleName(final String entryDn) {
585 if (entryDn == null) {
586 return null;
587 }
588 int start = entryDn.toLowerCase(Locale.ROOT).indexOf("cn=");
589 if (start == -1) {
590 return null;
591 }
592 start += 3;
593
594 final int end = entryDn.indexOf(',', start);
595 final String value = end == -1 ? entryDn.substring(start) : entryDn.substring(start, end);
596 if (fessConfig.isLdapGroupNameWithUnderscores()) {
597 return replaceWithUnderscores(value);
598 }
599 return value;
600 }
601
602
603
604
605
606
607
608 protected String replaceWithUnderscores(final String value) {
609 return value.replaceAll("[/\\\\\\[\\]:;|=,+\\*?<>]", "_");
610 }
611
612
613
614
615
616
617
618
619 protected void setAttributeValue(final List<SearchResult> result, final String name, final Consumer<Object> consumer) {
620 final List<Object> attrList = getAttributeValueList(result, name);
621 if (!attrList.isEmpty()) {
622 consumer.accept(attrList.get(0));
623 }
624 }
625
626
627
628
629
630
631
632
633 protected List<Object> getAttributeValueList(final List<SearchResult> result, final String name) {
634 try {
635 for (final SearchResult srcrslt : result) {
636 final Attributes attrs = srcrslt.getAttributes();
637
638 final Attribute attr = attrs.get(name);
639 if (attr == null) {
640 continue;
641 }
642
643 final List<Object> attrList = new ArrayList<>();
644 for (int i = 0; i < attr.size(); i++) {
645 final Object attrValue = attr.get(i);
646 if (attrValue != null) {
647 attrList.add(attrValue);
648 }
649 }
650 return attrList;
651 }
652 return Collections.emptyList();
653 } catch (final NamingException e) {
654 throw new LdapOperationException("Failed to parse attribute values for " + name, e);
655 }
656 }
657
658
659
660
661
662
663 public void apply(final User user) {
664 if (!fessConfig.isLdapAdminEnabled(user.getName())) {
665 return;
666 }
667
668 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
669 search(fessConfig.getLdapAdminUserBaseDn(), fessConfig.getLdapAdminUserFilter(user.getName()), null, adminEnv, result -> {
670 if (!result.isEmpty()) {
671 setAttributeValue(result, fessConfig.getLdapAttrSurname(), o -> user.setSurname(o.toString()));
672 setAttributeValue(result, fessConfig.getLdapAttrGivenName(), o -> user.setGivenName(o.toString()));
673 setAttributeValue(result, fessConfig.getLdapAttrMail(), o -> user.setMail(o.toString()));
674 setAttributeValue(result, fessConfig.getLdapAttrEmployeeNumber(), o -> user.setEmployeeNumber(o.toString()));
675 setAttributeValue(result, fessConfig.getLdapAttrTelephoneNumber(), o -> user.setTelephoneNumber(o.toString()));
676 setAttributeValue(result, fessConfig.getLdapAttrHomePhone(), o -> user.setHomePhone(o.toString()));
677 setAttributeValue(result, fessConfig.getLdapAttrHomePostalAddress(), o -> user.setHomePostalAddress(o.toString()));
678 setAttributeValue(result, fessConfig.getLdapAttrLabeledURI(), o -> user.setLabeledURI(o.toString()));
679 setAttributeValue(result, fessConfig.getLdapAttrRoomNumber(), o -> user.setRoomNumber(o.toString()));
680 setAttributeValue(result, fessConfig.getLdapAttrDescription(), o -> user.setDescription(o.toString()));
681 setAttributeValue(result, fessConfig.getLdapAttrTitle(), o -> user.setTitle(o.toString()));
682 setAttributeValue(result, fessConfig.getLdapAttrPager(), o -> user.setPager(o.toString()));
683 setAttributeValue(result, fessConfig.getLdapAttrStreet(), o -> user.setStreet(o.toString()));
684 setAttributeValue(result, fessConfig.getLdapAttrPostalCode(), o -> user.setPostalCode(o.toString()));
685 setAttributeValue(result, fessConfig.getLdapAttrPhysicalDeliveryOfficeName(),
686 o -> user.setPhysicalDeliveryOfficeName(o.toString()));
687 setAttributeValue(result, fessConfig.getLdapAttrDestinationIndicator(), o -> user.setDestinationIndicator(o.toString()));
688 setAttributeValue(result, fessConfig.getLdapAttrInternationaliSDNNumber(),
689 o -> user.setInternationaliSDNNumber(o.toString()));
690 setAttributeValue(result, fessConfig.getLdapAttrState(), o -> user.setState(o.toString()));
691 setAttributeValue(result, fessConfig.getLdapAttrEmployeeType(), o -> user.setEmployeeType(o.toString()));
692 setAttributeValue(result, fessConfig.getLdapAttrFacsimileTelephoneNumber(),
693 o -> user.setFacsimileTelephoneNumber(o.toString()));
694 setAttributeValue(result, fessConfig.getLdapAttrPostOfficeBox(), o -> user.setPostOfficeBox(o.toString()));
695 setAttributeValue(result, fessConfig.getLdapAttrInitials(), o -> user.setInitials(o.toString()));
696 setAttributeValue(result, fessConfig.getLdapAttrCarLicense(), o -> user.setCarLicense(o.toString()));
697 setAttributeValue(result, fessConfig.getLdapAttrMobile(), o -> user.setMobile(o.toString()));
698 setAttributeValue(result, fessConfig.getLdapAttrPostalAddress(), o -> user.setPostalAddress(o.toString()));
699 setAttributeValue(result, fessConfig.getLdapAttrCity(), o -> user.setCity(o.toString()));
700 setAttributeValue(result, fessConfig.getLdapAttrTeletexTerminalIdentifier(),
701 o -> user.setTeletexTerminalIdentifier(o.toString()));
702 setAttributeValue(result, fessConfig.getLdapAttrX121Address(), o -> user.setX121Address(o.toString()));
703 setAttributeValue(result, fessConfig.getLdapAttrBusinessCategory(), o -> user.setBusinessCategory(o.toString()));
704 setAttributeValue(result, fessConfig.getLdapAttrRegisteredAddress(), o -> user.setRegisteredAddress(o.toString()));
705 setAttributeValue(result, fessConfig.getLdapAttrDisplayName(), o -> user.setDisplayName(o.toString()));
706 setAttributeValue(result, fessConfig.getLdapAttrPreferredLanguage(), o -> user.setPreferredLanguage(o.toString()));
707 setAttributeValue(result, fessConfig.getLdapAttrDepartmentNumber(), o -> user.setDepartmentNumber(o.toString()));
708 setAttributeValue(result, fessConfig.getLdapAttrUidNumber(), o -> user.setUidNumber(DfTypeUtil.toLong(o)));
709 setAttributeValue(result, fessConfig.getLdapAttrGidNumber(), o -> user.setGidNumber(DfTypeUtil.toLong(o)));
710 setAttributeValue(result, fessConfig.getLdapAttrHomeDirectory(), o -> user.setHomeDirectory(o.toString()));
711 }
712 });
713
714
715 search(fessConfig.getLdapAdminUserBaseDn(), fessConfig.getLdapAdminUserFilter(user.getName()),
716 new String[] { fessConfig.getLdapMemberofAttribute() }, adminEnv, result -> {
717 if (!result.isEmpty()) {
718 final List<String> groupList = new ArrayList<>();
719 final List<String> roleList = new ArrayList<>();
720 final String lowerGroupDn = fessConfig.getLdapAdminGroupBaseDn().toLowerCase(Locale.ROOT);
721 final String lowerRoleDn = fessConfig.getLdapAdminRoleBaseDn().toLowerCase(Locale.ROOT);
722 processSearchRoles(result, (entryDn, name) -> {
723 final String lowerEntryDn = entryDn.toLowerCase(Locale.ROOT);
724 if (lowerEntryDn.indexOf(lowerGroupDn) != -1) {
725 groupList.add(Base64.getUrlEncoder().encodeToString(name.getBytes(Constants.CHARSET_UTF_8)));
726 } else if (lowerEntryDn.indexOf(lowerRoleDn) != -1) {
727 roleList.add(Base64.getUrlEncoder().encodeToString(name.getBytes(Constants.CHARSET_UTF_8)));
728 }
729 });
730 user.setGroups(groupList.toArray(new String[groupList.size()]));
731 user.setRoles(roleList.toArray(new String[roleList.size()]));
732 }
733 });
734
735 }
736
737
738
739
740
741
742 public void insert(final User user) {
743 if (!fessConfig.isLdapAdminEnabled(user.getName())) {
744 return;
745 }
746
747 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
748 final String userDN = fessConfig.getLdapAdminUserSecurityPrincipal(user.getName());
749
750 search(fessConfig.getLdapAdminUserBaseDn(), fessConfig.getLdapAdminUserFilter(user.getName()), null, adminEnv, result -> {
751 if (!result.isEmpty()) {
752 modifyUserAttributes(user, adminEnv, userDN, result);
753 } else {
754 final BasicAttributes entry = new BasicAttributes();
755 addUserAttributes(entry, user);
756 final Attribute oc = fessConfig.getLdapAdminUserObjectClassAttribute();
757 entry.put(oc);
758 insert(userDN, entry, adminEnv);
759 }
760 });
761
762
763 search(fessConfig.getLdapAdminUserBaseDn(), fessConfig.getLdapAdminUserFilter(user.getName()),
764 new String[] { fessConfig.getLdapMemberofAttribute() }, adminEnv, result -> {
765 if (!result.isEmpty()) {
766 final List<String> oldGroupList = new ArrayList<>();
767 final List<String> oldRoleList = new ArrayList<>();
768 final String lowerGroupDn = fessConfig.getLdapAdminGroupBaseDn().toLowerCase(Locale.ROOT);
769 final String lowerRoleDn = fessConfig.getLdapAdminRoleBaseDn().toLowerCase(Locale.ROOT);
770 processSearchRoles(result, (entryDn, name) -> {
771 final String lowerEntryDn = entryDn.toLowerCase(Locale.ROOT);
772 if (lowerEntryDn.indexOf(lowerGroupDn) != -1) {
773 oldGroupList.add(name);
774 } else if (lowerEntryDn.indexOf(lowerRoleDn) != -1) {
775 oldRoleList.add(name);
776 }
777 });
778 final List<String> newGroupList = stream(user.getGroupNames()).get(stream -> stream.collect(Collectors.toList()));
779 stream(user.getGroupNames()).of(stream -> stream.forEach(name -> {
780 if (oldGroupList.contains(name)) {
781 oldGroupList.remove(name);
782 newGroupList.remove(name);
783 }
784 }));
785 oldGroupList.stream().forEach(name -> {
786 search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(name), null, adminEnv,
787 subResult -> {
788 if (!subResult.isEmpty()) {
789 final List<ModificationItem> modifyList = new ArrayList<>();
790 modifyDeleteEntry(modifyList, "member", userDN);
791 modify(fessConfig.getLdapAdminGroupSecurityPrincipal(name), modifyList, adminEnv);
792 }
793 });
794 });
795 newGroupList.stream().forEach(name -> {
796 search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(name), null, adminEnv,
797 subResult -> {
798 if (subResult.isEmpty()) {
799 final Group group = new Group();
800 group.setName(name);
801 insert(group);
802 }
803 final List<ModificationItem> modifyList = new ArrayList<>();
804 modifyAddEntry(modifyList, "member", userDN);
805 modify(fessConfig.getLdapAdminGroupSecurityPrincipal(name), modifyList, adminEnv);
806 });
807 });
808
809 final List<String> newRoleList = stream(user.getRoleNames()).get(stream -> stream.collect(Collectors.toList()));
810 stream(user.getRoleNames()).of(stream -> stream.forEach(name -> {
811 if (oldRoleList.contains(name)) {
812 oldRoleList.remove(name);
813 newRoleList.remove(name);
814 }
815 }));
816 oldRoleList.stream().forEach(name -> {
817 search(fessConfig.getLdapAdminRoleBaseDn(), fessConfig.getLdapAdminRoleFilter(name), null, adminEnv,
818 subResult -> {
819 if (!subResult.isEmpty()) {
820 final List<ModificationItem> modifyList = new ArrayList<>();
821 modifyDeleteEntry(modifyList, "member", userDN);
822 modify(fessConfig.getLdapAdminRoleSecurityPrincipal(name), modifyList, adminEnv);
823 }
824 });
825 });
826 newRoleList.stream().forEach(name -> {
827 search(fessConfig.getLdapAdminRoleBaseDn(), fessConfig.getLdapAdminRoleFilter(name), null, adminEnv,
828 subResult -> {
829 if (subResult.isEmpty()) {
830 final Role role = new Role();
831 role.setName(name);
832 insert(role);
833 }
834 final List<ModificationItem> modifyList = new ArrayList<>();
835 modifyAddEntry(modifyList, "member", userDN);
836 modify(fessConfig.getLdapAdminRoleSecurityPrincipal(name), modifyList, adminEnv);
837 });
838 });
839 } else {
840 stream(user.getGroupNames()).of(stream -> stream.forEach(name -> {
841 search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(name), null, adminEnv,
842 subResult -> {
843 if (subResult.isEmpty()) {
844 final Group group = new Group();
845 group.setName(name);
846 insert(group);
847 }
848 final List<ModificationItem> modifyList = new ArrayList<>();
849 modifyAddEntry(modifyList, "member", userDN);
850 modify(fessConfig.getLdapAdminGroupSecurityPrincipal(name), modifyList, adminEnv);
851 });
852 }));
853
854 stream(user.getRoleNames()).of(stream -> stream.forEach(name -> {
855 search(fessConfig.getLdapAdminRoleBaseDn(), fessConfig.getLdapAdminRoleFilter(name), null, adminEnv,
856 subResult -> {
857 if (subResult.isEmpty()) {
858 final Role role = new Role();
859 role.setName(name);
860 insert(role);
861 }
862 final List<ModificationItem> modifyList = new ArrayList<>();
863 modifyAddEntry(modifyList, "member", userDN);
864 modify(fessConfig.getLdapAdminRoleSecurityPrincipal(name), modifyList, adminEnv);
865 });
866 }));
867 }
868 });
869
870 }
871
872
873
874
875
876
877
878
879
880 protected void modifyUserAttributes(final User user, final Supplier<Hashtable<String, String>> adminEnv, final String userDN,
881 final List<SearchResult> result) {
882 final List<ModificationItem> modifyList = new ArrayList<>();
883 if (user.getOriginalPassword() != null) {
884 modifyReplaceEntry(modifyList, "userPassword", user.getOriginalPassword());
885 }
886
887 final String attrSurname = fessConfig.getLdapAttrSurname();
888 OptionalUtil.ofNullable(user.getSurname())
889 .filter(StringUtil::isNotBlank)
890 .ifPresent(s -> modifyReplaceEntry(modifyList, attrSurname, s))
891 .orElse(() -> getAttributeValueList(result, attrSurname).stream()
892 .forEach(v -> modifyDeleteEntry(modifyList, attrSurname, v)));
893 final String attrGivenName = fessConfig.getLdapAttrGivenName();
894 OptionalUtil.ofNullable(user.getGivenName())
895 .filter(StringUtil::isNotBlank)
896 .ifPresent(s -> modifyReplaceEntry(modifyList, attrGivenName, s))
897 .orElse(() -> getAttributeValueList(result, attrGivenName).stream()
898 .forEach(v -> modifyDeleteEntry(modifyList, attrGivenName, v)));
899 final String attrMail = fessConfig.getLdapAttrMail();
900 OptionalUtil.ofNullable(user.getMail())
901 .filter(StringUtil::isNotBlank)
902 .ifPresent(s -> modifyReplaceEntry(modifyList, attrMail, s))
903 .orElse(() -> getAttributeValueList(result, attrMail).stream().forEach(v -> modifyDeleteEntry(modifyList, attrMail, v)));
904 final String attrEmployeeNumber = fessConfig.getLdapAttrEmployeeNumber();
905 OptionalUtil.ofNullable(user.getEmployeeNumber())
906 .filter(StringUtil::isNotBlank)
907 .ifPresent(s -> modifyReplaceEntry(modifyList, attrEmployeeNumber, s))
908 .orElse(() -> getAttributeValueList(result, attrEmployeeNumber).stream()
909 .forEach(v -> modifyDeleteEntry(modifyList, attrEmployeeNumber, v)));
910 final String attrTelephoneNumber = fessConfig.getLdapAttrTelephoneNumber();
911 OptionalUtil.ofNullable(user.getTelephoneNumber())
912 .filter(StringUtil::isNotBlank)
913 .ifPresent(s -> modifyReplaceEntry(modifyList, attrTelephoneNumber, s))
914 .orElse(() -> getAttributeValueList(result, attrTelephoneNumber).stream()
915 .forEach(v -> modifyDeleteEntry(modifyList, attrTelephoneNumber, v)));
916 final String attrHomePhone = fessConfig.getLdapAttrHomePhone();
917 OptionalUtil.ofNullable(user.getHomePhone())
918 .filter(StringUtil::isNotBlank)
919 .ifPresent(s -> modifyReplaceEntry(modifyList, attrHomePhone, s))
920 .orElse(() -> getAttributeValueList(result, attrHomePhone).stream()
921 .forEach(v -> modifyDeleteEntry(modifyList, attrHomePhone, v)));
922 final String attrHomePostalAddress = fessConfig.getLdapAttrHomePostalAddress();
923 OptionalUtil.ofNullable(user.getHomePostalAddress())
924 .filter(StringUtil::isNotBlank)
925 .ifPresent(s -> modifyReplaceEntry(modifyList, attrHomePostalAddress, s))
926 .orElse(() -> getAttributeValueList(result, attrHomePostalAddress).stream()
927 .forEach(v -> modifyDeleteEntry(modifyList, attrHomePostalAddress, v)));
928 final String attrLabeledURI = fessConfig.getLdapAttrLabeledURI();
929 OptionalUtil.ofNullable(user.getLabeledURI())
930 .filter(StringUtil::isNotBlank)
931 .ifPresent(s -> modifyReplaceEntry(modifyList, attrLabeledURI, s))
932 .orElse(() -> getAttributeValueList(result, attrLabeledURI).stream()
933 .forEach(v -> modifyDeleteEntry(modifyList, attrLabeledURI, v)));
934 final String attrRoomNumber = fessConfig.getLdapAttrRoomNumber();
935 OptionalUtil.ofNullable(user.getRoomNumber())
936 .filter(StringUtil::isNotBlank)
937 .ifPresent(s -> modifyReplaceEntry(modifyList, attrRoomNumber, s))
938 .orElse(() -> getAttributeValueList(result, attrRoomNumber).stream()
939 .forEach(v -> modifyDeleteEntry(modifyList, attrRoomNumber, v)));
940 final String attrDescription = fessConfig.getLdapAttrDescription();
941 OptionalUtil.ofNullable(user.getDescription())
942 .filter(StringUtil::isNotBlank)
943 .ifPresent(s -> modifyReplaceEntry(modifyList, attrDescription, s))
944 .orElse(() -> getAttributeValueList(result, attrDescription).stream()
945 .forEach(v -> modifyDeleteEntry(modifyList, attrDescription, v)));
946 final String attrTitle = fessConfig.getLdapAttrTitle();
947 OptionalUtil.ofNullable(user.getTitle())
948 .filter(StringUtil::isNotBlank)
949 .ifPresent(s -> modifyReplaceEntry(modifyList, attrTitle, s))
950 .orElse(() -> getAttributeValueList(result, attrTitle).stream().forEach(v -> modifyDeleteEntry(modifyList, attrTitle, v)));
951 final String attrPager = fessConfig.getLdapAttrPager();
952 OptionalUtil.ofNullable(user.getPager())
953 .filter(StringUtil::isNotBlank)
954 .ifPresent(s -> modifyReplaceEntry(modifyList, attrPager, s))
955 .orElse(() -> getAttributeValueList(result, attrPager).stream().forEach(v -> modifyDeleteEntry(modifyList, attrPager, v)));
956 final String attrStreet = fessConfig.getLdapAttrStreet();
957 OptionalUtil.ofNullable(user.getStreet())
958 .filter(StringUtil::isNotBlank)
959 .ifPresent(s -> modifyReplaceEntry(modifyList, attrStreet, s))
960 .orElse(() -> getAttributeValueList(result, attrStreet).stream()
961 .forEach(v -> modifyDeleteEntry(modifyList, attrStreet, v)));
962 final String attrPostalCode = fessConfig.getLdapAttrPostalCode();
963 OptionalUtil.ofNullable(user.getPostalCode())
964 .filter(StringUtil::isNotBlank)
965 .ifPresent(s -> modifyReplaceEntry(modifyList, attrPostalCode, s))
966 .orElse(() -> getAttributeValueList(result, attrPostalCode).stream()
967 .forEach(v -> modifyDeleteEntry(modifyList, attrPostalCode, v)));
968 final String attrPhysicalDeliveryOfficeName = fessConfig.getLdapAttrPhysicalDeliveryOfficeName();
969 OptionalUtil.ofNullable(user.getPhysicalDeliveryOfficeName())
970 .filter(StringUtil::isNotBlank)
971 .ifPresent(s -> modifyReplaceEntry(modifyList, attrPhysicalDeliveryOfficeName, s))
972 .orElse(() -> getAttributeValueList(result, attrPhysicalDeliveryOfficeName).stream()
973 .forEach(v -> modifyDeleteEntry(modifyList, attrPhysicalDeliveryOfficeName, v)));
974 final String attrDestinationIndicator = fessConfig.getLdapAttrDestinationIndicator();
975 OptionalUtil.ofNullable(user.getDestinationIndicator())
976 .filter(StringUtil::isNotBlank)
977 .ifPresent(s -> modifyReplaceEntry(modifyList, attrDestinationIndicator, s))
978 .orElse(() -> getAttributeValueList(result, attrDestinationIndicator).stream()
979 .forEach(v -> modifyDeleteEntry(modifyList, attrDestinationIndicator, v)));
980 final String attrInternationaliSDNNumber = fessConfig.getLdapAttrInternationaliSDNNumber();
981 OptionalUtil.ofNullable(user.getInternationaliSDNNumber())
982 .filter(StringUtil::isNotBlank)
983 .ifPresent(s -> modifyReplaceEntry(modifyList, attrInternationaliSDNNumber, s))
984 .orElse(() -> getAttributeValueList(result, attrInternationaliSDNNumber).stream()
985 .forEach(v -> modifyDeleteEntry(modifyList, attrInternationaliSDNNumber, v)));
986 final String attrState = fessConfig.getLdapAttrState();
987 OptionalUtil.ofNullable(user.getState())
988 .filter(StringUtil::isNotBlank)
989 .ifPresent(s -> modifyReplaceEntry(modifyList, attrState, s))
990 .orElse(() -> getAttributeValueList(result, attrState).stream().forEach(v -> modifyDeleteEntry(modifyList, attrState, v)));
991 final String attrEmployeeType = fessConfig.getLdapAttrEmployeeType();
992 OptionalUtil.ofNullable(user.getEmployeeType())
993 .filter(StringUtil::isNotBlank)
994 .ifPresent(s -> modifyReplaceEntry(modifyList, attrEmployeeType, s))
995 .orElse(() -> getAttributeValueList(result, attrEmployeeType).stream()
996 .forEach(v -> modifyDeleteEntry(modifyList, attrEmployeeType, v)));
997 final String attrFacsimileTelephoneNumber = fessConfig.getLdapAttrFacsimileTelephoneNumber();
998 OptionalUtil.ofNullable(user.getFacsimileTelephoneNumber())
999 .filter(StringUtil::isNotBlank)
1000 .ifPresent(s -> modifyReplaceEntry(modifyList, attrFacsimileTelephoneNumber, s))
1001 .orElse(() -> getAttributeValueList(result, attrFacsimileTelephoneNumber).stream()
1002 .forEach(v -> modifyDeleteEntry(modifyList, attrFacsimileTelephoneNumber, v)));
1003 final String attrPostOfficeBox = fessConfig.getLdapAttrPostOfficeBox();
1004 OptionalUtil.ofNullable(user.getPostOfficeBox())
1005 .filter(StringUtil::isNotBlank)
1006 .ifPresent(s -> modifyReplaceEntry(modifyList, attrPostOfficeBox, s))
1007 .orElse(() -> getAttributeValueList(result, attrPostOfficeBox).stream()
1008 .forEach(v -> modifyDeleteEntry(modifyList, attrPostOfficeBox, v)));
1009 final String attrInitials = fessConfig.getLdapAttrInitials();
1010 OptionalUtil.ofNullable(user.getInitials())
1011 .filter(StringUtil::isNotBlank)
1012 .ifPresent(s -> modifyReplaceEntry(modifyList, attrInitials, s))
1013 .orElse(() -> getAttributeValueList(result, attrInitials).stream()
1014 .forEach(v -> modifyDeleteEntry(modifyList, attrInitials, v)));
1015 final String attrCarLicense = fessConfig.getLdapAttrCarLicense();
1016 OptionalUtil.ofNullable(user.getCarLicense())
1017 .filter(StringUtil::isNotBlank)
1018 .ifPresent(s -> modifyReplaceEntry(modifyList, attrCarLicense, s))
1019 .orElse(() -> getAttributeValueList(result, attrCarLicense).stream()
1020 .forEach(v -> modifyDeleteEntry(modifyList, attrCarLicense, v)));
1021 final String attrMobile = fessConfig.getLdapAttrMobile();
1022 OptionalUtil.ofNullable(user.getMobile())
1023 .filter(StringUtil::isNotBlank)
1024 .ifPresent(s -> modifyReplaceEntry(modifyList, attrMobile, s))
1025 .orElse(() -> getAttributeValueList(result, attrMobile).stream()
1026 .forEach(v -> modifyDeleteEntry(modifyList, attrMobile, v)));
1027 final String attrPostalAddress = fessConfig.getLdapAttrPostalAddress();
1028 OptionalUtil.ofNullable(user.getPostalAddress())
1029 .filter(StringUtil::isNotBlank)
1030 .ifPresent(s -> modifyReplaceEntry(modifyList, attrPostalAddress, s))
1031 .orElse(() -> getAttributeValueList(result, attrPostalAddress).stream()
1032 .forEach(v -> modifyDeleteEntry(modifyList, attrPostalAddress, v)));
1033 final String attrCity = fessConfig.getLdapAttrCity();
1034 OptionalUtil.ofNullable(user.getCity())
1035 .filter(StringUtil::isNotBlank)
1036 .ifPresent(s -> modifyReplaceEntry(modifyList, attrCity, s))
1037 .orElse(() -> getAttributeValueList(result, attrCity).stream().forEach(v -> modifyDeleteEntry(modifyList, attrCity, v)));
1038 final String attrTeletexTerminalIdentifier = fessConfig.getLdapAttrTeletexTerminalIdentifier();
1039 OptionalUtil.ofNullable(user.getTeletexTerminalIdentifier())
1040 .filter(StringUtil::isNotBlank)
1041 .ifPresent(s -> modifyReplaceEntry(modifyList, attrTeletexTerminalIdentifier, s))
1042 .orElse(() -> getAttributeValueList(result, attrTeletexTerminalIdentifier).stream()
1043 .forEach(v -> modifyDeleteEntry(modifyList, attrTeletexTerminalIdentifier, v)));
1044 final String attrX121Address = fessConfig.getLdapAttrX121Address();
1045 OptionalUtil.ofNullable(user.getX121Address())
1046 .filter(StringUtil::isNotBlank)
1047 .ifPresent(s -> modifyReplaceEntry(modifyList, attrX121Address, s))
1048 .orElse(() -> getAttributeValueList(result, attrX121Address).stream()
1049 .forEach(v -> modifyDeleteEntry(modifyList, attrX121Address, v)));
1050 final String attrBusinessCategory = fessConfig.getLdapAttrBusinessCategory();
1051 OptionalUtil.ofNullable(user.getBusinessCategory())
1052 .filter(StringUtil::isNotBlank)
1053 .ifPresent(s -> modifyReplaceEntry(modifyList, attrBusinessCategory, s))
1054 .orElse(() -> getAttributeValueList(result, attrBusinessCategory).stream()
1055 .forEach(v -> modifyDeleteEntry(modifyList, attrBusinessCategory, v)));
1056 final String attrRegisteredAddress = fessConfig.getLdapAttrRegisteredAddress();
1057 OptionalUtil.ofNullable(user.getRegisteredAddress())
1058 .filter(StringUtil::isNotBlank)
1059 .ifPresent(s -> modifyReplaceEntry(modifyList, attrRegisteredAddress, s))
1060 .orElse(() -> getAttributeValueList(result, attrRegisteredAddress).stream()
1061 .forEach(v -> modifyDeleteEntry(modifyList, attrRegisteredAddress, v)));
1062 final String attrDisplayName = fessConfig.getLdapAttrDisplayName();
1063 OptionalUtil.ofNullable(user.getDisplayName())
1064 .filter(StringUtil::isNotBlank)
1065 .ifPresent(s -> modifyReplaceEntry(modifyList, attrDisplayName, s))
1066 .orElse(() -> getAttributeValueList(result, attrDisplayName).stream()
1067 .forEach(v -> modifyDeleteEntry(modifyList, attrDisplayName, v)));
1068 final String attrPreferredLanguage = fessConfig.getLdapAttrPreferredLanguage();
1069 OptionalUtil.ofNullable(user.getPreferredLanguage())
1070 .filter(StringUtil::isNotBlank)
1071 .ifPresent(s -> modifyReplaceEntry(modifyList, attrPreferredLanguage, s))
1072 .orElse(() -> getAttributeValueList(result, attrPreferredLanguage).stream()
1073 .forEach(v -> modifyDeleteEntry(modifyList, attrPreferredLanguage, v)));
1074 final String attrDepartmentNumber = fessConfig.getLdapAttrDepartmentNumber();
1075 OptionalUtil.ofNullable(user.getDepartmentNumber())
1076 .filter(StringUtil::isNotBlank)
1077 .ifPresent(s -> modifyReplaceEntry(modifyList, attrDepartmentNumber, s))
1078 .orElse(() -> getAttributeValueList(result, attrDepartmentNumber).stream()
1079 .forEach(v -> modifyDeleteEntry(modifyList, attrDepartmentNumber, v)));
1080 final String attrUidNumber = fessConfig.getLdapAttrUidNumber();
1081 OptionalUtil.ofNullable(user.getUidNumber())
1082 .filter(s -> StringUtil.isNotBlank(s.toString()))
1083 .ifPresent(s -> modifyReplaceEntry(modifyList, attrUidNumber, s.toString()))
1084 .orElse(() -> getAttributeValueList(result, attrUidNumber).stream()
1085 .forEach(v -> modifyDeleteEntry(modifyList, attrUidNumber, v)));
1086 final String attrGidNumber = fessConfig.getLdapAttrGidNumber();
1087 OptionalUtil.ofNullable(user.getGidNumber())
1088 .filter(s -> StringUtil.isNotBlank(s.toString()))
1089 .ifPresent(s -> modifyReplaceEntry(modifyList, attrGidNumber, s.toString()))
1090 .orElse(() -> getAttributeValueList(result, attrGidNumber).stream()
1091 .forEach(v -> modifyDeleteEntry(modifyList, attrGidNumber, v)));
1092 final String attrHomeDirectory = fessConfig.getLdapAttrHomeDirectory();
1093 OptionalUtil.ofNullable(user.getHomeDirectory())
1094 .filter(StringUtil::isNotBlank)
1095 .ifPresent(s -> modifyReplaceEntry(modifyList, attrHomeDirectory, s))
1096 .orElse(() -> getAttributeValueList(result, attrHomeDirectory).stream()
1097 .forEach(v -> modifyDeleteEntry(modifyList, attrHomeDirectory, v)));
1098
1099 modify(userDN, modifyList, adminEnv);
1100 }
1101
1102
1103
1104
1105
1106
1107
1108 protected void addUserAttributes(final BasicAttributes entry, final User user) {
1109 entry.put(new BasicAttribute("cn", user.getName()));
1110 entry.put(new BasicAttribute("userPassword", user.getOriginalPassword()));
1111
1112 OptionalUtil.ofNullable(user.getSurname())
1113 .filter(StringUtil::isNotBlank)
1114 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrSurname(), s)));
1115 OptionalUtil.ofNullable(user.getGivenName())
1116 .filter(StringUtil::isNotBlank)
1117 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrGivenName(), s)));
1118 OptionalUtil.ofNullable(user.getMail())
1119 .filter(StringUtil::isNotBlank)
1120 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrMail(), s)));
1121 OptionalUtil.ofNullable(user.getEmployeeNumber())
1122 .filter(StringUtil::isNotBlank)
1123 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrEmployeeNumber(), s)));
1124 OptionalUtil.ofNullable(user.getTelephoneNumber())
1125 .filter(StringUtil::isNotBlank)
1126 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrTelephoneNumber(), s)));
1127 OptionalUtil.ofNullable(user.getHomePhone())
1128 .filter(StringUtil::isNotBlank)
1129 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrHomePhone(), s)));
1130 OptionalUtil.ofNullable(user.getHomePostalAddress())
1131 .filter(StringUtil::isNotBlank)
1132 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrHomePostalAddress(), s)));
1133 OptionalUtil.ofNullable(user.getLabeledURI())
1134 .filter(StringUtil::isNotBlank)
1135 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrLabeledURI(), s)));
1136 OptionalUtil.ofNullable(user.getRoomNumber())
1137 .filter(StringUtil::isNotBlank)
1138 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrRoomNumber(), s)));
1139 OptionalUtil.ofNullable(user.getDescription())
1140 .filter(StringUtil::isNotBlank)
1141 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrDescription(), s)));
1142 OptionalUtil.ofNullable(user.getTitle())
1143 .filter(StringUtil::isNotBlank)
1144 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrTitle(), s)));
1145 OptionalUtil.ofNullable(user.getPager())
1146 .filter(StringUtil::isNotBlank)
1147 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrPager(), s)));
1148 OptionalUtil.ofNullable(user.getStreet())
1149 .filter(StringUtil::isNotBlank)
1150 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrStreet(), s)));
1151 OptionalUtil.ofNullable(user.getPostalCode())
1152 .filter(StringUtil::isNotBlank)
1153 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrPostalCode(), s)));
1154 OptionalUtil.ofNullable(user.getPhysicalDeliveryOfficeName())
1155 .filter(StringUtil::isNotBlank)
1156 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrPhysicalDeliveryOfficeName(), s)));
1157 OptionalUtil.ofNullable(user.getDestinationIndicator())
1158 .filter(StringUtil::isNotBlank)
1159 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrDestinationIndicator(), s)));
1160 OptionalUtil.ofNullable(user.getInternationaliSDNNumber())
1161 .filter(StringUtil::isNotBlank)
1162 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrInternationaliSDNNumber(), s)));
1163 OptionalUtil.ofNullable(user.getState())
1164 .filter(StringUtil::isNotBlank)
1165 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrState(), s)));
1166 OptionalUtil.ofNullable(user.getEmployeeType())
1167 .filter(StringUtil::isNotBlank)
1168 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrEmployeeType(), s)));
1169 OptionalUtil.ofNullable(user.getFacsimileTelephoneNumber())
1170 .filter(StringUtil::isNotBlank)
1171 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrFacsimileTelephoneNumber(), s)));
1172 OptionalUtil.ofNullable(user.getPostOfficeBox())
1173 .filter(StringUtil::isNotBlank)
1174 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrPostOfficeBox(), s)));
1175 OptionalUtil.ofNullable(user.getInitials())
1176 .filter(StringUtil::isNotBlank)
1177 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrInitials(), s)));
1178 OptionalUtil.ofNullable(user.getCarLicense())
1179 .filter(StringUtil::isNotBlank)
1180 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrCarLicense(), s)));
1181 OptionalUtil.ofNullable(user.getMobile())
1182 .filter(StringUtil::isNotBlank)
1183 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrMobile(), s)));
1184 OptionalUtil.ofNullable(user.getPostalAddress())
1185 .filter(StringUtil::isNotBlank)
1186 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrPostalAddress(), s)));
1187 OptionalUtil.ofNullable(user.getCity())
1188 .filter(StringUtil::isNotBlank)
1189 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrCity(), s)));
1190 OptionalUtil.ofNullable(user.getTeletexTerminalIdentifier())
1191 .filter(StringUtil::isNotBlank)
1192 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrTeletexTerminalIdentifier(), s)));
1193 OptionalUtil.ofNullable(user.getX121Address())
1194 .filter(StringUtil::isNotBlank)
1195 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrX121Address(), s)));
1196 OptionalUtil.ofNullable(user.getBusinessCategory())
1197 .filter(StringUtil::isNotBlank)
1198 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrBusinessCategory(), s)));
1199 OptionalUtil.ofNullable(user.getRegisteredAddress())
1200 .filter(StringUtil::isNotBlank)
1201 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrRegisteredAddress(), s)));
1202 OptionalUtil.ofNullable(user.getDisplayName())
1203 .filter(StringUtil::isNotBlank)
1204 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrDisplayName(), s)));
1205 OptionalUtil.ofNullable(user.getPreferredLanguage())
1206 .filter(StringUtil::isNotBlank)
1207 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrPreferredLanguage(), s)));
1208 OptionalUtil.ofNullable(user.getDepartmentNumber())
1209 .filter(StringUtil::isNotBlank)
1210 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrDepartmentNumber(), s)));
1211 OptionalUtil.ofNullable(user.getUidNumber())
1212 .filter(s -> StringUtil.isNotBlank(s.toString()))
1213 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrUidNumber(), s)));
1214 OptionalUtil.ofNullable(user.getGidNumber())
1215 .filter(s -> StringUtil.isNotBlank(s.toString()))
1216 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrGidNumber(), s)));
1217 OptionalUtil.ofNullable(user.getHomeDirectory())
1218 .filter(StringUtil::isNotBlank)
1219 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrHomeDirectory(), s)));
1220 }
1221
1222
1223
1224
1225
1226
1227
1228
1229 public void validateUserAttributes(final Class<?> type, final Map<String, String> attributes, final Consumer<String> consumer) {
1230 if (type == Long.class) {
1231
1232 final String attrUidNumber = fessConfig.getLdapAttrUidNumber();
1233 final String attrGidNumber = fessConfig.getLdapAttrGidNumber();
1234
1235 Stream.of(attrUidNumber, attrGidNumber)
1236 .forEach(attrName -> OptionalUtil.ofNullable(attributes.get(attrName)).filter(StringUtil::isNotBlank).ifPresent(s -> {
1237 try {
1238 DfTypeUtil.toLong(s);
1239 } catch (final NumberFormatException e) {
1240 consumer.accept(attrName);
1241 }
1242 }));
1243 } else {
1244
1245 }
1246 }
1247
1248
1249
1250
1251
1252
1253 public void delete(final User user) {
1254 if (!fessConfig.isLdapAdminEnabled(user.getName())) {
1255 return;
1256 }
1257
1258 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
1259 final String userDN = fessConfig.getLdapAdminUserSecurityPrincipal(user.getName());
1260
1261 stream(user.getGroupNames()).of(stream -> stream.forEach(name -> {
1262 search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(name), null, adminEnv, subResult -> {
1263 if (subResult.isEmpty()) {
1264 final Group group = new Group();
1265 group.setName(name);
1266 insert(group);
1267 }
1268 final List<ModificationItem> modifyList = new ArrayList<>();
1269 modifyDeleteEntry(modifyList, "member", userDN);
1270 modify(fessConfig.getLdapAdminGroupSecurityPrincipal(name), modifyList, adminEnv);
1271 });
1272 }));
1273 stream(user.getRoleNames()).of(stream -> stream.forEach(name -> {
1274 search(fessConfig.getLdapAdminRoleBaseDn(), fessConfig.getLdapAdminRoleFilter(name), null, adminEnv, subResult -> {
1275 if (subResult.isEmpty()) {
1276 final Role role = new Role();
1277 role.setName(name);
1278 insert(role);
1279 }
1280 final List<ModificationItem> modifyList = new ArrayList<>();
1281 modifyDeleteEntry(modifyList, "member", userDN);
1282 modify(fessConfig.getLdapAdminRoleSecurityPrincipal(name), modifyList, adminEnv);
1283 });
1284 }));
1285
1286 search(fessConfig.getLdapAdminUserBaseDn(), fessConfig.getLdapAdminUserFilter(user.getName()), null, adminEnv, result -> {
1287 if (!result.isEmpty()) {
1288 delete(userDN, adminEnv);
1289 } else {
1290 logger.info("User does not exist in LDAP server: name={}", user.getName());
1291 }
1292 });
1293
1294 }
1295
1296
1297
1298
1299
1300
1301 public void insert(final Role role) {
1302 if (!fessConfig.isLdapAdminEnabled()) {
1303 return;
1304 }
1305
1306 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
1307 search(fessConfig.getLdapAdminRoleBaseDn(), fessConfig.getLdapAdminRoleFilter(role.getName()), null, adminEnv, result -> {
1308 if (!result.isEmpty()) {
1309 logger.info("Role already exists in LDAP server: name={}", role.getName());
1310 } else {
1311 final String entryDN = fessConfig.getLdapAdminRoleSecurityPrincipal(role.getName());
1312 final BasicAttributes entry = new BasicAttributes();
1313 addRoleAttributes(entry, role);
1314 final Attribute oc = fessConfig.getLdapAdminRoleObjectClassAttribute();
1315 entry.put(oc);
1316 insert(entryDN, entry, adminEnv);
1317 }
1318 });
1319
1320 }
1321
1322
1323
1324
1325
1326
1327
1328 protected void addRoleAttributes(final BasicAttributes entry, final Role user) {
1329
1330 }
1331
1332
1333
1334
1335
1336
1337 public void delete(final Role role) {
1338 if (!fessConfig.isLdapAdminEnabled()) {
1339 return;
1340 }
1341
1342 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
1343 search(fessConfig.getLdapAdminRoleBaseDn(), fessConfig.getLdapAdminRoleFilter(role.getName()), null, adminEnv, result -> {
1344 if (!result.isEmpty()) {
1345 final String entryDN = fessConfig.getLdapAdminRoleSecurityPrincipal(role.getName());
1346 delete(entryDN, adminEnv);
1347 } else {
1348 logger.info("Role does not exist in LDAP server: name={}", role.getName());
1349 }
1350 });
1351
1352 }
1353
1354
1355
1356
1357
1358
1359 public void apply(final Group group) {
1360 if (!fessConfig.isLdapAdminEnabled()) {
1361 return;
1362 }
1363
1364 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
1365 search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(group.getName()), null, adminEnv, result -> {
1366 if (!result.isEmpty()) {
1367 setAttributeValue(result, fessConfig.getLdapAttrGidNumber(), o -> group.setGidNumber(DfTypeUtil.toLong(o)));
1368 }
1369 });
1370 }
1371
1372
1373
1374
1375
1376
1377 public void insert(final Group group) {
1378 if (!fessConfig.isLdapAdminEnabled()) {
1379 return;
1380 }
1381
1382 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
1383 final String entryDN = fessConfig.getLdapAdminGroupSecurityPrincipal(group.getName());
1384 search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(group.getName()), null, adminEnv, result -> {
1385 if (!result.isEmpty()) {
1386 logger.info("Group already exists in LDAP server: name={}", group.getName());
1387 modifyGroupAttributes(group, adminEnv, entryDN, result);
1388 } else {
1389 final BasicAttributes entry = new BasicAttributes();
1390 addGroupAttributes(entry, group);
1391 final Attribute oc = fessConfig.getLdapAdminGroupObjectClassAttribute();
1392 entry.put(oc);
1393 insert(entryDN, entry, adminEnv);
1394 }
1395 });
1396 }
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406 protected void modifyGroupAttributes(final Group group, final Supplier<Hashtable<String, String>> adminEnv, final String entryDN,
1407 final List<SearchResult> result) {
1408 final List<ModificationItem> modifyList = new ArrayList<>();
1409
1410 final String attrGidNumber = fessConfig.getLdapAttrGidNumber();
1411 OptionalUtil.ofNullable(group.getGidNumber())
1412 .filter(s -> StringUtil.isNotBlank(s.toString()))
1413 .ifPresent(s -> modifyReplaceEntry(modifyList, attrGidNumber, s.toString()))
1414 .orElse(() -> getAttributeValueList(result, attrGidNumber).stream()
1415 .forEach(v -> modifyDeleteEntry(modifyList, attrGidNumber, v)));
1416
1417 modify(entryDN, modifyList, adminEnv);
1418 }
1419
1420
1421
1422
1423
1424
1425
1426 protected void addGroupAttributes(final BasicAttributes entry, final Group group) {
1427 OptionalUtil.ofNullable(group.getGidNumber())
1428 .filter(s -> StringUtil.isNotBlank(s.toString()))
1429 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrGidNumber(), s)));
1430 }
1431
1432
1433
1434
1435
1436
1437
1438
1439 public void validateGroupAttributes(final Class<?> type, final Map<String, String> attributes, final Consumer<String> consumer) {
1440 if (type == Long.class) {
1441
1442 final String attrGidNumber = fessConfig.getLdapAttrGidNumber();
1443
1444 Stream.of(attrGidNumber)
1445 .forEach(attrName -> OptionalUtil.ofNullable(attributes.get(attrName)).filter(StringUtil::isNotBlank).ifPresent(s -> {
1446 try {
1447 DfTypeUtil.toLong(s);
1448 } catch (final NumberFormatException e) {
1449 consumer.accept(attrName);
1450 }
1451 }));
1452 } else {
1453
1454 }
1455 }
1456
1457
1458
1459
1460
1461
1462 public void delete(final Group group) {
1463 if (!fessConfig.isLdapAdminEnabled()) {
1464 return;
1465 }
1466
1467 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
1468 search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(group.getName()), null, adminEnv, result -> {
1469 if (!result.isEmpty()) {
1470 final String entryDN = fessConfig.getLdapAdminGroupSecurityPrincipal(group.getName());
1471 delete(entryDN, adminEnv);
1472 } else {
1473 logger.info("Group does not exist in LDAP server: name={}", group.getName());
1474 }
1475 });
1476 }
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493 public boolean changePassword(final String username, final String password) {
1494
1495 if (StringUtil.isBlank(username) || StringUtil.isBlank(password)) {
1496 logger.warn("Cannot change password: username or password is blank");
1497 return false;
1498 }
1499
1500 if (!fessConfig.isLdapAdminEnabled(username)) {
1501 if (logger.isDebugEnabled()) {
1502 logger.debug("LDAP admin not enabled for user: {}", username);
1503 }
1504 return false;
1505 }
1506
1507 try {
1508 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
1509 final String userDN = fessConfig.getLdapAdminUserSecurityPrincipal(username);
1510 search(fessConfig.getLdapAdminUserBaseDn(), fessConfig.getLdapAdminUserFilter(username), null, adminEnv, result -> {
1511 if (result.isEmpty()) {
1512 throw new LdapOperationException("User is not found: " + username);
1513 }
1514 final List<ModificationItem> modifyList = new ArrayList<>();
1515 modifyReplaceEntry(modifyList, "userPassword", password);
1516 modify(userDN, modifyList, adminEnv);
1517 });
1518 return true;
1519 } catch (final LdapOperationException e) {
1520 logger.warn("Failed to change password for user: {}", username, e);
1521 throw e;
1522 } catch (final Exception e) {
1523 logger.warn("Unexpected error while changing password for user: {}", username, e);
1524 return false;
1525 }
1526 }
1527
1528
1529
1530
1531
1532
1533
1534
1535 protected void insert(final String entryDN, final Attributes entry, final Supplier<Hashtable<String, String>> envSupplier) {
1536 try (DirContextHolder holder = getDirContext(envSupplier)) {
1537 logger.debug("Inserting LDAP entry: dn={}", entryDN);
1538 holder.get().createSubcontext(entryDN, entry);
1539 } catch (final NamingException e) {
1540 throw new LdapOperationException("Failed to add " + entryDN, e);
1541 }
1542 }
1543
1544
1545
1546
1547
1548
1549
1550 protected void delete(final String entryDN, final Supplier<Hashtable<String, String>> envSupplier) {
1551 try (DirContextHolder holder = getDirContext(envSupplier)) {
1552 logger.debug("Deleting LDAP entry: dn={}", entryDN);
1553 holder.get().destroySubcontext(entryDN);
1554 } catch (final NamingException e) {
1555 throw new LdapOperationException("Failed to delete " + entryDN, e);
1556 }
1557 }
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568 protected void search(final String baseDn, final String filter, final String[] returningAttrs,
1569 final Supplier<Hashtable<String, String>> envSupplier, final SearchConsumer consumer) {
1570 try (DirContextHolder holder = getDirContext(envSupplier)) {
1571 final SearchControls controls = new SearchControls();
1572 controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
1573 if (returningAttrs != null) {
1574 controls.setReturningAttributes(returningAttrs);
1575 }
1576
1577 final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
1578 final long startTime = systemHelper.getCurrentTimeAsLong();
1579 final List<SearchResult> list = Collections.list(holder.get().search(baseDn, filter, controls));
1580 if (logger.isDebugEnabled()) {
1581 logger.debug("LDAP search completed: time={}ms, baseDn={}, filter={}", systemHelper.getCurrentTimeAsLong() - startTime,
1582 baseDn, filter);
1583 }
1584 consumer.accept(list);
1585 } catch (final NamingException e) {
1586 throw new LdapOperationException("Failed to search " + baseDn + " with " + filter, e);
1587 }
1588 }
1589
1590
1591
1592
1593
1594
1595
1596
1597 protected void modifyAddEntry(final List<ModificationItem> modifyList, final String name, final String value) {
1598 final Attribute attr = new BasicAttribute(name, value);
1599 final ModificationItem mod = new ModificationItem(DirContext.ADD_ATTRIBUTE, attr);
1600 modifyList.add(mod);
1601 }
1602
1603
1604
1605
1606
1607
1608
1609
1610 protected void modifyReplaceEntry(final List<ModificationItem> modifyList, final String name, final String value) {
1611 final Attribute attr = new BasicAttribute(name, value);
1612 final ModificationItem mod = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr);
1613 modifyList.add(mod);
1614 }
1615
1616
1617
1618
1619
1620
1621
1622
1623 protected void modifyDeleteEntry(final List<ModificationItem> modifyList, final String name, final Object value) {
1624 final Attribute attr = new BasicAttribute(name, value);
1625 final ModificationItem mod = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attr);
1626 modifyList.add(mod);
1627 }
1628
1629
1630
1631
1632
1633
1634
1635
1636 protected void modify(final String dn, final List<ModificationItem> modifyList, final Supplier<Hashtable<String, String>> envSupplier) {
1637 if (modifyList.isEmpty()) {
1638 return;
1639 }
1640 try (DirContextHolder holder = getDirContext(envSupplier)) {
1641 holder.get().modifyAttributes(dn, modifyList.toArray(new ModificationItem[modifyList.size()]));
1642 } catch (final NamingException e) {
1643 throw new LdapOperationException("Failed to search " + dn, e);
1644 }
1645 }
1646
1647
1648
1649
1650 interface SearchConsumer {
1651
1652
1653
1654
1655
1656
1657 void accept(List<SearchResult> t) throws NamingException;
1658 }
1659
1660
1661
1662
1663
1664
1665
1666 protected DirContextHolder getDirContext(final Supplier<Hashtable<String, String>> envSupplier) {
1667 DirContextHolder holder = contextLocal.get();
1668 if (holder != null) {
1669 holder.inc();
1670 return holder;
1671 }
1672 final Hashtable<String, String> env = envSupplier.get();
1673 try {
1674 holder = new DirContextHolder(new InitialDirContext(env));
1675 contextLocal.set(holder);
1676 return holder;
1677 } catch (final NamingException e) {
1678 throw new LdapOperationException("Failed to create DirContext.", e);
1679 }
1680 }
1681
1682
1683
1684
1685 protected class DirContextHolder implements AutoCloseable {
1686 private final DirContext context;
1687
1688 private int counter = 1;
1689
1690
1691
1692
1693
1694
1695 protected DirContextHolder(final DirContext context) {
1696 this.context = context;
1697 }
1698
1699
1700
1701
1702
1703
1704 public DirContext get() {
1705 return context;
1706 }
1707
1708
1709
1710
1711 public void inc() {
1712 counter++;
1713 }
1714
1715 @Override
1716 public void close() {
1717 if (counter > 1) {
1718 counter--;
1719 } else {
1720 try {
1721 if (context != null) {
1722 try {
1723 context.close();
1724 } catch (final NamingException e) {
1725 if (logger.isDebugEnabled()) {
1726 logger.debug("Failed to close LDAP context", e);
1727 }
1728 }
1729 }
1730 } finally {
1731
1732 contextLocal.remove();
1733 }
1734 }
1735 }
1736 }
1737 }