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.annotation.PostConstruct;
36 import javax.naming.Context;
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.es.user.exentity.Group;
55 import org.codelibs.fess.es.user.exentity.Role;
56 import org.codelibs.fess.es.user.exentity.User;
57 import org.codelibs.fess.exception.LdapConfigurationException;
58 import org.codelibs.fess.exception.LdapOperationException;
59 import org.codelibs.fess.helper.SystemHelper;
60 import org.codelibs.fess.mylasta.direction.FessConfig;
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 public class LdapManager {
67 private static final Logger logger = LogManager.getLogger(LdapManager.class);
68
69 protected ThreadLocal<DirContextHolder> contextLocal = new ThreadLocal<>();
70
71 protected volatile boolean isBind = false;
72
73 protected FessConfig fessConfig;
74
75 @PostConstruct
76 public void init() {
77 if (logger.isDebugEnabled()) {
78 logger.debug("Initialize {}", this.getClass().getSimpleName());
79 }
80 fessConfig = ComponentUtil.getFessConfig();
81 }
82
83 protected Hashtable<String, String> createEnvironment(final String initialContextFactory, final String securityAuthentication,
84 final String providerUrl, final String principal, final String credntials) {
85 final Hashtable<String, String> env = new Hashtable<>();
86 putEnv(env, Context.INITIAL_CONTEXT_FACTORY, initialContextFactory);
87 putEnv(env, Context.SECURITY_AUTHENTICATION, securityAuthentication);
88 putEnv(env, Context.PROVIDER_URL, providerUrl);
89 putEnv(env, Context.SECURITY_PRINCIPAL, principal);
90 putEnv(env, Context.SECURITY_CREDENTIALS, credntials);
91 if (providerUrl != null && providerUrl.startsWith("ldaps://")) {
92 putEnv(env, Context.SECURITY_PROTOCOL, "ssl");
93 }
94 return env;
95 }
96
97 protected void putEnv(final Hashtable<String, String> env, final String key, final String value) {
98 if (value == null) {
99 throw new LdapConfigurationException(key + " is null.");
100 }
101 env.put(key, value);
102 }
103
104 protected Hashtable<String, String> createAdminEnv() {
105 return createEnvironment(
106 fessConfig.getLdapInitialContextFactory(),
107 fessConfig.getLdapSecurityAuthentication(), fessConfig.getLdapProviderUrl(),
108 fessConfig.getLdapAdminSecurityPrincipal(),
109 fessConfig.getLdapAdminSecurityCredentials());
110 }
111
112 protected Hashtable<String, String> createSearchEnv(final String username, final String password) {
113 return createEnvironment(
114 fessConfig.getLdapInitialContextFactory(),
115 fessConfig.getLdapSecurityAuthentication(),
116 fessConfig.getLdapProviderUrl(),
117 fessConfig.getLdapSecurityPrincipal(username), password);
118 }
119
120 protected Hashtable<String, String> createSearchEnv() {
121 return createEnvironment(
122 fessConfig.getLdapInitialContextFactory(),
123 fessConfig.getLdapSecurityAuthentication(), fessConfig.getLdapProviderUrl(),
124 fessConfig.getLdapAdminSecurityPrincipal(),
125 fessConfig.getLdapAdminSecurityCredentials());
126 }
127
128 public void updateConfig() {
129 isBind = false;
130 }
131
132 protected boolean validate() {
133 if (!isBind) {
134 if (fessConfig.getLdapAdminSecurityPrincipal() == null || fessConfig.getLdapAdminSecurityCredentials() == null) {
135
136 return !fessConfig.isLdapAuthValidation();
137 }
138 final Hashtable<String, String> env = createAdminEnv();
139 try (DirContextHolder holder = getDirContext(() -> env)) {
140 final DirContext context = holder.get();
141 if (logger.isDebugEnabled()) {
142 logger.debug("Logged in as Bind DN.", context);
143 }
144 isBind = true;
145 } catch (final Exception e) {
146 logger.warn("LDAP configuration is wrong.", e);
147 }
148 }
149 return isBind;
150 }
151
152 public OptionalEntity<FessUser> login(final String username, final String password) {
153 if (StringUtil.isBlank(fessConfig.getLdapProviderUrl())) {
154 return OptionalEntity.empty();
155 }
156
157 if (!validate()) {
158 return OptionalEntity.empty();
159 }
160
161 final Hashtable<String, String> env = createSearchEnv(username, password);
162 try (DirContextHolder holder = getDirContext(() -> env)) {
163 final DirContext context = holder.get();
164 if (logger.isDebugEnabled()) {
165 logger.debug("Logged in.", context);
166 }
167 return OptionalEntity.of(createLdapUser(username, env));
168 } catch (final Exception e) {
169 logger.debug("Login failed.", e);
170 }
171 return OptionalEntity.empty();
172 }
173
174 public OptionalEntity<FessUser> login(final String username) {
175 final Hashtable<String, String> env = createSearchEnv();
176 try (DirContextHolder holder = getDirContext(() -> env)) {
177 final DirContext context = holder.get();
178 if (logger.isDebugEnabled()) {
179 logger.debug("Logged in.", context);
180 }
181 return OptionalEntity.of(createLdapUser(username, env));
182 } catch (final Exception e) {
183 logger.debug("Login failed.", e);
184 }
185 return OptionalEntity.empty();
186 }
187
188 protected LdapUser createLdapUser(final String username, final Hashtable<String, String> env) {
189 return new LdapUser(env, username);
190 }
191
192 public String[] getRoles(final LdapUser ldapUser, final String bindDn, final String accountFilter, final String groupFilter,
193 final Consumer<String[]> lazyLoading) {
194 final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
195 final Set<String> roleSet = new HashSet<>();
196
197 if (fessConfig.isLdapRoleSearchUserEnabled()) {
198 roleSet.add(normalizePermissionName(systemHelper.getSearchRoleByUser(ldapUser.getName())));
199 }
200
201
202
203 final String filter = String.format(accountFilter, ldapUser.getName());
204 if (logger.isDebugEnabled()) {
205 logger.debug("Account Filter: {}", filter);
206 }
207 final Set<String> subRoleSet = new HashSet<>();
208 search(bindDn, filter, new String[] { fessConfig.getLdapMemberofAttribute() }, () -> ldapUser.getEnvironment(), result -> {
209 processSearchRoles(result, entryDn -> {
210 updateSearchRoles(roleSet, entryDn);
211
212 if (StringUtil.isNotBlank(groupFilter)) {
213 subRoleSet.add(entryDn);
214 }
215 });
216 });
217
218 if (logger.isDebugEnabled()) {
219 logger.debug("role: {}", roleSet);
220 }
221 final String[] roles = roleSet.toArray(new String[roleSet.size()]);
222
223 if (!subRoleSet.isEmpty()) {
224 TimeoutManager.getInstance().addTimeoutTarget(() -> {
225 processSubRoles(ldapUser, bindDn, subRoleSet, groupFilter, roleSet);
226 if (logger.isDebugEnabled()) {
227 logger.debug("role(lazy loading): {}", roleSet);
228 }
229 lazyLoading.accept(roleSet.toArray(new String[roleSet.size()]));
230 }, 0, false);
231 }
232
233 return roles;
234 }
235
236 protected void processSubRoles(final LdapUser ldapUser, final String bindDn, final Set<String> subRoleSet, final String groupFilter,
237 final Set<String> roleSet) {
238
239 if (subRoleSet.isEmpty()) {
240 return;
241 }
242 String filter = subRoleSet.stream().map(s -> String.format(groupFilter, s)).collect(Collectors.joining());
243 if (subRoleSet.size() > 1) {
244 filter = "(|" + filter + ")";
245 }
246
247 if (logger.isDebugEnabled()) {
248 logger.debug("Group Filter: {}", filter);
249 }
250 search(bindDn, filter, null, () -> ldapUser.getEnvironment(), result -> {
251 for (final SearchResult srcrslt : result) {
252 final String groupDn = srcrslt.getNameInNamespace();
253 if (logger.isDebugEnabled()) {
254 logger.debug("groupDn: {}", groupDn);
255 }
256 updateSearchRoles(roleSet, groupDn);
257 }
258 });
259 }
260
261 protected void updateSearchRoles(final Set<String> roleSet, final String entryDn) {
262 final String name = getSearchRoleName(entryDn);
263 if (StringUtil.isBlank(name)) {
264 return;
265 }
266
267 final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
268 final boolean isRole = entryDn.toLowerCase(Locale.ROOT).indexOf("ou=role") != -1;
269 if (isRole) {
270 if (fessConfig.isLdapRoleSearchRoleEnabled()) {
271 roleSet.add(systemHelper.getSearchRoleByRole(normalizePermissionName(name)));
272 }
273 } else if (fessConfig.isLdapRoleSearchGroupEnabled()) {
274 roleSet.add(systemHelper.getSearchRoleByGroup(normalizePermissionName(name)));
275 }
276 }
277
278 public String normalizePermissionName(final String name) {
279 if (fessConfig.isLdapLowercasePermissionName()) {
280 return name.toLowerCase(Locale.ROOT);
281 }
282 return name;
283 }
284
285 protected void processSearchRoles(final List<SearchResult> result, final BiConsumer<String, String> consumer) throws NamingException {
286 processSearchRoles(result, entryDn -> {
287 final String name = getSearchRoleName(entryDn);
288 if (name != null) {
289 consumer.accept(entryDn, name);
290 }
291 });
292 }
293
294 protected void processSearchRoles(final List<SearchResult> result, final Consumer<String> consumer) throws NamingException {
295 for (final SearchResult srcrslt : result) {
296 final Attributes attrs = srcrslt.getAttributes();
297
298
299 final Attribute attr = attrs.get(fessConfig.getLdapMemberofAttribute());
300 if (attr == null) {
301 continue;
302 }
303
304 for (int i = 0; i < attr.size(); i++) {
305 final Object attrValue = attr.get(i);
306 if (attrValue != null) {
307 final String entryDn = attrValue.toString();
308
309 if (logger.isDebugEnabled()) {
310 logger.debug("entryDn: {}", entryDn);
311 }
312 consumer.accept(entryDn);
313 }
314 }
315 }
316 }
317
318 protected String getSearchRoleName(final String entryDn) {
319 if (entryDn == null) {
320 return null;
321 }
322 int start = entryDn.toLowerCase(Locale.ROOT).indexOf("cn=");
323 if (start == -1) {
324 return null;
325 }
326 start += 3;
327
328 final int end = entryDn.indexOf(',', start);
329 final String value = end == -1 ? entryDn.substring(start) : entryDn.substring(start, end);
330 if (fessConfig.isLdapGroupNameWithUnderscores()) {
331 return replaceWithUnderscores(value);
332 }
333 return value;
334 }
335
336 protected String replaceWithUnderscores(final String value) {
337 return value.replaceAll("[/\\\\\\[\\]:;|=,+\\*?<>]", "_");
338 }
339
340 protected void setAttributeValue(final List<SearchResult> result, final String name, final Consumer<Object> consumer) {
341 final List<Object> attrList = getAttributeValueList(result, name);
342 if (!attrList.isEmpty()) {
343 consumer.accept(attrList.get(0));
344 }
345 }
346
347 protected List<Object> getAttributeValueList(final List<SearchResult> result, final String name) {
348 try {
349 for (final SearchResult srcrslt : result) {
350 final Attributes attrs = srcrslt.getAttributes();
351
352 final Attribute attr = attrs.get(name);
353 if (attr == null) {
354 continue;
355 }
356
357 final List<Object> attrList = new ArrayList<>();
358 for (int i = 0; i < attr.size(); i++) {
359 final Object attrValue = attr.get(i);
360 if (attrValue != null) {
361 attrList.add(attrValue);
362 }
363 }
364 return attrList;
365 }
366 return Collections.emptyList();
367 } catch (final NamingException e) {
368 throw new LdapOperationException("Failed to parse attribute values for " + name, e);
369 }
370 }
371
372 public void apply(final User user) {
373 if (!fessConfig.isLdapAdminEnabled(user.getName())) {
374 return;
375 }
376
377 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
378 search(fessConfig.getLdapAdminUserBaseDn(), fessConfig.getLdapAdminUserFilter(user.getName()), null, adminEnv, result -> {
379 if (!result.isEmpty()) {
380 setAttributeValue(result, fessConfig.getLdapAttrSurname(), o -> user.setSurname(o.toString()));
381 setAttributeValue(result, fessConfig.getLdapAttrGivenName(), o -> user.setGivenName(o.toString()));
382 setAttributeValue(result, fessConfig.getLdapAttrMail(), o -> user.setMail(o.toString()));
383 setAttributeValue(result, fessConfig.getLdapAttrEmployeeNumber(), o -> user.setEmployeeNumber(o.toString()));
384 setAttributeValue(result, fessConfig.getLdapAttrTelephoneNumber(), o -> user.setTelephoneNumber(o.toString()));
385 setAttributeValue(result, fessConfig.getLdapAttrHomePhone(), o -> user.setHomePhone(o.toString()));
386 setAttributeValue(result, fessConfig.getLdapAttrHomePostalAddress(), o -> user.setHomePostalAddress(o.toString()));
387 setAttributeValue(result, fessConfig.getLdapAttrLabeleduri(), o -> user.setLabeledURI(o.toString()));
388 setAttributeValue(result, fessConfig.getLdapAttrRoomNumber(), o -> user.setRoomNumber(o.toString()));
389 setAttributeValue(result, fessConfig.getLdapAttrDescription(), o -> user.setDescription(o.toString()));
390 setAttributeValue(result, fessConfig.getLdapAttrTitle(), o -> user.setTitle(o.toString()));
391 setAttributeValue(result, fessConfig.getLdapAttrPager(), o -> user.setPager(o.toString()));
392 setAttributeValue(result, fessConfig.getLdapAttrStreet(), o -> user.setStreet(o.toString()));
393 setAttributeValue(result, fessConfig.getLdapAttrPostalCode(), o -> user.setPostalCode(o.toString()));
394 setAttributeValue(result, fessConfig.getLdapAttrPhysicalDeliveryOfficeName(),
395 o -> user.setPhysicalDeliveryOfficeName(o.toString()));
396 setAttributeValue(result, fessConfig.getLdapAttrDestinationIndicator(), o -> user.setDestinationIndicator(o.toString()));
397 setAttributeValue(result, fessConfig.getLdapAttrInternationalisdnNumber(),
398 o -> user.setInternationaliSDNNumber(o.toString()));
399 setAttributeValue(result, fessConfig.getLdapAttrState(), o -> user.setState(o.toString()));
400 setAttributeValue(result, fessConfig.getLdapAttrEmployeeType(), o -> user.setEmployeeType(o.toString()));
401 setAttributeValue(result, fessConfig.getLdapAttrFacsimileTelephoneNumber(),
402 o -> user.setFacsimileTelephoneNumber(o.toString()));
403 setAttributeValue(result, fessConfig.getLdapAttrPostOfficeBox(), o -> user.setPostOfficeBox(o.toString()));
404 setAttributeValue(result, fessConfig.getLdapAttrInitials(), o -> user.setInitials(o.toString()));
405 setAttributeValue(result, fessConfig.getLdapAttrCarLicense(), o -> user.setCarLicense(o.toString()));
406 setAttributeValue(result, fessConfig.getLdapAttrMobile(), o -> user.setMobile(o.toString()));
407 setAttributeValue(result, fessConfig.getLdapAttrPostalAddress(), o -> user.setPostalAddress(o.toString()));
408 setAttributeValue(result, fessConfig.getLdapAttrCity(), o -> user.setCity(o.toString()));
409 setAttributeValue(result, fessConfig.getLdapAttrTeletexTerminalIdentifier(),
410 o -> user.setTeletexTerminalIdentifier(o.toString()));
411 setAttributeValue(result, fessConfig.getLdapAttrX121Address(), o -> user.setX121Address(o.toString()));
412 setAttributeValue(result, fessConfig.getLdapAttrBusinessCategory(), o -> user.setBusinessCategory(o.toString()));
413 setAttributeValue(result, fessConfig.getLdapAttrRegisteredAddress(), o -> user.setRegisteredAddress(o.toString()));
414 setAttributeValue(result, fessConfig.getLdapAttrDisplayName(), o -> user.setDisplayName(o.toString()));
415 setAttributeValue(result, fessConfig.getLdapAttrPreferredLanguage(), o -> user.setPreferredLanguage(o.toString()));
416 setAttributeValue(result, fessConfig.getLdapAttrDepartmentNumber(), o -> user.setDepartmentNumber(o.toString()));
417 setAttributeValue(result, fessConfig.getLdapAttrUidNumber(), o -> user.setUidNumber(DfTypeUtil.toLong(o)));
418 setAttributeValue(result, fessConfig.getLdapAttrGidNumber(), o -> user.setGidNumber(DfTypeUtil.toLong(o)));
419 setAttributeValue(result, fessConfig.getLdapAttrHomeDirectory(), o -> user.setHomeDirectory(o.toString()));
420 }
421 });
422
423
424 search(fessConfig.getLdapAdminUserBaseDn(), fessConfig.getLdapAdminUserFilter(user.getName()),
425 new String[] { fessConfig.getLdapMemberofAttribute() }, adminEnv, result -> {
426 if (!result.isEmpty()) {
427 final List<String> groupList = new ArrayList<>();
428 final List<String> roleList = new ArrayList<>();
429 final String lowerGroupDn = fessConfig.getLdapAdminGroupBaseDn().toLowerCase(Locale.ROOT);
430 final String lowerRoleDn = fessConfig.getLdapAdminRoleBaseDn().toLowerCase(Locale.ROOT);
431 processSearchRoles(result, (entryDn, name) -> {
432 final String lowerEntryDn = entryDn.toLowerCase(Locale.ROOT);
433 if (lowerEntryDn.indexOf(lowerGroupDn) != -1) {
434 groupList.add(Base64.getUrlEncoder().encodeToString(name.getBytes(Constants.CHARSET_UTF_8)));
435 } else if (lowerEntryDn.indexOf(lowerRoleDn) != -1) {
436 roleList.add(Base64.getUrlEncoder().encodeToString(name.getBytes(Constants.CHARSET_UTF_8)));
437 }
438 });
439 user.setGroups(groupList.toArray(new String[groupList.size()]));
440 user.setRoles(roleList.toArray(new String[roleList.size()]));
441 }
442 });
443
444 }
445
446 public void insert(final User user) {
447 if (!fessConfig.isLdapAdminEnabled(user.getName())) {
448 return;
449 }
450
451 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
452 final String userDN = fessConfig.getLdapAdminUserSecurityPrincipal(user.getName());
453
454 search(fessConfig.getLdapAdminUserBaseDn(), fessConfig.getLdapAdminUserFilter(user.getName()), null, adminEnv, result -> {
455 if (!result.isEmpty()) {
456 modifyUserAttributes(user, adminEnv, userDN, result);
457 } else {
458 final BasicAttributes entry = new BasicAttributes();
459 addUserAttributes(entry, user);
460 final Attribute oc = fessConfig.getLdapAdminUserObjectClassAttribute();
461 entry.put(oc);
462 insert(userDN, entry, adminEnv);
463 }
464 });
465
466
467 search(fessConfig.getLdapAdminUserBaseDn(), fessConfig.getLdapAdminUserFilter(user.getName()),
468 new String[] { fessConfig.getLdapMemberofAttribute() }, adminEnv, result -> {
469 if (!result.isEmpty()) {
470 final List<String> oldGroupList = new ArrayList<>();
471 final List<String> oldRoleList = new ArrayList<>();
472 final String lowerGroupDn = fessConfig.getLdapAdminGroupBaseDn().toLowerCase(Locale.ROOT);
473 final String lowerRoleDn = fessConfig.getLdapAdminRoleBaseDn().toLowerCase(Locale.ROOT);
474 processSearchRoles(result, (entryDn, name) -> {
475 final String lowerEntryDn = entryDn.toLowerCase(Locale.ROOT);
476 if (lowerEntryDn.indexOf(lowerGroupDn) != -1) {
477 oldGroupList.add(name);
478 } else if (lowerEntryDn.indexOf(lowerRoleDn) != -1) {
479 oldRoleList.add(name);
480 }
481 });
482 final List<String> newGroupList = stream(user.getGroupNames()).get(stream -> stream.collect(Collectors.toList()));
483 stream(user.getGroupNames()).of(stream -> stream.forEach(name -> {
484 if (oldGroupList.contains(name)) {
485 oldGroupList.remove(name);
486 newGroupList.remove(name);
487 }
488 }));
489 oldGroupList.stream().forEach(name -> {
490 search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(name), null, adminEnv,
491 subResult -> {
492 if (!subResult.isEmpty()) {
493 final List<ModificationItem> modifyList = new ArrayList<>();
494 modifyDeleteEntry(modifyList, "member", userDN);
495 modify(fessConfig.getLdapAdminGroupSecurityPrincipal(name), modifyList, adminEnv);
496 }
497 });
498 });
499 newGroupList.stream().forEach(name -> {
500 search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(name), null, adminEnv,
501 subResult -> {
502 if (subResult.isEmpty()) {
503 final Group group = new Group();
504 group.setName(name);
505 insert(group);
506 }
507 final List<ModificationItem> modifyList = new ArrayList<>();
508 modifyAddEntry(modifyList, "member", userDN);
509 modify(fessConfig.getLdapAdminGroupSecurityPrincipal(name), modifyList, adminEnv);
510 });
511 });
512
513 final List<String> newRoleList = stream(user.getRoleNames()).get(stream -> stream.collect(Collectors.toList()));
514 stream(user.getRoleNames()).of(stream -> stream.forEach(name -> {
515 if (oldRoleList.contains(name)) {
516 oldRoleList.remove(name);
517 newRoleList.remove(name);
518 }
519 }));
520 oldRoleList.stream().forEach(name -> {
521 search(fessConfig.getLdapAdminRoleBaseDn(), fessConfig.getLdapAdminRoleFilter(name), null, adminEnv,
522 subResult -> {
523 if (!subResult.isEmpty()) {
524 final List<ModificationItem> modifyList = new ArrayList<>();
525 modifyDeleteEntry(modifyList, "member", userDN);
526 modify(fessConfig.getLdapAdminRoleSecurityPrincipal(name), modifyList, adminEnv);
527 }
528 });
529 });
530 newRoleList.stream().forEach(name -> {
531 search(fessConfig.getLdapAdminRoleBaseDn(), fessConfig.getLdapAdminRoleFilter(name), null, adminEnv,
532 subResult -> {
533 if (subResult.isEmpty()) {
534 final Role role = new Role();
535 role.setName(name);
536 insert(role);
537 }
538 final List<ModificationItem> modifyList = new ArrayList<>();
539 modifyAddEntry(modifyList, "member", userDN);
540 modify(fessConfig.getLdapAdminRoleSecurityPrincipal(name), modifyList, adminEnv);
541 });
542 });
543 } else {
544 stream(user.getGroupNames()).of(stream -> stream.forEach(name -> {
545 search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(name), null, adminEnv,
546 subResult -> {
547 if (subResult.isEmpty()) {
548 final Group group = new Group();
549 group.setName(name);
550 insert(group);
551 }
552 final List<ModificationItem> modifyList = new ArrayList<>();
553 modifyAddEntry(modifyList, "member", userDN);
554 modify(fessConfig.getLdapAdminGroupSecurityPrincipal(name), modifyList, adminEnv);
555 });
556 }));
557
558 stream(user.getRoleNames()).of(stream -> stream.forEach(name -> {
559 search(fessConfig.getLdapAdminRoleBaseDn(), fessConfig.getLdapAdminRoleFilter(name), null, adminEnv,
560 subResult -> {
561 if (subResult.isEmpty()) {
562 final Role role = new Role();
563 role.setName(name);
564 insert(role);
565 }
566 final List<ModificationItem> modifyList = new ArrayList<>();
567 modifyAddEntry(modifyList, "member", userDN);
568 modify(fessConfig.getLdapAdminRoleSecurityPrincipal(name), modifyList, adminEnv);
569 });
570 }));
571 }
572 });
573
574 }
575
576 protected void modifyUserAttributes(final User user, final Supplier<Hashtable<String, String>> adminEnv, final String userDN,
577 final List<SearchResult> result) {
578 final List<ModificationItem> modifyList = new ArrayList<>();
579 if (user.getOriginalPassword() != null) {
580 modifyReplaceEntry(modifyList, "userPassword", user.getOriginalPassword());
581 }
582
583 final String attrSurname = fessConfig.getLdapAttrSurname();
584 OptionalUtil.ofNullable(user.getSurname()).filter(StringUtil::isNotBlank)
585 .ifPresent(s -> modifyReplaceEntry(modifyList, attrSurname, s)).orElse(() -> getAttributeValueList(result, attrSurname)
586 .stream().forEach(v -> modifyDeleteEntry(modifyList, attrSurname, v)));
587 final String attrGivenName = fessConfig.getLdapAttrGivenName();
588 OptionalUtil.ofNullable(user.getGivenName()).filter(StringUtil::isNotBlank)
589 .ifPresent(s -> modifyReplaceEntry(modifyList, attrGivenName, s)).orElse(() -> getAttributeValueList(result, attrGivenName)
590 .stream().forEach(v -> modifyDeleteEntry(modifyList, attrGivenName, v)));
591 final String attrMail = fessConfig.getLdapAttrMail();
592 OptionalUtil.ofNullable(user.getMail()).filter(StringUtil::isNotBlank).ifPresent(s -> modifyReplaceEntry(modifyList, attrMail, s))
593 .orElse(() -> getAttributeValueList(result, attrMail).stream().forEach(v -> modifyDeleteEntry(modifyList, attrMail, v)));
594 final String attrEmployeeNumber = fessConfig.getLdapAttrEmployeeNumber();
595 OptionalUtil.ofNullable(user.getEmployeeNumber()).filter(StringUtil::isNotBlank)
596 .ifPresent(s -> modifyReplaceEntry(modifyList, attrEmployeeNumber, s))
597 .orElse(() -> getAttributeValueList(result, attrEmployeeNumber).stream()
598 .forEach(v -> modifyDeleteEntry(modifyList, attrEmployeeNumber, v)));
599 final String attrTelephoneNumber = fessConfig.getLdapAttrTelephoneNumber();
600 OptionalUtil.ofNullable(user.getTelephoneNumber()).filter(StringUtil::isNotBlank)
601 .ifPresent(s -> modifyReplaceEntry(modifyList, attrTelephoneNumber, s))
602 .orElse(() -> getAttributeValueList(result, attrTelephoneNumber).stream()
603 .forEach(v -> modifyDeleteEntry(modifyList, attrTelephoneNumber, v)));
604 final String attrHomePhone = fessConfig.getLdapAttrHomePhone();
605 OptionalUtil.ofNullable(user.getHomePhone()).filter(StringUtil::isNotBlank)
606 .ifPresent(s -> modifyReplaceEntry(modifyList, attrHomePhone, s)).orElse(() -> getAttributeValueList(result, attrHomePhone)
607 .stream().forEach(v -> modifyDeleteEntry(modifyList, attrHomePhone, v)));
608 final String attrHomePostalAddress = fessConfig.getLdapAttrHomePostalAddress();
609 OptionalUtil.ofNullable(user.getHomePostalAddress()).filter(StringUtil::isNotBlank)
610 .ifPresent(s -> modifyReplaceEntry(modifyList, attrHomePostalAddress, s))
611 .orElse(() -> getAttributeValueList(result, attrHomePostalAddress).stream()
612 .forEach(v -> modifyDeleteEntry(modifyList, attrHomePostalAddress, v)));
613 final String attrLabeledURI = fessConfig.getLdapAttrLabeleduri();
614 OptionalUtil.ofNullable(user.getLabeledURI()).filter(StringUtil::isNotBlank)
615 .ifPresent(s -> modifyReplaceEntry(modifyList, attrLabeledURI, s))
616 .orElse(() -> getAttributeValueList(result, attrLabeledURI).stream()
617 .forEach(v -> modifyDeleteEntry(modifyList, attrLabeledURI, v)));
618 final String attrRoomNumber = fessConfig.getLdapAttrRoomNumber();
619 OptionalUtil.ofNullable(user.getRoomNumber()).filter(StringUtil::isNotBlank)
620 .ifPresent(s -> modifyReplaceEntry(modifyList, attrRoomNumber, s))
621 .orElse(() -> getAttributeValueList(result, attrRoomNumber).stream()
622 .forEach(v -> modifyDeleteEntry(modifyList, attrRoomNumber, v)));
623 final String attrDescription = fessConfig.getLdapAttrDescription();
624 OptionalUtil.ofNullable(user.getDescription()).filter(StringUtil::isNotBlank)
625 .ifPresent(s -> modifyReplaceEntry(modifyList, attrDescription, s))
626 .orElse(() -> getAttributeValueList(result, attrDescription).stream()
627 .forEach(v -> modifyDeleteEntry(modifyList, attrDescription, v)));
628 final String attrTitle = fessConfig.getLdapAttrTitle();
629 OptionalUtil.ofNullable(user.getTitle()).filter(StringUtil::isNotBlank).ifPresent(s -> modifyReplaceEntry(modifyList, attrTitle, s))
630 .orElse(() -> getAttributeValueList(result, attrTitle).stream().forEach(v -> modifyDeleteEntry(modifyList, attrTitle, v)));
631 final String attrPager = fessConfig.getLdapAttrPager();
632 OptionalUtil.ofNullable(user.getPager()).filter(StringUtil::isNotBlank).ifPresent(s -> modifyReplaceEntry(modifyList, attrPager, s))
633 .orElse(() -> getAttributeValueList(result, attrPager).stream().forEach(v -> modifyDeleteEntry(modifyList, attrPager, v)));
634 final String attrStreet = fessConfig.getLdapAttrStreet();
635 OptionalUtil.ofNullable(user.getStreet()).filter(StringUtil::isNotBlank)
636 .ifPresent(s -> modifyReplaceEntry(modifyList, attrStreet, s)).orElse(() -> getAttributeValueList(result, attrStreet)
637 .stream().forEach(v -> modifyDeleteEntry(modifyList, attrStreet, v)));
638 final String attrPostalCode = fessConfig.getLdapAttrPostalCode();
639 OptionalUtil.ofNullable(user.getPostalCode()).filter(StringUtil::isNotBlank)
640 .ifPresent(s -> modifyReplaceEntry(modifyList, attrPostalCode, s))
641 .orElse(() -> getAttributeValueList(result, attrPostalCode).stream()
642 .forEach(v -> modifyDeleteEntry(modifyList, attrPostalCode, v)));
643 final String attrPhysicalDeliveryOfficeName = fessConfig.getLdapAttrPhysicalDeliveryOfficeName();
644 OptionalUtil.ofNullable(user.getPhysicalDeliveryOfficeName()).filter(StringUtil::isNotBlank)
645 .ifPresent(s -> modifyReplaceEntry(modifyList, attrPhysicalDeliveryOfficeName, s))
646 .orElse(() -> getAttributeValueList(result, attrPhysicalDeliveryOfficeName).stream()
647 .forEach(v -> modifyDeleteEntry(modifyList, attrPhysicalDeliveryOfficeName, v)));
648 final String attrDestinationIndicator = fessConfig.getLdapAttrDestinationIndicator();
649 OptionalUtil.ofNullable(user.getDestinationIndicator()).filter(StringUtil::isNotBlank)
650 .ifPresent(s -> modifyReplaceEntry(modifyList, attrDestinationIndicator, s))
651 .orElse(() -> getAttributeValueList(result, attrDestinationIndicator).stream()
652 .forEach(v -> modifyDeleteEntry(modifyList, attrDestinationIndicator, v)));
653 final String attrInternationaliSDNNumber = fessConfig.getLdapAttrInternationalisdnNumber();
654 OptionalUtil.ofNullable(user.getInternationaliSDNNumber()).filter(StringUtil::isNotBlank)
655 .ifPresent(s -> modifyReplaceEntry(modifyList, attrInternationaliSDNNumber, s))
656 .orElse(() -> getAttributeValueList(result, attrInternationaliSDNNumber).stream()
657 .forEach(v -> modifyDeleteEntry(modifyList, attrInternationaliSDNNumber, v)));
658 final String attrState = fessConfig.getLdapAttrState();
659 OptionalUtil.ofNullable(user.getState()).filter(StringUtil::isNotBlank).ifPresent(s -> modifyReplaceEntry(modifyList, attrState, s))
660 .orElse(() -> getAttributeValueList(result, attrState).stream().forEach(v -> modifyDeleteEntry(modifyList, attrState, v)));
661 final String attrEmployeeType = fessConfig.getLdapAttrEmployeeType();
662 OptionalUtil.ofNullable(user.getEmployeeType()).filter(StringUtil::isNotBlank)
663 .ifPresent(s -> modifyReplaceEntry(modifyList, attrEmployeeType, s))
664 .orElse(() -> getAttributeValueList(result, attrEmployeeType).stream()
665 .forEach(v -> modifyDeleteEntry(modifyList, attrEmployeeType, v)));
666 final String attrFacsimileTelephoneNumber = fessConfig.getLdapAttrFacsimileTelephoneNumber();
667 OptionalUtil.ofNullable(user.getFacsimileTelephoneNumber()).filter(StringUtil::isNotBlank)
668 .ifPresent(s -> modifyReplaceEntry(modifyList, attrFacsimileTelephoneNumber, s))
669 .orElse(() -> getAttributeValueList(result, attrFacsimileTelephoneNumber).stream()
670 .forEach(v -> modifyDeleteEntry(modifyList, attrFacsimileTelephoneNumber, v)));
671 final String attrPostOfficeBox = fessConfig.getLdapAttrPostOfficeBox();
672 OptionalUtil.ofNullable(user.getPostOfficeBox()).filter(StringUtil::isNotBlank)
673 .ifPresent(s -> modifyReplaceEntry(modifyList, attrPostOfficeBox, s))
674 .orElse(() -> getAttributeValueList(result, attrPostOfficeBox).stream()
675 .forEach(v -> modifyDeleteEntry(modifyList, attrPostOfficeBox, v)));
676 final String attrInitials = fessConfig.getLdapAttrInitials();
677 OptionalUtil.ofNullable(user.getInitials()).filter(StringUtil::isNotBlank)
678 .ifPresent(s -> modifyReplaceEntry(modifyList, attrInitials, s)).orElse(() -> getAttributeValueList(result, attrInitials)
679 .stream().forEach(v -> modifyDeleteEntry(modifyList, attrInitials, v)));
680 final String attrCarLicense = fessConfig.getLdapAttrCarLicense();
681 OptionalUtil.ofNullable(user.getCarLicense()).filter(StringUtil::isNotBlank)
682 .ifPresent(s -> modifyReplaceEntry(modifyList, attrCarLicense, s))
683 .orElse(() -> getAttributeValueList(result, attrCarLicense).stream()
684 .forEach(v -> modifyDeleteEntry(modifyList, attrCarLicense, v)));
685 final String attrMobile = fessConfig.getLdapAttrMobile();
686 OptionalUtil.ofNullable(user.getMobile()).filter(StringUtil::isNotBlank)
687 .ifPresent(s -> modifyReplaceEntry(modifyList, attrMobile, s)).orElse(() -> getAttributeValueList(result, attrMobile)
688 .stream().forEach(v -> modifyDeleteEntry(modifyList, attrMobile, v)));
689 final String attrPostalAddress = fessConfig.getLdapAttrPostalAddress();
690 OptionalUtil.ofNullable(user.getPostalAddress()).filter(StringUtil::isNotBlank)
691 .ifPresent(s -> modifyReplaceEntry(modifyList, attrPostalAddress, s))
692 .orElse(() -> getAttributeValueList(result, attrPostalAddress).stream()
693 .forEach(v -> modifyDeleteEntry(modifyList, attrPostalAddress, v)));
694 final String attrCity = fessConfig.getLdapAttrCity();
695 OptionalUtil.ofNullable(user.getCity()).filter(StringUtil::isNotBlank).ifPresent(s -> modifyReplaceEntry(modifyList, attrCity, s))
696 .orElse(() -> getAttributeValueList(result, attrCity).stream().forEach(v -> modifyDeleteEntry(modifyList, attrCity, v)));
697 final String attrTeletexTerminalIdentifier = fessConfig.getLdapAttrTeletexTerminalIdentifier();
698 OptionalUtil.ofNullable(user.getTeletexTerminalIdentifier()).filter(StringUtil::isNotBlank)
699 .ifPresent(s -> modifyReplaceEntry(modifyList, attrTeletexTerminalIdentifier, s))
700 .orElse(() -> getAttributeValueList(result, attrTeletexTerminalIdentifier).stream()
701 .forEach(v -> modifyDeleteEntry(modifyList, attrTeletexTerminalIdentifier, v)));
702 final String attrX121Address = fessConfig.getLdapAttrX121Address();
703 OptionalUtil.ofNullable(user.getX121Address()).filter(StringUtil::isNotBlank)
704 .ifPresent(s -> modifyReplaceEntry(modifyList, attrX121Address, s))
705 .orElse(() -> getAttributeValueList(result, attrX121Address).stream()
706 .forEach(v -> modifyDeleteEntry(modifyList, attrX121Address, v)));
707 final String attrBusinessCategory = fessConfig.getLdapAttrBusinessCategory();
708 OptionalUtil.ofNullable(user.getBusinessCategory()).filter(StringUtil::isNotBlank)
709 .ifPresent(s -> modifyReplaceEntry(modifyList, attrBusinessCategory, s))
710 .orElse(() -> getAttributeValueList(result, attrBusinessCategory).stream()
711 .forEach(v -> modifyDeleteEntry(modifyList, attrBusinessCategory, v)));
712 final String attrRegisteredAddress = fessConfig.getLdapAttrRegisteredAddress();
713 OptionalUtil.ofNullable(user.getRegisteredAddress()).filter(StringUtil::isNotBlank)
714 .ifPresent(s -> modifyReplaceEntry(modifyList, attrRegisteredAddress, s))
715 .orElse(() -> getAttributeValueList(result, attrRegisteredAddress).stream()
716 .forEach(v -> modifyDeleteEntry(modifyList, attrRegisteredAddress, v)));
717 final String attrDisplayName = fessConfig.getLdapAttrDisplayName();
718 OptionalUtil.ofNullable(user.getDisplayName()).filter(StringUtil::isNotBlank)
719 .ifPresent(s -> modifyReplaceEntry(modifyList, attrDisplayName, s))
720 .orElse(() -> getAttributeValueList(result, attrDisplayName).stream()
721 .forEach(v -> modifyDeleteEntry(modifyList, attrDisplayName, v)));
722 final String attrPreferredLanguage = fessConfig.getLdapAttrPreferredLanguage();
723 OptionalUtil.ofNullable(user.getPreferredLanguage()).filter(StringUtil::isNotBlank)
724 .ifPresent(s -> modifyReplaceEntry(modifyList, attrPreferredLanguage, s))
725 .orElse(() -> getAttributeValueList(result, attrPreferredLanguage).stream()
726 .forEach(v -> modifyDeleteEntry(modifyList, attrPreferredLanguage, v)));
727 final String attrDepartmentNumber = fessConfig.getLdapAttrDepartmentNumber();
728 OptionalUtil.ofNullable(user.getDepartmentNumber()).filter(StringUtil::isNotBlank)
729 .ifPresent(s -> modifyReplaceEntry(modifyList, attrDepartmentNumber, s))
730 .orElse(() -> getAttributeValueList(result, attrDepartmentNumber).stream()
731 .forEach(v -> modifyDeleteEntry(modifyList, attrDepartmentNumber, v)));
732 final String attrUidNumber = fessConfig.getLdapAttrUidNumber();
733 OptionalUtil.ofNullable(user.getUidNumber()).filter(s -> StringUtil.isNotBlank(s.toString()))
734 .ifPresent(s -> modifyReplaceEntry(modifyList, attrUidNumber, s.toString()))
735 .orElse(() -> getAttributeValueList(result, attrUidNumber).stream()
736 .forEach(v -> modifyDeleteEntry(modifyList, attrUidNumber, v)));
737 final String attrGidNumber = fessConfig.getLdapAttrGidNumber();
738 OptionalUtil.ofNullable(user.getGidNumber()).filter(s -> StringUtil.isNotBlank(s.toString()))
739 .ifPresent(s -> modifyReplaceEntry(modifyList, attrGidNumber, s.toString()))
740 .orElse(() -> getAttributeValueList(result, attrGidNumber).stream()
741 .forEach(v -> modifyDeleteEntry(modifyList, attrGidNumber, v)));
742 final String attrHomeDirectory = fessConfig.getLdapAttrHomeDirectory();
743 OptionalUtil.ofNullable(user.getHomeDirectory()).filter(StringUtil::isNotBlank)
744 .ifPresent(s -> modifyReplaceEntry(modifyList, attrHomeDirectory, s))
745 .orElse(() -> getAttributeValueList(result, attrHomeDirectory).stream()
746 .forEach(v -> modifyDeleteEntry(modifyList, attrHomeDirectory, v)));
747
748 modify(userDN, modifyList, adminEnv);
749 }
750
751 protected void addUserAttributes(final BasicAttributes entry, final User user) {
752 entry.put(new BasicAttribute("cn", user.getName()));
753 entry.put(new BasicAttribute("userPassword", user.getOriginalPassword()));
754
755 OptionalUtil.ofNullable(user.getSurname()).filter(StringUtil::isNotBlank)
756 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrSurname(), s)));
757 OptionalUtil.ofNullable(user.getGivenName()).filter(StringUtil::isNotBlank)
758 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrGivenName(), s)));
759 OptionalUtil.ofNullable(user.getMail()).filter(StringUtil::isNotBlank)
760 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrMail(), s)));
761 OptionalUtil.ofNullable(user.getEmployeeNumber()).filter(StringUtil::isNotBlank)
762 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrEmployeeNumber(), s)));
763 OptionalUtil.ofNullable(user.getTelephoneNumber()).filter(StringUtil::isNotBlank)
764 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrTelephoneNumber(), s)));
765 OptionalUtil.ofNullable(user.getHomePhone()).filter(StringUtil::isNotBlank)
766 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrHomePhone(), s)));
767 OptionalUtil.ofNullable(user.getHomePostalAddress()).filter(StringUtil::isNotBlank)
768 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrHomePostalAddress(), s)));
769 OptionalUtil.ofNullable(user.getLabeledURI()).filter(StringUtil::isNotBlank)
770 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrLabeleduri(), s)));
771 OptionalUtil.ofNullable(user.getRoomNumber()).filter(StringUtil::isNotBlank)
772 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrRoomNumber(), s)));
773 OptionalUtil.ofNullable(user.getDescription()).filter(StringUtil::isNotBlank)
774 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrDescription(), s)));
775 OptionalUtil.ofNullable(user.getTitle()).filter(StringUtil::isNotBlank)
776 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrTitle(), s)));
777 OptionalUtil.ofNullable(user.getPager()).filter(StringUtil::isNotBlank)
778 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrPager(), s)));
779 OptionalUtil.ofNullable(user.getStreet()).filter(StringUtil::isNotBlank)
780 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrStreet(), s)));
781 OptionalUtil.ofNullable(user.getPostalCode()).filter(StringUtil::isNotBlank)
782 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrPostalCode(), s)));
783 OptionalUtil.ofNullable(user.getPhysicalDeliveryOfficeName()).filter(StringUtil::isNotBlank)
784 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrPhysicalDeliveryOfficeName(), s)));
785 OptionalUtil.ofNullable(user.getDestinationIndicator()).filter(StringUtil::isNotBlank)
786 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrDestinationIndicator(), s)));
787 OptionalUtil.ofNullable(user.getInternationaliSDNNumber()).filter(StringUtil::isNotBlank)
788 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrInternationalisdnNumber(), s)));
789 OptionalUtil.ofNullable(user.getState()).filter(StringUtil::isNotBlank)
790 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrState(), s)));
791 OptionalUtil.ofNullable(user.getEmployeeType()).filter(StringUtil::isNotBlank)
792 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrEmployeeType(), s)));
793 OptionalUtil.ofNullable(user.getFacsimileTelephoneNumber()).filter(StringUtil::isNotBlank)
794 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrFacsimileTelephoneNumber(), s)));
795 OptionalUtil.ofNullable(user.getPostOfficeBox()).filter(StringUtil::isNotBlank)
796 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrPostOfficeBox(), s)));
797 OptionalUtil.ofNullable(user.getInitials()).filter(StringUtil::isNotBlank)
798 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrInitials(), s)));
799 OptionalUtil.ofNullable(user.getCarLicense()).filter(StringUtil::isNotBlank)
800 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrCarLicense(), s)));
801 OptionalUtil.ofNullable(user.getMobile()).filter(StringUtil::isNotBlank)
802 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrMobile(), s)));
803 OptionalUtil.ofNullable(user.getPostalAddress()).filter(StringUtil::isNotBlank)
804 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrPostalAddress(), s)));
805 OptionalUtil.ofNullable(user.getCity()).filter(StringUtil::isNotBlank)
806 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrCity(), s)));
807 OptionalUtil.ofNullable(user.getTeletexTerminalIdentifier()).filter(StringUtil::isNotBlank)
808 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrTeletexTerminalIdentifier(), s)));
809 OptionalUtil.ofNullable(user.getX121Address()).filter(StringUtil::isNotBlank)
810 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrX121Address(), s)));
811 OptionalUtil.ofNullable(user.getBusinessCategory()).filter(StringUtil::isNotBlank)
812 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrBusinessCategory(), s)));
813 OptionalUtil.ofNullable(user.getRegisteredAddress()).filter(StringUtil::isNotBlank)
814 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrRegisteredAddress(), s)));
815 OptionalUtil.ofNullable(user.getDisplayName()).filter(StringUtil::isNotBlank)
816 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrDisplayName(), s)));
817 OptionalUtil.ofNullable(user.getPreferredLanguage()).filter(StringUtil::isNotBlank)
818 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrPreferredLanguage(), s)));
819 OptionalUtil.ofNullable(user.getDepartmentNumber()).filter(StringUtil::isNotBlank)
820 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrDepartmentNumber(), s)));
821 OptionalUtil.ofNullable(user.getUidNumber()).filter(s -> StringUtil.isNotBlank(s.toString()))
822 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrUidNumber(), s)));
823 OptionalUtil.ofNullable(user.getGidNumber()).filter(s -> StringUtil.isNotBlank(s.toString()))
824 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrGidNumber(), s)));
825 OptionalUtil.ofNullable(user.getHomeDirectory()).filter(StringUtil::isNotBlank)
826 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrHomeDirectory(), s)));
827 }
828
829 public void validateUserAttributes(final Class<?> type, final Map<String, String> attributes, final Consumer<String> consumer) {
830 if (type == Long.class) {
831
832 final String attrUidNumber = fessConfig.getLdapAttrUidNumber();
833 final String attrGidNumber = fessConfig.getLdapAttrGidNumber();
834
835 Stream.of(attrUidNumber, attrGidNumber)
836 .forEach(attrName -> OptionalUtil.ofNullable(attributes.get(attrName)).filter(StringUtil::isNotBlank).ifPresent(s -> {
837 try {
838 DfTypeUtil.toLong(s);
839 } catch (final NumberFormatException e) {
840 consumer.accept(attrName);
841 }
842 }));
843 } else {
844
845 }
846 }
847
848 public void delete(final User user) {
849 if (!fessConfig.isLdapAdminEnabled(user.getName())) {
850 return;
851 }
852
853 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
854 final String userDN = fessConfig.getLdapAdminUserSecurityPrincipal(user.getName());
855
856 stream(user.getGroupNames()).of(stream -> stream.forEach(name -> {
857 search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(name), null, adminEnv, subResult -> {
858 if (subResult.isEmpty()) {
859 final Group group = new Group();
860 group.setName(name);
861 insert(group);
862 }
863 final List<ModificationItem> modifyList = new ArrayList<>();
864 modifyDeleteEntry(modifyList, "member", userDN);
865 modify(fessConfig.getLdapAdminGroupSecurityPrincipal(name), modifyList, adminEnv);
866 });
867 }));
868 stream(user.getRoleNames()).of(stream -> stream.forEach(name -> {
869 search(fessConfig.getLdapAdminRoleBaseDn(), fessConfig.getLdapAdminRoleFilter(name), null, adminEnv, subResult -> {
870 if (subResult.isEmpty()) {
871 final Role role = new Role();
872 role.setName(name);
873 insert(role);
874 }
875 final List<ModificationItem> modifyList = new ArrayList<>();
876 modifyDeleteEntry(modifyList, "member", userDN);
877 modify(fessConfig.getLdapAdminRoleSecurityPrincipal(name), modifyList, adminEnv);
878 });
879 }));
880
881 search(fessConfig.getLdapAdminUserBaseDn(), fessConfig.getLdapAdminUserFilter(user.getName()), null, adminEnv, result -> {
882 if (!result.isEmpty()) {
883 delete(userDN, adminEnv);
884 } else {
885 logger.info("{} does not exist in LDAP server.", user.getName());
886 }
887 });
888
889 }
890
891 public void insert(final Role role) {
892 if (!fessConfig.isLdapAdminEnabled()) {
893 return;
894 }
895
896 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
897 search(fessConfig.getLdapAdminRoleBaseDn(), fessConfig.getLdapAdminRoleFilter(role.getName()), null, adminEnv, result -> {
898 if (!result.isEmpty()) {
899 logger.info("{} exists in LDAP server.", role.getName());
900 } else {
901 final String entryDN = fessConfig.getLdapAdminRoleSecurityPrincipal(role.getName());
902 final BasicAttributes entry = new BasicAttributes();
903 addRoleAttributes(entry, role);
904 final Attribute oc = fessConfig.getLdapAdminRoleObjectClassAttribute();
905 entry.put(oc);
906 insert(entryDN, entry, adminEnv);
907 }
908 });
909
910 }
911
912 protected void addRoleAttributes(final BasicAttributes entry, final Role user) {
913
914 }
915
916 public void delete(final Role role) {
917 if (!fessConfig.isLdapAdminEnabled()) {
918 return;
919 }
920
921 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
922 search(fessConfig.getLdapAdminRoleBaseDn(), fessConfig.getLdapAdminRoleFilter(role.getName()), null, adminEnv, result -> {
923 if (!result.isEmpty()) {
924 final String entryDN = fessConfig.getLdapAdminRoleSecurityPrincipal(role.getName());
925 delete(entryDN, adminEnv);
926 } else {
927 logger.info("{} does not exist in LDAP server.", role.getName());
928 }
929 });
930
931 }
932
933 public void apply(final Group group) {
934 if (!fessConfig.isLdapAdminEnabled()) {
935 return;
936 }
937
938 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
939 search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(group.getName()), null, adminEnv, result -> {
940 if (!result.isEmpty()) {
941 setAttributeValue(result, fessConfig.getLdapAttrGidNumber(), o -> group.setGidNumber(DfTypeUtil.toLong(o)));
942 }
943 });
944 }
945
946 public void insert(final Group group) {
947 if (!fessConfig.isLdapAdminEnabled()) {
948 return;
949 }
950
951 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
952 final String entryDN = fessConfig.getLdapAdminGroupSecurityPrincipal(group.getName());
953 search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(group.getName()), null, adminEnv, result -> {
954 if (!result.isEmpty()) {
955 logger.info("{} exists in LDAP server.", group.getName());
956 modifyGroupAttributes(group, adminEnv, entryDN, result);
957 } else {
958 final BasicAttributes entry = new BasicAttributes();
959 addGroupAttributes(entry, group);
960 final Attribute oc = fessConfig.getLdapAdminGroupObjectClassAttribute();
961 entry.put(oc);
962 insert(entryDN, entry, adminEnv);
963 }
964 });
965 }
966
967 protected void modifyGroupAttributes(final Group group, final Supplier<Hashtable<String, String>> adminEnv, final String entryDN,
968 final List<SearchResult> result) {
969 final List<ModificationItem> modifyList = new ArrayList<>();
970
971 final String attrGidNumber = fessConfig.getLdapAttrGidNumber();
972 OptionalUtil.ofNullable(group.getGidNumber()).filter(s -> StringUtil.isNotBlank(s.toString()))
973 .ifPresent(s -> modifyReplaceEntry(modifyList, attrGidNumber, s.toString()))
974 .orElse(() -> getAttributeValueList(result, attrGidNumber).stream()
975 .forEach(v -> modifyDeleteEntry(modifyList, attrGidNumber, v)));
976
977 modify(entryDN, modifyList, adminEnv);
978 }
979
980 protected void addGroupAttributes(final BasicAttributes entry, final Group group) {
981 OptionalUtil.ofNullable(group.getGidNumber()).filter(s -> StringUtil.isNotBlank(s.toString()))
982 .ifPresent(s -> entry.put(new BasicAttribute(fessConfig.getLdapAttrGidNumber(), s)));
983 }
984
985 public void validateGroupAttributes(final Class<?> type, final Map<String, String> attributes, final Consumer<String> consumer) {
986 if (type == Long.class) {
987
988 final String attrGidNumber = fessConfig.getLdapAttrGidNumber();
989
990 Stream.of(attrGidNumber)
991 .forEach(attrName -> OptionalUtil.ofNullable(attributes.get(attrName)).filter(StringUtil::isNotBlank).ifPresent(s -> {
992 try {
993 DfTypeUtil.toLong(s);
994 } catch (final NumberFormatException e) {
995 consumer.accept(attrName);
996 }
997 }));
998 } else {
999
1000 }
1001 }
1002
1003 public void delete(final Group group) {
1004 if (!fessConfig.isLdapAdminEnabled()) {
1005 return;
1006 }
1007
1008 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
1009 search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(group.getName()), null, adminEnv, result -> {
1010 if (!result.isEmpty()) {
1011 final String entryDN = fessConfig.getLdapAdminGroupSecurityPrincipal(group.getName());
1012 delete(entryDN, adminEnv);
1013 } else {
1014 logger.info("{} does not exist in LDAP server.", group.getName());
1015 }
1016 });
1017 }
1018
1019 public boolean changePassword(final String username, final String password) {
1020 if (!fessConfig.isLdapAdminEnabled(username)) {
1021 return false;
1022 }
1023
1024 final Supplier<Hashtable<String, String>> adminEnv = this::createAdminEnv;
1025 final String userDN = fessConfig.getLdapAdminUserSecurityPrincipal(username);
1026 search(fessConfig.getLdapAdminUserBaseDn(), fessConfig.getLdapAdminUserFilter(username), null, adminEnv, result -> {
1027 if (result.isEmpty()) {
1028 throw new LdapOperationException("User is not found: " + username);
1029 }
1030 final List<ModificationItem> modifyList = new ArrayList<>();
1031 modifyReplaceEntry(modifyList, "userPassword", password);
1032 modify(userDN, modifyList, adminEnv);
1033 });
1034 return true;
1035 }
1036
1037 protected void insert(final String entryDN, final Attributes entry, final Supplier<Hashtable<String, String>> envSupplier) {
1038 try (DirContextHolder holder = getDirContext(envSupplier)) {
1039 logger.debug("Inserting {}", entryDN);
1040 holder.get().createSubcontext(entryDN, entry);
1041 } catch (final NamingException e) {
1042 throw new LdapOperationException("Failed to add " + entryDN, e);
1043 }
1044 }
1045
1046 protected void delete(final String entryDN, final Supplier<Hashtable<String, String>> envSupplier) {
1047 try (DirContextHolder holder = getDirContext(envSupplier)) {
1048 logger.debug("Deleting {}", entryDN);
1049 holder.get().destroySubcontext(entryDN);
1050 } catch (final NamingException e) {
1051 throw new LdapOperationException("Failed to delete " + entryDN, e);
1052 }
1053 }
1054
1055 protected void search(final String baseDn, final String filter, final String[] returningAttrs,
1056 final Supplier<Hashtable<String, String>> envSupplier, final SearchConsumer consumer) {
1057 try (DirContextHolder holder = getDirContext(envSupplier)) {
1058 final SearchControls controls = new SearchControls();
1059 controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
1060 if (returningAttrs != null) {
1061 controls.setReturningAttributes(returningAttrs);
1062 }
1063
1064 final long startTime = System.currentTimeMillis();
1065 final List<SearchResult> list = Collections.list(holder.get().search(baseDn, filter, controls));
1066 if (logger.isDebugEnabled()) {
1067 logger.debug("LDAP search[{}ms]: {} - {}", System.currentTimeMillis() - startTime, baseDn, filter);
1068 }
1069 consumer.accept(list);
1070 } catch (final NamingException e) {
1071 throw new LdapOperationException("Failed to search " + baseDn + " with " + filter, e);
1072 }
1073 }
1074
1075 protected void modifyAddEntry(final List<ModificationItem> modifyList, final String name, final String value) {
1076 final Attribute attr = new BasicAttribute(name, value);
1077 final ModificationItem mod = new ModificationItem(DirContext.ADD_ATTRIBUTE, attr);
1078 modifyList.add(mod);
1079 }
1080
1081 protected void modifyReplaceEntry(final List<ModificationItem> modifyList, final String name, final String value) {
1082 final Attribute attr = new BasicAttribute(name, value);
1083 final ModificationItem mod = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr);
1084 modifyList.add(mod);
1085 }
1086
1087 protected void modifyDeleteEntry(final List<ModificationItem> modifyList, final String name, final Object value) {
1088 final Attribute attr = new BasicAttribute(name, value);
1089 final ModificationItem mod = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attr);
1090 modifyList.add(mod);
1091 }
1092
1093 protected void modify(final String dn, final List<ModificationItem> modifyList, final Supplier<Hashtable<String, String>> envSupplier) {
1094 if (modifyList.isEmpty()) {
1095 return;
1096 }
1097 try (DirContextHolder holder = getDirContext(envSupplier)) {
1098 holder.get().modifyAttributes(dn, modifyList.toArray(new ModificationItem[modifyList.size()]));
1099 } catch (final NamingException e) {
1100 throw new LdapOperationException("Failed to search " + dn, e);
1101 }
1102 }
1103
1104 interface SearchConsumer {
1105 void accept(List<SearchResult> t) throws NamingException;
1106 }
1107
1108 protected DirContextHolder getDirContext(final Supplier<Hashtable<String, String>> envSupplier) {
1109 DirContextHolder holder = contextLocal.get();
1110 if (holder != null) {
1111 holder.inc();
1112 return holder;
1113 }
1114 final Hashtable<String, String> env = envSupplier.get();
1115 try {
1116 holder = new DirContextHolder(new InitialDirContext(env));
1117 contextLocal.set(holder);
1118 return holder;
1119 } catch (final NamingException e) {
1120 throw new LdapOperationException("Failed to create DirContext.", e);
1121 }
1122 }
1123
1124 protected class DirContextHolder implements AutoCloseable {
1125 private final DirContext context;
1126
1127 private int counter = 1;
1128
1129 protected DirContextHolder(final DirContext context) {
1130 this.context = context;
1131 }
1132
1133 public DirContext get() {
1134 return context;
1135 }
1136
1137 public void inc() {
1138 counter++;
1139 }
1140
1141 @Override
1142 public void close() {
1143 if (counter > 1) {
1144 counter--;
1145 } else {
1146 contextLocal.remove();
1147 if (context != null) {
1148 try {
1149 context.close();
1150 } catch (final NamingException e) {
1151
1152 }
1153 }
1154 }
1155 }
1156 }
1157 }