1 package edu.internet2.middleware.grouper.pspng;
2
3 import java.io.*;
4 import java.util.*;
5 import java.util.regex.Matcher;
6 import java.util.regex.Pattern;
7
8 import javax.net.ssl.SSLSocket;
9 import javax.net.ssl.SSLSocketFactory;
10
11 import com.unboundid.ldap.sdk.DN;
12 import edu.internet2.middleware.morphString.Morph;
13 import org.apache.commons.lang.StringUtils;
14 import org.ldaptive.*;
15 import org.ldaptive.ad.handler.RangeEntryHandler;
16 import org.ldaptive.control.util.PagedResultsClient;
17 import org.ldaptive.handler.HandlerResult;
18 import org.ldaptive.handler.SearchEntryHandler;
19 import org.ldaptive.pool.BlockingConnectionPool;
20 import org.ldaptive.pool.PoolConfig;
21 import org.ldaptive.pool.PoolException;
22 import org.ldaptive.pool.SearchValidator;
23 import org.ldaptive.props.BindConnectionInitializerPropertySource;
24 import org.ldaptive.props.ConnectionConfigPropertySource;
25 import org.ldaptive.props.DefaultConnectionFactoryPropertySource;
26 import org.ldaptive.props.PoolConfigPropertySource;
27 import org.ldaptive.props.SearchRequestPropertySource;
28 import org.ldaptive.sasl.GssApiConfig;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 import edu.internet2.middleware.grouper.app.loader.GrouperLoaderConfig;
33 import edu.internet2.middleware.grouper.util.GrouperUtil;
34 import static edu.internet2.middleware.grouper.pspng.PspUtils.*;
35
36
37
38
39
40
41
42 public class LdapSystem {
43 private static final Logger LOG = LoggerFactory.getLogger(LdapSystem.class);
44
45
46
47 public static final String ENCRYPTABLE_LDAPTIVE_PROPERTIES[]
48 = new String[]{"org.ldaptive.bindCredential"};
49
50 public final String ldapSystemName;
51 protected Properties _ldaptiveProperties = new Properties();
52
53 private final boolean isActiveDirectory;
54 private BlockingConnectionPool ldapPool;
55
56 protected boolean searchResultPagingEnabled_defaultValue = true;
57 protected int searchResultPagingSize_default_value = 100;
58
59
60 public static boolean attributeHasNoValues(final LdapAttribute attribute) {
61 if ( attribute == null ) {
62 return true;
63 }
64
65 Collection<String> values = attribute.getStringValues();
66
67 return values.size() == 0 || values.iterator().next().length() == 0;
68 }
69
70
71 public LdapSystem(String ldapSystemName, boolean isActiveDirectory) {
72 this.ldapSystemName = ldapSystemName;
73 this.isActiveDirectory = isActiveDirectory;
74 getLdaptiveProperties();
75 }
76
77
78 private BlockingConnectionPool buildLdapConnectionPool() throws PspException {
79 BlockingConnectionPool result;
80
81 LOG.info("{}: Creating LDAP Pool", ldapSystemName);
82 Properties ldaptiveProperties = getLdaptiveProperties();
83
84 Properties loggableProperties = new Properties();
85 loggableProperties.putAll(ldaptiveProperties);
86
87 for ( String propertyToMask : ENCRYPTABLE_LDAPTIVE_PROPERTIES )
88 {
89 if ( loggableProperties.containsKey(propertyToMask) )
90 {
91 loggableProperties.put(propertyToMask, "**masked**");
92 }
93 }
94
95 LOG.info("Setting up LDAP Connection with properties: {}", loggableProperties);
96
97
98 ConnectionConfig connConfig = new ConnectionConfig();
99 ConnectionConfigPropertySource ccpSource = new ConnectionConfigPropertySource(connConfig, ldaptiveProperties);
100 ccpSource.initialize();
101
102
103
104
105
106
107 BindConnectionInitializer binder = new BindConnectionInitializer();
108
109 BindConnectionInitializerPropertySource bcip = new BindConnectionInitializerPropertySource(binder, ldaptiveProperties);
110 bcip.initialize();
111
112
113
114
115 GssApiConfig gssApiConfig = null;
116 String val = (String) ldaptiveProperties.get("org.ldaptive.saslRealm");
117 if (!StringUtils.isBlank(val)) {
118 LOG.info("Processing saslRealm");
119 if ( gssApiConfig == null )
120 gssApiConfig = new GssApiConfig();
121 gssApiConfig.setRealm(val);
122
123 }
124
125 val = (String) ldaptiveProperties.get("org.ldaptive.saslAuthorizationId");
126 if (!StringUtils.isBlank(val)) {
127 LOG.info("Processing saslAuthorizationId");
128 if ( gssApiConfig == null )
129 gssApiConfig = new GssApiConfig();
130 gssApiConfig.setAuthorizationId(val);
131 }
132
133
134 if ( gssApiConfig != null ) {
135 LOG.info("Setting gssApiConfig");
136 binder.setBindSaslConfig(gssApiConfig);
137 }
138
139 DefaultConnectionFactory connectionFactory = new DefaultConnectionFactory();
140 DefaultConnectionFactoryPropertySource dcfSource = new DefaultConnectionFactoryPropertySource(connectionFactory, ldaptiveProperties);
141 dcfSource.initialize();
142
143
144 Connection conn = connectionFactory.getConnection();
145 performTestLdapRead(conn);
146
147
148
149
150 PoolConfig ldapPoolConfig = new PoolConfig();
151 PoolConfigPropertySource pcps = new PoolConfigPropertySource(ldapPoolConfig, ldaptiveProperties);
152 pcps.initialize();
153
154
155 if ( !ldapPoolConfig.isValidateOnCheckIn() &&
156 !ldapPoolConfig.isValidateOnCheckOut() &&
157 !ldapPoolConfig.isValidatePeriodically() ) {
158 LOG.debug("{}: Using default onCheckOut ldap-connection validation", ldapSystemName);
159 ldapPoolConfig.setValidateOnCheckOut(true);
160 }
161
162 result = new BlockingConnectionPool(ldapPoolConfig, connectionFactory);
163 result.setValidator(new SearchValidator());
164 result.initialize();
165
166
167
168 try {
169 conn = result.getConnection();
170 performTestLdapRead(conn);
171 } catch (LdapException e) {
172 LOG.error("Problem while testing ldap pool", e);
173 throw new PspException("Problem testing ldap pool: %s", e.getMessage());
174 }
175
176
177 return result;
178 }
179
180 public void log(LdapEntry ldapEntry, String ldapEntryDescriptionFormat, Object... ldapEntryDescriptionArgs) {
181 String ldapEntryDescription;
182 if (LOG.isInfoEnabled() || LOG.isDebugEnabled()) {
183 ldapEntryDescription = String.format(ldapEntryDescriptionFormat, ldapEntryDescriptionArgs);
184 } else {
185 return;
186 }
187
188
189 if ( LOG.isInfoEnabled() ) {
190 StringBuilder sb = new StringBuilder();
191 sb.append(String.format("dn=%s|", ldapEntry == null ? "null" : ldapEntry.getDn()));
192 if (ldapEntry != null && ldapEntry.getAttributes() != null) {
193 for (LdapAttribute attribute : ldapEntry.getAttributes()) {
194 sb.append(String.format("%d %s values|", attribute.size(), attribute.getName()));
195 }
196 }
197 LOG.info("{}: {} Entry Summary: {}", ldapSystemName, ldapEntryDescription, sb.toString());
198 }
199
200 LOG.debug("{}: {} Entry Details: {}", ldapSystemName, ldapEntryDescription, ldapEntry);
201 }
202
203 public void log( ModifyRequest modifyRequest, String descriptionFormat, Object... descriptionArgs) {
204 String ldapEntryDescription;
205 if (LOG.isInfoEnabled() || LOG.isDebugEnabled()) {
206 ldapEntryDescription = String.format(descriptionFormat, descriptionArgs);
207 } else {
208 return;
209 }
210
211
212 if ( LOG.isInfoEnabled() ) {
213 StringBuilder sb = new StringBuilder();
214 sb.append(String.format("dn=%s|", modifyRequest == null ? "null" : modifyRequest.getDn()));
215
216 if (modifyRequest != null && modifyRequest.getAttributeModifications() != null) {
217 for (AttributeModification mod : modifyRequest.getAttributeModifications()) {
218 sb.append(String.format("%s %d %s values|",
219 mod.getAttributeModificationType(), mod.getAttribute().size(), mod.getAttribute().getName()));
220 }
221 }
222 LOG.info("{}: {} Mod Summary: {}", ldapSystemName, ldapEntryDescription, sb.toString());
223 }
224
225 LOG.debug("{}: {} Mod Details: {}", ldapSystemName, ldapEntryDescription, modifyRequest);
226 }
227
228
229 protected void performTestLdapRead(Connection conn) throws PspException {
230 LOG.info("{}: Performing test read of directory root", ldapSystemName);
231 SearchExecutor searchExecutor = new SearchExecutor();
232 SearchRequestPropertySource srSource = new SearchRequestPropertySource(searchExecutor, getLdaptiveProperties());
233 srSource.initialize();
234
235 String baseDn = GrouperLoaderConfig.retrieveConfig().propertyValueString("ldap." + ldapSystemName + ".uiTestSearchDn", "");
236 String filter = GrouperLoaderConfig.retrieveConfig().propertyValueString("ldap." + ldapSystemName + ".uiTestFilter", "objectclass=*");
237
238 filter = StringUtils.trim(filter);
239 if (filter.startsWith("(") && filter.endsWith(")")) {
240 filter = filter.substring(1, filter.length()-1);
241 }
242
243 SearchRequest read = new SearchRequest(baseDn, filter);
244 read.setSearchScope(SearchScope.OBJECT);
245
246
247 if ( isActiveDirectory() )
248 read.setSearchEntryHandlers(new RangeEntryHandler());
249
250 try {
251 conn.open();
252 SearchOperation searchOp = new SearchOperation(conn);
253
254 Response<SearchResult> response = searchOp.execute(read);
255 SearchResult searchResult = response.getResult();
256
257 LdapEntry searchResultEntry = searchResult.getEntry();
258 log(searchResultEntry, "Ldap test success");
259 }
260 catch (LdapException e) {
261 LOG.error("Ldap problem",e);
262 throw new PspException("Problem testing ldap connection: %s", e.getMessage());
263 }
264 finally {
265 conn.close();
266 }
267 }
268
269
270
271 public BlockingConnectionPool getLdapPool() throws PspException {
272 if ( ldapPool != null )
273 return ldapPool;
274
275
276 synchronized(this) {
277
278 if ( ldapPool != null )
279 return ldapPool;
280
281 ldapPool = buildLdapConnectionPool();
282 }
283
284 return ldapPool;
285 }
286
287
288
289 public boolean isActiveDirectory() {
290 return isActiveDirectory;
291 }
292
293
294
295 public Properties getLdaptiveProperties() {
296 if ( _ldaptiveProperties.size() == 0 ) {
297 String ldapPropertyPrefix = "ldap." + ldapSystemName.toLowerCase() + ".";
298
299 for (String propName : GrouperLoaderConfig.retrieveConfig().propertyNames()) {
300 if ( propName.toLowerCase().startsWith(ldapPropertyPrefix) ) {
301 String propValue = GrouperLoaderConfig.retrieveConfig().propertyValueString(propName, "");
302
303
304 String propNameTail = propName.substring(ldapPropertyPrefix.length());
305 _ldaptiveProperties.put("org.ldaptive." + propNameTail, propValue);
306
307
308
309 if ( propNameTail.equalsIgnoreCase("url") ) {
310 LOG.info("Setting org.ldaptive.ldapUrl");
311 _ldaptiveProperties.put("org.ldaptive.ldapUrl", propValue);
312 }
313
314 if ( propNameTail.equalsIgnoreCase("tls") ) {
315 LOG.info("Setting org.ldaptive.useStartTLS");
316 _ldaptiveProperties.put("org.ldaptive.useStartTLS", propValue);
317 }
318
319 if ( propNameTail.equalsIgnoreCase("user") )
320 {
321 LOG.info("Setting org.ldaptive.bindDn");
322 _ldaptiveProperties.put("org.ldaptive.bindDn", propValue);
323 }
324
325 if ( propNameTail.equalsIgnoreCase("pass") )
326 {
327 LOG.info("Setting org.ldaptive.bindCredential");
328 _ldaptiveProperties.put("org.ldaptive.bindCredential", propValue);
329 }
330 }
331 }
332 }
333
334
335 for (String encryptablePropertyKey : ENCRYPTABLE_LDAPTIVE_PROPERTIES) {
336 String value = _ldaptiveProperties.getProperty(encryptablePropertyKey);
337 value = Morph.decryptIfFile(value);
338 _ldaptiveProperties.put(encryptablePropertyKey, value);
339 }
340 return _ldaptiveProperties;
341 }
342
343
344
345 public int getSearchResultPagingSize() {
346 Object searchResultPagingSize = getLdaptiveProperties().get("org.ldaptive.searchResultPagingSize");
347
348 return GrouperUtil.intValue(searchResultPagingSize, searchResultPagingSize_default_value);
349 }
350
351
352
353 public boolean isSearchResultPagingEnabled() {
354 Object searchResultPagingEnabled = getLdaptiveProperties().get("org.ldaptive.searchResultPagingEnabled");
355
356 return GrouperUtil.booleanValue(searchResultPagingEnabled, searchResultPagingEnabled_defaultValue);
357 }
358
359
360
361 protected Connection getLdapConnection() throws PspException {
362 BlockingConnectionPool pool = getLdapPool();
363 try {
364 Connection conn = pool.getConnection();
365 return conn;
366 } catch (PoolException e) {
367 LOG.error("LDAP Pool Exception", e);
368 throw new PspException("Problem connecting to ldap server %s: %s",pool, e.getMessage());
369 }
370 }
371
372
373
374
375
376
377
378 public SearchExecutor getSearchExecutor() {
379 SearchExecutor searchExecutor = new SearchExecutor();
380 SearchRequestPropertySource srSource = new SearchRequestPropertySource(searchExecutor, getLdaptiveProperties());
381 srSource.initialize();
382
383 return searchExecutor;
384 }
385
386
387
388 protected void performLdapAdd(LdapEntry entryToAdd) throws PspException {
389 log(entryToAdd, "Creating LDAP object");
390
391 Connection conn = getLdapConnection();
392 try {
393
394 conn.open();
395 conn.getProviderConnection().add(new AddRequest(entryToAdd.getDn(), entryToAdd.getAttributes()));
396 } catch (LdapException e) {
397 if ( e.getResultCode() == ResultCode.ENTRY_ALREADY_EXISTS ) {
398 LOG.warn("{}: Skipping LDAP ADD because object already existed: {}", ldapSystemName, entryToAdd.getDn());
399 } else {
400 LOG.error("{}: Problem while creating new ldap object: {}",
401 new Object[] {ldapSystemName, entryToAdd, e});
402
403 throw new PspException("LDAP problem creating object: %s", e.getMessage());
404 }
405 }
406 finally {
407 conn.close();
408 }
409
410 }
411
412
413
414 protected void performLdapDelete(String dnToDelete) throws PspException {
415 LOG.info("{}: Deleting LDAP object: {}", ldapSystemName, dnToDelete);
416
417 Connection conn = getLdapConnection();
418 try {
419
420 conn.open();
421 conn.getProviderConnection().delete(new DeleteRequest(dnToDelete));
422 } catch (LdapException e) {
423 LOG.error("Problem while deleting ldap object: {}", dnToDelete, e);
424 throw new PspException("LDAP problem deleting object: %s", e.getMessage());
425 }
426 finally {
427 conn.close();
428 }
429
430 }
431
432 public void performLdapModify(ModifyRequest mod, boolean valuesAreCaseSensitive) throws PspException {
433 performLdapModify(mod, valuesAreCaseSensitive,true);
434 }
435
436
437
438
439
440
441
442
443
444
445 public void performLdapModify(ModifyRequest mod, boolean valuesAreCaseSensitive, boolean retryIfFails) throws PspException {
446 log(mod, "Performing ldap mod (%s retry)", retryIfFails ? "with" : "without");
447
448 Connection conn = getLdapConnection();
449 try {
450 conn.open();
451 conn.getProviderConnection().modify(mod);
452 } catch (LdapException e) {
453
454
455 if ( !retryIfFails ) {
456 throw new PspException("%s: Unrecoverable problem modifying ldap object: %s %s",
457 ldapSystemName, mod, e.getMessage());
458 }
459
460
461 LOG.warn("{}: Problem while modifying ldap system based on grouper expectations. Starting to perform adaptive modifications based on data already on server: {}: {}",
462 new Object[]{ldapSystemName, mod.getDn(), e.getResultCode()});
463
464
465
466
467
468
469
470 if ( mod.getAttributeModifications().length == 1 &&
471 mod.getAttributeModifications()[0].getAttribute().getStringValues().size() == 1 ) {
472 AttributeModification modification = mod.getAttributeModifications()[0];
473
474 boolean attributeMatches = performLdapComparison(mod.getDn(), modification.getAttribute());
475
476 if ( attributeMatches && modification.getAttributeModificationType() == AttributeModificationType.ADD ) {
477 LOG.info("{}: Change not necessary: System already had attribute value", ldapSystemName);
478 return;
479 }
480 else if ( !attributeMatches && modification.getAttributeModificationType() == AttributeModificationType.REMOVE ) {
481 LOG.info("{}: Change not necessary: System already had attribute value removed", ldapSystemName);
482 return;
483 }
484 else {
485 LOG.error("{}: Single-attribute-value Ldap mod-{} failed when Ldap server {} already have {}={}. Mod that failed: {}",
486 ldapSystemName, modification.getAttributeModificationType().toString().toLowerCase(),
487 attributeMatches ? "does" : "does not",
488 modification.getAttribute().getName(), modification.getAttribute().getStringValue(),
489 mod,
490 e);
491 throw new PspException("LDAP Modification Failed");
492 }
493 }
494
495
496
497
498
499
500 Set<String> attributeNames = new HashSet<>();
501 for ( AttributeModification attributeMod : mod.getAttributeModifications()) {
502 attributeNames.add(attributeMod.getAttribute().getName());
503 }
504
505
506 LOG.info("{}: Modification retrying... reading object to know what needs to change: {}",
507 ldapSystemName, mod.getDn());
508
509 LdapObject currentLdapObject = performLdapRead(mod.getDn(), attributeNames);
510 log(currentLdapObject.ldapEntry, "Data already on ldap server");
511
512
513 for ( AttributeModification attributeMod : mod.getAttributeModifications()) {
514 String attributeName = attributeMod.getAttribute().getName();
515
516 LOG.info("{}: Summary: Comparing modification of {} to what is already in LDAP: {}/{} Values",
517 ldapSystemName,
518 attributeName,
519 attributeMod.getAttributeModificationType(),
520 attributeMod.getAttribute().size());
521 LOG.debug("{}: Details: Comparing modification of {} to what is already in LDAP: {}/{}",
522 ldapSystemName,
523 attributeName,
524 attributeMod.getAttributeModificationType(),
525 attributeMod.getAttribute());
526
527 Collection<String> currentValues = currentLdapObject.getStringValues(attributeName);
528 Collection<String> modifyValues = attributeMod.getAttribute().getStringValues();
529
530 LOG.info("{}: Comparing Attribute {}. #Values on server already {}. #Values in mod/{}: {}",
531 ldapSystemName, attributeName, currentValues.size(), attributeMod.getAttributeModificationType(), modifyValues.size());
532
533 switch (attributeMod.getAttributeModificationType()) {
534 case ADD:
535
536
537
538 Set<String> valuesNotAlreadyOnServer =
539 subtractStringCollections(valuesAreCaseSensitive, modifyValues, currentValues);
540
541 LOG.debug("{}: {}: Values on server: {}",
542 ldapSystemName, attributeName, currentValues);
543 LOG.debug("{}: {}: Modify/Add values: {}",
544 ldapSystemName, attributeName, modifyValues);
545
546 LOG.info("{}: {}: Need to add {} values",
547 ldapSystemName, attributeName, valuesNotAlreadyOnServer.size());
548
549 for ( String valueToChange : valuesNotAlreadyOnServer ) {
550 performLdapModify( new ModifyRequest( mod.getDn(),
551 new AttributeModification(AttributeModificationType.ADD,
552 new LdapAttribute(attributeName, valueToChange))),
553 valuesAreCaseSensitive,false);
554 }
555 break;
556
557 case REMOVE:
558
559 if ( modifyValues.size() == 0 ) {
560 modifyValues.addAll(currentValues);
561 }
562
563
564
565
566 Set<String> valuesStillOnServer
567 = intersectStringCollections(valuesAreCaseSensitive, modifyValues, currentValues);
568 LOG.debug("{}: {}: Values on server: {}",
569 ldapSystemName, attributeName, currentValues);
570 LOG.debug("{}: {}: Modify/Delete values: {}",
571 ldapSystemName, attributeName, modifyValues);
572
573 LOG.info("{}: {}: {} values need to be REMOVEd",
574 ldapSystemName, attributeName, valuesStillOnServer.size());
575
576 for (String valueToChange : valuesStillOnServer) {
577 performLdapModify(new ModifyRequest(mod.getDn(),
578 new AttributeModification(AttributeModificationType.REMOVE,
579 new LdapAttribute(attributeName, valueToChange))),
580 valuesAreCaseSensitive,false);
581 }
582 break;
583
584 case REPLACE:
585
586
587
588 LOG.debug("{}: {}: Values on server: {}",
589 ldapSystemName, attributeName, currentValues);
590 LOG.debug("{}: {}: Modify/Replace values: {}",
591 ldapSystemName, attributeName, modifyValues);
592
593 Set<String> extraValuesOnServer =
594 subtractStringCollections(valuesAreCaseSensitive, currentValues, modifyValues);
595 LOG.info("{}: REPLACE: {}: {} values still need to be REMOVEd",
596 ldapSystemName, attributeNames, extraValuesOnServer.size());
597
598 for (String valueToChange : extraValuesOnServer) {
599 performLdapModify(new ModifyRequest(mod.getDn(),
600 new AttributeModification(AttributeModificationType.REMOVE,
601 new LdapAttribute(attributeName, valueToChange))),
602 valuesAreCaseSensitive,false);
603 }
604
605 Set<String> missingValuesOnServer =
606 subtractStringCollections(valuesAreCaseSensitive, modifyValues, currentValues);
607
608 LOG.info("{}: REPLACE: {}: {} values need to be ADDed",
609 ldapSystemName, attributeName, missingValuesOnServer.size());
610
611 for ( String valueToChange : missingValuesOnServer ) {
612 performLdapModify( new ModifyRequest( mod.getDn(),
613 new AttributeModification(AttributeModificationType.ADD,
614 new LdapAttribute(attributeName, valueToChange))),
615 valuesAreCaseSensitive, false);
616 }
617 }
618 }
619 }
620 finally {
621 conn.close();
622 }
623 }
624
625 private boolean performLdapComparison(String dn, LdapAttribute attribute) throws PspException {
626 LOG.info("{}: Performaing Ldap comparison operation: {} on {}",
627 new Object[]{ldapSystemName, attribute, LdapObject.getDnSummary(dn,2)});
628
629 Connection conn = getLdapConnection();
630 try {
631 try {
632 conn.open();
633 CompareOperation compare = new CompareOperation(conn);
634
635 boolean result = compare.execute(new CompareRequest(dn, attribute)).getResult();
636 return result;
637
638 } catch (LdapException ldapException) {
639 ResultCode resultCode = ldapException.getResultCode();
640
641
642 if (resultCode == ResultCode.NO_SUCH_OBJECT || resultCode == ResultCode.NO_SUCH_ATTRIBUTE) {
643 return false;
644 } else {
645 LOG.error("{}: Error performing compare operation: {}",
646 new Object[]{ldapSystemName, attribute, ldapException});
647
648 throw new PspException("LDAP problem performing ldap comparison: %s", ldapException.getMessage());
649 }
650 }
651 }
652 finally {
653 conn.close();
654 }
655
656 }
657
658
659 void performLdapModifyDn(ModifyDnRequest mod) throws PspException {
660 LOG.info("{}: Performing Ldap mod-dn operation: {}", ldapSystemName, mod);
661
662 Connection conn = getLdapConnection();
663 try {
664 conn.open();
665 conn.getProviderConnection().modifyDn(mod);
666 } catch (LdapException e) {
667 LOG.error("Problem while modifying dn of ldap object: {}", mod, e);
668 throw new PspException("LDAP problem modifying dn of ldap object: %s", e.getMessage());
669 }
670 finally {
671 conn.close();
672 }
673 }
674
675
676
677
678 protected LdapObject performLdapRead(DN dn, String... attributes) throws PspException {
679 return performLdapRead(dn.toMinimallyEncodedString(), attributes);
680 }
681
682 protected LdapObject performLdapRead(String dn, Collection<String> attributes) throws PspException {
683 return performLdapRead(dn, attributes.toArray(new String[0]));
684 }
685
686 protected LdapObject performLdapRead(String dn, String... attributes) throws PspException {
687 LOG.debug("Doing ldap read: {} attributes {}", dn, Arrays.toString(attributes));
688
689 Connection conn = getLdapConnection();
690 try {
691 conn.open();
692
693 SearchRequest read = new SearchRequest(dn, "objectclass=*");
694 read.setSearchScope(SearchScope.OBJECT);
695 read.setReturnAttributes(attributes);
696
697
698 if ( isActiveDirectory() ) {
699 LOG.info("Active Directory: Searching with Ldap RangeEntryHandler");
700 read.setSearchEntryHandlers(new RangeEntryHandler());
701 }
702
703 SearchOperation searchOp = new SearchOperation(conn);
704
705 Response<SearchResult> response = searchOp.execute(read);
706 SearchResult searchResult = response.getResult();
707
708 LdapEntry result = searchResult.getEntry();
709
710 if ( result == null ) {
711 LOG.debug("{}: Object does not exist: {}", ldapSystemName, dn);
712 return null;
713 } else {
714 LOG.debug("{}: Object does exist: {}", ldapSystemName, dn);
715 return new LdapObject(result, attributes);
716 }
717 }
718 catch (LdapException e) {
719 if ( e.getResultCode() == ResultCode.NO_SUCH_OBJECT ) {
720 LOG.warn("{}: Ldap object does not exist: '{}'", ldapSystemName, dn);
721 return null;
722 }
723
724 LOG.error("Problem during ldap read {}", dn, e);
725 throw new PspException("Problem during LDAP read: %s", e.getMessage());
726 }
727 finally {
728 if ( conn != null )
729 conn.close();
730 }
731 }
732
733
734
735
736
737
738
739
740 protected void performLdapSearchRequest(int approximateNumResultsExpected, SearchRequest request, SearchEntryHandler callback) throws PspException {
741 LOG.debug("Doing ldap search: {} / {} / {}",
742 new Object[] {request.getSearchFilter(), request.getBaseDn(), Arrays.toString(request.getReturnAttributes())});
743 List<LdapObject> result = new ArrayList<LdapObject>();
744
745 Connection conn = getLdapConnection();
746 try {
747 conn.open();
748
749
750 if ( isActiveDirectory() ) {
751 LOG.debug("Using attribute-value paging");
752 request.setSearchEntryHandlers(
753 new RangeEntryHandler(),
754 new LdapSearchProgressHandler(approximateNumResultsExpected, LOG, "Performing ldap search"),
755 callback);
756 }
757 else {
758 LOG.debug("Not using attribute-value paging");
759 request.setSearchEntryHandlers(
760 new LdapSearchProgressHandler(approximateNumResultsExpected, LOG, "Performing ldap search"),
761 callback);
762 }
763
764
765 if ( isSearchResultPagingEnabled() ) {
766 PagedResultsClient client = new PagedResultsClient(conn, getSearchResultPagingSize());
767 LOG.debug("Using ldap search-result paging");
768 client.executeToCompletion(request);
769 }
770 else {
771 LOG.debug("Not using ldap search-result paging");
772 SearchOperation searchOp = new SearchOperation(conn);
773 searchOp.execute(request);
774 }
775
776 }
777 catch (LdapException e) {
778 if ( e.getResultCode() == ResultCode.NO_SUCH_OBJECT ) {
779 LOG.warn("Search base does not exist: {} (No such object ldap error)", request.getBaseDn());
780 return;
781 }
782
783 LOG.error("Problem during ldap search {}", request, e);
784 throw new PspException("LDAP problem while searching: " + e.getMessage());
785 }
786 catch (RuntimeException e) {
787 LOG.error("Runtime problem during ldap search {}", request, e);
788 throw e;
789 }
790 finally {
791 if ( conn != null )
792 conn.close();
793 }
794 }
795
796
797
798 public List<LdapObject> performLdapSearchRequest(int approximateNumResultsExpected, String searchBaseDn, SearchScope scope, Collection<String> attributesToReturn, String filterTemplate, Object... filterParams)
799 throws PspException {
800 SearchFilter filter = new SearchFilter(filterTemplate);
801
802 for (int i=0; i<filterParams.length; i++) {
803 filter.setParameter(i, filterParams[i]);
804 }
805
806 return performLdapSearchRequest(approximateNumResultsExpected, searchBaseDn, scope, attributesToReturn, filter);
807 }
808
809
810 public List<LdapObject> performLdapSearchRequest(int approximateNumResultsExpected, String searchBaseDn, SearchScope scope, Collection<String> attributesToReturn, SearchFilter filter)
811 throws PspException {
812 LOG.debug("Running ldap search: <{}>/{}: {} << {}",
813 searchBaseDn, scope, filter.getFilter(), filter.getParameters());
814
815 final SearchRequest request = new SearchRequest(searchBaseDn, filter, attributesToReturn.toArray(new String[0]));
816 request.setSearchScope(scope);
817
818
819 final List<LdapObject> result = new ArrayList<>();
820 SearchEntryHandler searchCallback = new SearchEntryHandler() {
821 @Override
822 public HandlerResult<SearchEntry> handle(Connection connection, SearchRequest searchRequest, SearchEntry searchEntry) throws LdapException {
823 LOG.debug("Ldap result: {}", searchEntry.getDn());
824 result.add(new LdapObject(searchEntry, request.getReturnAttributes()));
825 return null;
826 }
827
828 @Override
829 public void initializeRequest(SearchRequest searchRequest) {
830
831 }
832 };
833 performLdapSearchRequest(approximateNumResultsExpected, request, searchCallback);
834
835 LOG.info("LDAP search returned {} entries", result.size());
836
837 if ( LOG.isTraceEnabled() ) {
838 int i=0;
839 for (LdapObject ldapObject : result ) {
840 i++;
841 LOG.trace("...ldap-search result {} of {}: {}", new Object[]{i, result.size(), ldapObject.getMap()});
842 }
843 }
844 return result;
845
846 }
847
848
849 public Set<String> performLdapSearchRequest_returningValuesOfAnAttribute(int approximateNumResultsExpected, String searchBaseDn, SearchScope scope, final String attributeToReturn, String filterTemplate, Object... filterParams)
850 throws PspException {
851 SearchFilter filter = new SearchFilter(filterTemplate);
852 LOG.debug("Running ldap search: <{}>/{}: {} << {}",
853 new Object[]{searchBaseDn, scope, filterTemplate, Arrays.toString(filterParams)});
854
855 for (int i=0; i<filterParams.length; i++) {
856 filter.setParameter(i, filterParams[i]);
857 }
858
859 final SearchRequest request = new SearchRequest(searchBaseDn, filter, new String[]{attributeToReturn});
860 request.setSearchScope(scope);
861
862
863
864 final Set<String> result = new HashSet<>();
865 SearchEntryHandler searchCallback = new SearchEntryHandler() {
866 @Override
867 public HandlerResult<SearchEntry> handle(Connection connection, SearchRequest searchRequest, SearchEntry searchEntry) throws LdapException {
868
869 if ( attributeToReturn.equalsIgnoreCase("dn") || attributeToReturn.equalsIgnoreCase("distinguishedName") ) {
870 result.add(searchEntry.getDn().toLowerCase());
871 } else {
872 LdapAttribute attribute = searchEntry.getAttribute(attributeToReturn);
873 if (attribute != null)
874 result.addAll(attribute.getStringValues());
875 }
876 return null;
877 }
878
879 @Override
880 public void initializeRequest(SearchRequest searchRequest) {
881
882 }
883 };
884
885 performLdapSearchRequest(approximateNumResultsExpected, request, searchCallback);
886
887 LOG.info("LDAP search returned {} entries", result.size());
888
889 if ( LOG.isTraceEnabled() ) {
890 int i=0;
891 for (String attributeValue : result ) {
892 i++;
893 LOG.trace("...ldap-search result {} of {}: {}", i, result.size(), attributeValue);
894 }
895 }
896 return result;
897
898 }
899
900
901 public boolean makeLdapObjectCorrect(LdapEntry correctEntry,
902 LdapEntry existingEntry,
903 boolean valuesAreCaseSensitive)
904 throws PspException
905 {
906 boolean changedDn = false, changedAttributes = false;
907
908 changedDn = makeLdapDnCorrect(correctEntry, existingEntry);
909 if ( changedDn ) {
910 LOG.info("{}: Rereading entry after changing DN", ldapSystemName, correctEntry.getDn());
911
912 LdapObject rereadLdapObject = performLdapRead(correctEntry.getDn(), getAttributeNames(existingEntry));
913
914
915 if ( rereadLdapObject!= null ) {
916 existingEntry = rereadLdapObject.ldapEntry;
917 }
918 }
919
920 changedAttributes = makeLdapDataCorrect(correctEntry, existingEntry, valuesAreCaseSensitive);
921
922 return changedDn || changedAttributes;
923
924
925
926
927
928
929
930
931
932 }
933
934
935
936
937
938
939
940
941
942
943
944 public LdapEntry rereadEntry(LdapEntry ldapEntry) throws PspException {
945 Collection<String> attributeNames = getAttributeNames(ldapEntry);
946
947 try {
948 LOG.debug("{}: Rereading entry {}", ldapSystemName, ldapEntry.getDn());
949 LdapObject result = performLdapRead(ldapEntry.getDn(), attributeNames);
950 return result.ldapEntry;
951 } catch (PspException e) {
952 LOG.error("{} Unable to reread ldap object {}", ldapSystemName, ldapEntry.getDn(), e);
953 throw e;
954 }
955 }
956
957
958
959
960
961
962 private Collection<String> getAttributeNames(LdapEntry ldapEntry) {
963 Collection<String> attributeNames = new HashSet<>();
964
965 for (LdapAttribute attribute : ldapEntry.getAttributes() ) {
966 attributeNames.add(attribute.getName());
967 }
968 return attributeNames;
969 }
970
971 protected boolean makeLdapDataCorrect(LdapEntry correctEntry,
972 LdapEntry existingEntry,
973 boolean valuesAreCaseSensitive)
974 throws PspException
975 {
976 boolean changed = false ;
977 for ( String attributeName : correctEntry.getAttributeNames() ) {
978 LdapAttribute correctAttribute = correctEntry.getAttribute(attributeName);
979 if ( attributeHasNoValues(correctAttribute) ) {
980 correctAttribute = null;
981 }
982
983 LdapAttribute existingAttribute= existingEntry.getAttribute(attributeName);
984
985
986 if ( correctAttribute == null ) {
987 if ( existingAttribute != null ) {
988 changed = true;
989 LOG.info("{}: Attribute {} is incorrect: {} current values, Correct values: none",
990 correctEntry.getDn(), attributeName,
991 (existingAttribute != null ? existingAttribute.size() : "<none>"));
992
993 AttributeModification mod = new AttributeModification(AttributeModificationType.REMOVE, existingAttribute);
994 ModifyRequest modRequest = new ModifyRequest(correctEntry.getDn(), mod);
995 performLdapModify(modRequest, valuesAreCaseSensitive);
996 }
997 }
998 else if ( !correctAttribute.equals(existingAttribute) ) {
999
1000 changed = true;
1001 LOG.info("{}: Attribute {} is incorrect: {} Current values, {} Correct values",
1002 correctEntry.getDn(),
1003 attributeName,
1004 (existingAttribute != null ? existingAttribute.size() : "<none>"),
1005 (correctAttribute != null ? correctAttribute.size() : "<none>" ));
1006
1007 AttributeModification mod = new AttributeModification(AttributeModificationType.REPLACE, correctAttribute);
1008 ModifyRequest modRequest = new ModifyRequest(correctEntry.getDn(), mod);
1009 performLdapModify(modRequest, valuesAreCaseSensitive);
1010 }
1011 }
1012 return changed;
1013 }
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025 protected boolean makeLdapDnCorrect(LdapEntry correctEntry, LdapEntry existingEntry) throws PspException {
1026
1027 String correctDn = correctEntry.getDn();
1028 String existingDn= existingEntry.getDn();
1029
1030
1031 if ( !correctDn.equalsIgnoreCase(existingDn) ) {
1032
1033 LOG.debug("{}: DN needs to change to {}", existingDn, correctDn);
1034
1035
1036 ModifyDnRequest moddn = new ModifyDnRequest(existingDn, correctDn);
1037 moddn.setDeleteOldRDn(true);
1038
1039 performLdapModifyDn(moddn);
1040 return true;
1041 }
1042 return false;
1043 }
1044
1045
1046 public boolean test() {
1047 String ldapUrlString = (String) getLdaptiveProperties().get("org.ldaptive.ldapUrl");
1048 if ( ldapUrlString == null ) {
1049 LOG.error("Could not find LDAP URL");
1050 return false;
1051 }
1052
1053 LOG.info("LDAP Url: " + ldapUrlString);
1054
1055 if ( !ldapUrlString.startsWith("ldaps") ) {
1056 LOG.warn("Not an SSL ldap url");
1057 }
1058 else {
1059 LOG.info("Testing SSL before the LDAP test");
1060 try {
1061
1062 Pattern urlPattern = Pattern.compile("ldaps://([^:]*)(:[0-9]+)?.*");
1063 Matcher m = urlPattern.matcher(ldapUrlString);
1064 if ( !m.matches() ) {
1065 LOG.error("Unable to parse ldap url: " + ldapUrlString);
1066 return false;
1067 }
1068
1069 String host = m.group(1);
1070 String portString = m.group(2);
1071 int port;
1072 if ( portString == null || portString.length() == 0 ) {
1073 port=636;
1074 }
1075 else {
1076 port=Integer.parseInt(portString.substring(1));
1077 }
1078
1079 LOG.info(" Making SSL connection to {}:{}", host, port);
1080
1081 SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
1082 SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(host, port);
1083
1084 InputStream in = sslsocket.getInputStream();
1085 OutputStream out = sslsocket.getOutputStream();
1086
1087
1088 out.write(1);
1089
1090 while (in.available() > 0) {
1091 System.out.print(in.read());
1092 }
1093 LOG.info("Successfully connected");
1094
1095 } catch (Exception exception) {
1096 exception.printStackTrace();
1097 }
1098 }
1099
1100 try {
1101 BlockingConnectionPool pool = buildLdapConnectionPool();
1102 LOG.info("Success: Ldap pool built");
1103
1104 performTestLdapRead(pool.getConnection());
1105 LOG.info("Success: Test ldap read");
1106 return true;
1107 }
1108 catch (LdapException e) {
1109 LOG.error("LDAP Failure",e);
1110 return false;
1111 }
1112 catch (PspException e) {
1113 LOG.error("LDAP Failure",e);
1114 return false;
1115 }
1116 }
1117
1118 public static void main(String[] args) {
1119 if ( args.length != 1 ) {
1120 LOG.error("USAGE: <ldap-pool-name from grouper-loader.properties>");
1121 System.exit(1);
1122 }
1123
1124 LOG.info("Starting LDAP-connection test");
1125 LdapSystemper/pspng/LdapSystem.html#LdapSystem">LdapSystem system = new LdapSystem(args[0], false);
1126 system.test();
1127 }
1128 }