001/* 002 * (C) Copyright 2006-2018 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Nuxeo - initial API and implementation 018 * 019 */ 020 021package org.nuxeo.ecm.directory.ldap; 022 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.List; 026import java.util.Set; 027import java.util.TreeSet; 028 029import javax.naming.CompositeName; 030import javax.naming.InvalidNameException; 031import javax.naming.Name; 032import javax.naming.NamingEnumeration; 033import javax.naming.NamingException; 034import javax.naming.directory.Attribute; 035import javax.naming.directory.Attributes; 036import javax.naming.directory.BasicAttribute; 037import javax.naming.directory.BasicAttributes; 038import javax.naming.directory.DirContext; 039import javax.naming.directory.SchemaViolationException; 040import javax.naming.directory.SearchControls; 041import javax.naming.directory.SearchResult; 042import javax.naming.ldap.LdapName; 043import javax.naming.ldap.Rdn; 044 045import org.apache.commons.lang3.StringUtils; 046import org.apache.commons.logging.Log; 047import org.apache.commons.logging.LogFactory; 048import org.apache.directory.shared.ldap.codec.util.LdapURL; 049import org.apache.directory.shared.ldap.codec.util.LdapURLEncodingException; 050import org.nuxeo.common.xmap.annotation.XNode; 051import org.nuxeo.common.xmap.annotation.XNodeList; 052import org.nuxeo.common.xmap.annotation.XObject; 053import org.nuxeo.ecm.core.api.DocumentModel; 054import org.nuxeo.ecm.core.api.PropertyException; 055import org.nuxeo.ecm.directory.AbstractReference; 056import org.nuxeo.ecm.directory.BaseSession; 057import org.nuxeo.ecm.directory.Directory; 058import org.nuxeo.ecm.directory.DirectoryEntryNotFoundException; 059import org.nuxeo.ecm.directory.DirectoryException; 060import org.nuxeo.ecm.directory.DirectoryFieldMapper; 061import org.nuxeo.ecm.directory.ReferenceDescriptor; 062import org.nuxeo.ecm.directory.Session; 063import org.nuxeo.ecm.directory.ldap.filter.FilterExpressionCorrector; 064import org.nuxeo.ecm.directory.ldap.filter.FilterExpressionCorrector.FilterJobs; 065 066/** 067 * Implementation of the directory Reference interface that leverage two common ways of storing relationships in LDAP 068 * directories: 069 * <ul> 070 * <li>the static attribute strategy where a multi-valued attribute store the exhaustive list of distinguished names of 071 * the refereed entries (eg. the uniqueMember attribute of the groupOfUniqueNames objectclass)</li> 072 * <li>the dynamic attribute strategy where a potentially multi-valued attribute stores a ldap urls intensively 073 * describing the refereed LDAP entries (eg. the memberURLs attribute of the groupOfURLs objectclass)</li> 074 * </ul> 075 * <p> 076 * Please note that both static and dynamic references are resolved in read mode whereas only the static attribute 077 * strategy is used when creating new references or when deleting existing ones (write / update mode). 078 * <p> 079 * Some design considerations behind the implementation of such reference can be found at: 080 * http://jira.nuxeo.org/browse/NXP-1506 081 * 082 * @author Olivier Grisel <[email protected]> 083 */ 084@XObject(value = "ldapReference") 085public class LDAPReference extends AbstractReference implements Cloneable { 086 087 private static final Log log = LogFactory.getLog(LDAPReference.class); 088 089 @XNodeList(value = "dynamicReference", type = LDAPDynamicReferenceDescriptor[].class, componentType = LDAPDynamicReferenceDescriptor.class) 090 private LDAPDynamicReferenceDescriptor[] dynamicReferences; 091 092 @XNode("@forceDnConsistencyCheck") 093 public boolean forceDnConsistencyCheck; 094 095 protected LDAPDirectoryDescriptor targetDirectoryDescriptor; 096 097 /** 098 * Resolve staticAttributeId as distinguished names (true by default) such as in the uniqueMember field of 099 * groupOfUniqueNames. Set to false to resolve as simple id (as in memberUID of posixGroup for instance). 100 */ 101 @XNode("@staticAttributeIdIsDn") 102 private boolean staticAttributeIdIsDn = true; 103 104 @XNode("@staticAttributeId") 105 protected String staticAttributeId; 106 107 @XNode("@dynamicAttributeId") 108 protected String dynamicAttributeId; 109 110 public LDAPReference() { 111 super(null, null); 112 } 113 114 public LDAPReference(ReferenceDescriptor referenceDescriptor) { 115 super(referenceDescriptor.getFieldName(), referenceDescriptor.getDirectory()); 116 } 117 118 @XNode("@field") 119 public void setFieldName(String fieldName) { 120 this.fieldName = fieldName; 121 } 122 123 private LDAPFilterMatcher getFilterMatcher() { 124 return new LDAPFilterMatcher(); 125 } 126 127 /** 128 * @return true if the reference should resolve statically refereed entries (identified by dn-valued attribute) 129 */ 130 public boolean isStatic() { 131 return getStaticAttributeId() != null; 132 } 133 134 public String getStaticAttributeId() { 135 return getStaticAttributeId(null); 136 } 137 138 public String getStaticAttributeId(DirectoryFieldMapper sourceFM) { 139 if (staticAttributeId != null) { 140 // explicitly provided attributeId 141 return staticAttributeId; 142 } 143 144 // sourceFM can be passed to avoid infinite loop in LDAPDirectory init 145 if (sourceFM == null) { 146 sourceFM = ((LDAPDirectory) getSourceDirectory()).getFieldMapper(); 147 } 148 String backendFieldId = sourceFM.getBackendField(fieldName); 149 if (fieldName.equals(backendFieldId)) { 150 // no specific backendField found and no staticAttributeId found 151 // either, this reference should not be statically resolved 152 return null; 153 } else { 154 // BBB: the field mapper has been explicitly used to specify the 155 // staticAttributeId value as this was the case before the 156 // introduction of the staticAttributeId dynamicAttributeId duality 157 log.warn(String.format( 158 "implicit static attribute definition through fieldMapping is deprecated, " 159 + "please update your setup with " 160 + "<ldapReference field=\"%s\" directory=\"%s\" staticAttributeId=\"%s\">", 161 fieldName, sourceDirectoryName, backendFieldId)); 162 return backendFieldId; 163 } 164 } 165 166 public List<LDAPDynamicReferenceDescriptor> getDynamicAttributes() { 167 return Arrays.asList(dynamicReferences); 168 } 169 170 public String getDynamicAttributeId() { 171 return dynamicAttributeId; 172 } 173 174 /** 175 * @return true if the reference should resolve dynamically refereed entries (identified by a LDAP url-valued 176 * attribute) 177 */ 178 public boolean isDynamic() { 179 return dynamicAttributeId != null; 180 } 181 182 @Override 183 @XNode("@directory") 184 public void setTargetDirectoryName(String targetDirectoryName) { 185 this.targetDirectoryName = targetDirectoryName; 186 } 187 188 @Override 189 public Directory getSourceDirectory() { 190 191 Directory sourceDir = super.getSourceDirectory(); 192 if (sourceDir instanceof LDAPDirectory) { 193 return sourceDir; 194 } else { 195 throw new DirectoryException(sourceDirectoryName 196 + " is not a LDAPDirectory and thus cannot be used in a reference for " + fieldName); 197 } 198 } 199 200 @Override 201 public Directory getTargetDirectory() { 202 Directory targetDir = super.getTargetDirectory(); 203 if (targetDir instanceof LDAPDirectory) { 204 return targetDir; 205 } else { 206 throw new DirectoryException(targetDirectoryName 207 + " is not a LDAPDirectory and thus cannot be referenced as target by " + fieldName); 208 } 209 } 210 211 protected LDAPDirectory getTargetLDAPDirectory() { 212 return (LDAPDirectory) getTargetDirectory(); 213 } 214 215 protected LDAPDirectory getSourceLDAPDirectory() { 216 return (LDAPDirectory) getSourceDirectory(); 217 } 218 219 protected LDAPDirectoryDescriptor getTargetDirectoryDescriptor() { 220 if (targetDirectoryDescriptor == null) { 221 targetDirectoryDescriptor = getTargetLDAPDirectory().getDescriptor(); 222 } 223 return targetDirectoryDescriptor; 224 } 225 226 /** 227 * Store new links using the LDAP staticAttributeId strategy. 228 * 229 * @see org.nuxeo.ecm.directory.Reference#addLinks(String, List) 230 */ 231 @Override 232 public void addLinks(String sourceId, List<String> targetIds) { 233 234 if (targetIds.isEmpty()) { 235 // optim: nothing to do, return silently without further creating 236 // session instances 237 return; 238 } 239 240 LDAPDirectory ldapTargetDirectory = (LDAPDirectory) getTargetDirectory(); 241 LDAPDirectory ldapSourceDirectory = (LDAPDirectory) getSourceDirectory(); 242 String attributeId = getStaticAttributeId(); 243 if (attributeId == null) { 244 if (log.isTraceEnabled()) { 245 log.trace(String.format("trying to edit a non-static reference from %s in directory %s: ignoring", 246 sourceId, ldapSourceDirectory.getName())); 247 } 248 return; 249 } 250 try (LDAPSession targetSession = ldapTargetDirectory.getSession(); 251 LDAPSession sourceSession = ldapSourceDirectory.getSession()) { 252 // fetch the entry to be able to run the security policy 253 // implemented in an entry adaptor 254 DocumentModel sourceEntry = sourceSession.getEntry(sourceId, false); 255 if (sourceEntry == null) { 256 throw new DirectoryException(String.format("could not add links from unexisting %s in directory %s", 257 sourceId, ldapSourceDirectory.getName())); 258 } 259 if (!BaseSession.isReadOnlyEntry(sourceEntry)) { 260 SearchResult ldapEntry = sourceSession.getLdapEntry(sourceId); 261 262 String sourceDn = ldapEntry.getNameInNamespace(); 263 Attribute storedAttr = ldapEntry.getAttributes().get(attributeId); 264 String emptyRefMarker = ldapSourceDirectory.getDescriptor().getEmptyRefMarker(); 265 Attribute attrToAdd = new BasicAttribute(attributeId); 266 for (String targetId : targetIds) { 267 if (staticAttributeIdIsDn) { 268 // TODO optim: avoid LDAP search request when targetDn 269 // can be forged client side (rdnAttribute = idAttribute and scope is onelevel) 270 ldapEntry = targetSession.getLdapEntry(targetId); 271 if (ldapEntry == null) { 272 log.warn(String.format( 273 "entry '%s' in directory '%s' not found: could not add link from '%s' in directory '%s' for '%s'", 274 targetId, ldapTargetDirectory.getName(), sourceId, ldapSourceDirectory.getName(), 275 this)); 276 continue; 277 } 278 String dn = ldapEntry.getNameInNamespace(); 279 if (storedAttr == null || !storedAttr.contains(dn)) { 280 attrToAdd.add(dn); 281 } 282 } else { 283 if (storedAttr == null || !storedAttr.contains(targetId)) { 284 attrToAdd.add(targetId); 285 } 286 } 287 } 288 if (attrToAdd.size() > 0) { 289 try { 290 // do the LDAP request to store missing dns 291 Attributes attrsToAdd = new BasicAttributes(); 292 attrsToAdd.put(attrToAdd); 293 294 if (log.isDebugEnabled()) { 295 log.debug(String.format( 296 "LDAPReference.addLinks(%s, [%s]): LDAP modifyAttributes dn='%s' " 297 + "mod_op='ADD_ATTRIBUTE' attrs='%s' [%s]", 298 sourceId, StringUtils.join(targetIds, ", "), sourceDn, attrsToAdd, this)); 299 } 300 sourceSession.getContext().modifyAttributes(sourceDn, DirContext.ADD_ATTRIBUTE, attrsToAdd); 301 302 // robustly clean any existing empty marker now that we are sure that the list in not empty 303 if (storedAttr != null && storedAttr.contains(emptyRefMarker)) { 304 Attributes cleanAttrs = new BasicAttributes(attributeId, emptyRefMarker); 305 306 if (log.isDebugEnabled()) { 307 log.debug(String.format( 308 "LDAPReference.addLinks(%s, [%s]): LDAP modifyAttributes dn='%s'" 309 + " mod_op='REMOVE_ATTRIBUTE' attrs='%s' [%s]", 310 sourceId, StringUtils.join(targetIds, ", "), sourceDn, cleanAttrs, this)); 311 } 312 sourceSession.getContext().modifyAttributes(sourceDn, DirContext.REMOVE_ATTRIBUTE, 313 cleanAttrs); 314 } 315 } catch (SchemaViolationException e) { 316 if (isDynamic()) { 317 // we are editing an entry that has no static part 318 log.warn(String.format("cannot update dynamic reference in field %s for source %s", 319 getFieldName(), sourceId)); 320 } else { 321 // this is a real schema configuration problem, 322 // wrap up the exception 323 throw new DirectoryException(e); 324 } 325 } 326 } 327 } 328 } catch (NamingException e) { 329 throw new DirectoryException("addLinks failed: " + e.getMessage(), e); 330 } 331 } 332 333 /** 334 * Store new links using the LDAP staticAttributeId strategy. 335 * 336 * @see org.nuxeo.ecm.directory.Reference#addLinks(List, String) 337 */ 338 @Override 339 public void addLinks(List<String> sourceIds, String targetId) { 340 String attributeId = getStaticAttributeId(); 341 if (attributeId == null && !sourceIds.isEmpty()) { 342 log.warn("trying to edit a non-static reference: ignoring"); 343 return; 344 } 345 LDAPDirectory ldapTargetDirectory = (LDAPDirectory) getTargetDirectory(); 346 LDAPDirectory ldapSourceDirectory = (LDAPDirectory) getSourceDirectory(); 347 348 String emptyRefMarker = ldapSourceDirectory.getDescriptor().getEmptyRefMarker(); 349 try (LDAPSession targetSession = ldapTargetDirectory.getSession(); 350 LDAPSession sourceSession = ldapSourceDirectory.getSession()) { 351 if (!sourceSession.isReadOnly()) { 352 // compute the target dn to add to all the matching source 353 // entries 354 SearchResult ldapEntry = targetSession.getLdapEntry(targetId); 355 if (ldapEntry == null) { 356 throw new DirectoryException(String.format("could not add links to unexisting %s in directory %s", 357 targetId, ldapTargetDirectory.getName())); 358 } 359 String targetAttributeValue; 360 if (staticAttributeIdIsDn) { 361 targetAttributeValue = ldapEntry.getNameInNamespace(); 362 } else { 363 targetAttributeValue = targetId; 364 } 365 366 for (String sourceId : sourceIds) { 367 // fetch the entry to be able to run the security policy 368 // implemented in an entry adaptor 369 DocumentModel sourceEntry = sourceSession.getEntry(sourceId, false); 370 if (sourceEntry == null) { 371 log.warn(String.format( 372 "entry %s in directory %s not found: could not add link to %s in directory %s", 373 sourceId, ldapSourceDirectory.getName(), targetId, ldapTargetDirectory.getName())); 374 continue; 375 } 376 if (BaseSession.isReadOnlyEntry(sourceEntry)) { 377 // skip this entry since it cannot be edited to add the 378 // reference to targetId 379 log.warn(String.format( 380 "entry %s in directory %s is readonly: could not add link to %s in directory %s", 381 sourceId, ldapSourceDirectory.getName(), targetId, ldapTargetDirectory.getName())); 382 continue; 383 } 384 ldapEntry = sourceSession.getLdapEntry(sourceId); 385 String sourceDn = ldapEntry.getNameInNamespace(); 386 Attribute storedAttr = ldapEntry.getAttributes().get(attributeId); 387 if (storedAttr.contains(targetAttributeValue)) { 388 // no need to readd 389 continue; 390 } 391 try { 392 // add the new dn 393 Attributes attrs = new BasicAttributes(attributeId, targetAttributeValue); 394 395 if (log.isDebugEnabled()) { 396 log.debug(String.format( 397 "LDAPReference.addLinks([%s], %s): LDAP modifyAttributes dn='%s'" 398 + " mod_op='ADD_ATTRIBUTE' attrs='%s' [%s]", 399 StringUtils.join(sourceIds, ", "), targetId, sourceDn, attrs, this)); 400 } 401 sourceSession.getContext().modifyAttributes(sourceDn, DirContext.ADD_ATTRIBUTE, attrs); 402 403 // robustly clean any existing empty marker now that we 404 // are sure that the list in not empty 405 if (storedAttr.contains(emptyRefMarker)) { 406 Attributes cleanAttrs = new BasicAttributes(attributeId, emptyRefMarker); 407 if (log.isDebugEnabled()) { 408 log.debug(String.format( 409 "LDAPReference.addLinks(%s, %s): LDAP modifyAttributes dn='%s'" 410 + " mod_op='REMOVE_ATTRIBUTE' attrs='%s' [%s]", 411 StringUtils.join(sourceIds, ", "), targetId, sourceDn, cleanAttrs.toString(), 412 this)); 413 } 414 sourceSession.getContext().modifyAttributes(sourceDn, DirContext.REMOVE_ATTRIBUTE, 415 cleanAttrs); 416 } 417 } catch (SchemaViolationException e) { 418 if (isDynamic()) { 419 // we are editing an entry that has no static part 420 log.warn(String.format("cannot add dynamic reference in field %s for target %s", 421 getFieldName(), targetId)); 422 } else { 423 // this is a real schema configuration problem, 424 // wrap the exception 425 throw new DirectoryException(e); 426 } 427 } 428 } 429 } 430 } catch (NamingException e) { 431 throw new DirectoryException("addLinks failed: " + e.getMessage(), e); 432 } 433 } 434 435 /** 436 * Fetch both statically and dynamically defined references and merge the results. 437 * 438 * @see org.nuxeo.ecm.directory.Reference#getSourceIdsForTarget(String) 439 */ 440 @Override 441 public List<String> getSourceIdsForTarget(String targetId) { 442 443 // container to hold merged references 444 Set<String> sourceIds = new TreeSet<>(); 445 SearchResult targetLdapEntry = null; 446 String targetDn = null; 447 448 // fetch all attributes when dynamic groups are used 449 boolean fetchAllAttributes = isDynamic(); 450 451 // step #1: resolve static references 452 String staticAttributeId = getStaticAttributeId(); 453 if (staticAttributeId != null) { 454 // step #1.1: fetch the dn of the targetId entry in the target 455 // directory by the static dn valued strategy 456 LDAPDirectory targetDir = getTargetLDAPDirectory(); 457 458 if (staticAttributeIdIsDn) { 459 try (LDAPSession targetSession = targetDir.getSession()) { 460 targetLdapEntry = targetSession.getLdapEntry(targetId, fetchAllAttributes); 461 if (targetLdapEntry == null) { 462 String msg = String.format( 463 "Failed to perform inverse lookup on LDAPReference" 464 + " resolving field '%s' of '%s' to entries of '%s'" 465 + " using the static content of attribute '%s':" 466 + " entry '%s' cannot be found in '%s'", 467 fieldName, sourceDirectory, targetDirectoryName, staticAttributeId, targetId, 468 targetDirectoryName); 469 throw new DirectoryEntryNotFoundException(msg); 470 } 471 targetDn = pseudoNormalizeDn(targetLdapEntry.getNameInNamespace()); 472 473 } catch (NamingException e) { 474 throw new DirectoryException( 475 "error fetching " + targetId + " from " + targetDirectoryName + ": " + e.getMessage(), e); 476 } 477 } 478 479 // step #1.2: search for entries that reference that dn in the 480 // source directory and collect their ids 481 LDAPDirectory ldapSourceDirectory = getSourceLDAPDirectory(); 482 483 String filterExpr = String.format("(&(%s={0})%s)", staticAttributeId, ldapSourceDirectory.getBaseFilter()); 484 String[] filterArgs = new String[1]; 485 486 if (staticAttributeIdIsDn) { 487 filterArgs[0] = targetDn; 488 } else { 489 filterArgs[0] = targetId; 490 } 491 492 String searchBaseDn = ldapSourceDirectory.getDescriptor().getSearchBaseDn(); 493 SearchControls sctls = ldapSourceDirectory.getSearchControls(); 494 try (LDAPSession sourceSession = ldapSourceDirectory.getSession()) { 495 if (log.isDebugEnabled()) { 496 log.debug(String.format( 497 "LDAPReference.getSourceIdsForTarget(%s): LDAP search search base='%s'" 498 + " filter='%s' args='%s' scope='%s' [%s]", 499 targetId, searchBaseDn, filterExpr, StringUtils.join(filterArgs, ", "), 500 sctls.getSearchScope(), this)); 501 } 502 NamingEnumeration<SearchResult> results = sourceSession.getContext().search(searchBaseDn, filterExpr, 503 filterArgs, sctls); 504 505 try { 506 while (results.hasMore()) { 507 Attributes attributes = results.next().getAttributes(); 508 // NXP-2461: check that id field is filled 509 Attribute attr = attributes.get(sourceSession.idAttribute); 510 if (attr != null) { 511 Object value = attr.get(); 512 if (value != null) { 513 sourceIds.add(value.toString()); 514 } 515 } 516 } 517 } finally { 518 results.close(); 519 } 520 } catch (NamingException e) { 521 throw new DirectoryException("error during reference search for " + filterArgs[0], e); 522 } 523 } 524 // step #2: resolve dynamic references 525 String dynamicAttributeId = this.dynamicAttributeId; 526 if (dynamicAttributeId != null) { 527 528 LDAPDirectory ldapSourceDirectory = getSourceLDAPDirectory(); 529 LDAPDirectory ldapTargetDirectory = getTargetLDAPDirectory(); 530 String searchBaseDn = ldapSourceDirectory.getDescriptor().getSearchBaseDn(); 531 532 try (LDAPSession sourceSession = ldapSourceDirectory.getSession(); 533 LDAPSession targetSession = ldapTargetDirectory.getSession()) { 534 // step #2.1: fetch the target entry to apply the ldap url 535 // filters of the candidate sources on it 536 if (targetLdapEntry == null) { 537 // only fetch the entry if not already fetched by the 538 // static 539 // attributes references resolution 540 targetLdapEntry = targetSession.getLdapEntry(targetId, fetchAllAttributes); 541 } 542 if (targetLdapEntry == null) { 543 String msg = String.format( 544 "Failed to perform inverse lookup on LDAPReference" 545 + " resolving field '%s' of '%s' to entries of '%s'" 546 + " using the dynamic content of attribute '%s':" 547 + " entry '%s' cannot be found in '%s'", 548 fieldName, ldapSourceDirectory, targetDirectoryName, dynamicAttributeId, targetId, 549 targetDirectoryName); 550 throw new DirectoryException(msg); 551 } 552 targetDn = pseudoNormalizeDn(targetLdapEntry.getNameInNamespace()); 553 Attributes targetAttributes = targetLdapEntry.getAttributes(); 554 555 // step #2.2: find the list of entries that hold candidate 556 // dynamic links in the source directory 557 SearchControls sctls = new SearchControls(); 558 sctls.setSearchScope(ldapSourceDirectory.getDescriptor().getSearchScope()); 559 sctls.setReturningAttributes(new String[] { sourceSession.idAttribute, dynamicAttributeId }); 560 String filterExpr = String.format("%s=*", dynamicAttributeId); 561 562 if (log.isDebugEnabled()) { 563 log.debug(String.format( 564 "LDAPReference.getSourceIdsForTarget(%s): LDAP search search base='%s'" 565 + " filter='%s' scope='%s' [%s]", 566 targetId, searchBaseDn, filterExpr, sctls.getSearchScope(), this)); 567 } 568 NamingEnumeration<SearchResult> results = sourceSession.getContext().search(searchBaseDn, filterExpr, 569 sctls); 570 try { 571 while (results.hasMore()) { 572 // step #2.3: for each sourceId and each ldapUrl test 573 // whether the current target entry matches the 574 // collected 575 // URL 576 Attributes sourceAttributes = results.next().getAttributes(); 577 578 NamingEnumeration<?> ldapUrls = sourceAttributes.get(dynamicAttributeId).getAll(); 579 try { 580 while (ldapUrls.hasMore()) { 581 LdapURL ldapUrl = new LdapURL(ldapUrls.next().toString()); 582 String candidateDN = pseudoNormalizeDn(ldapUrl.getDn().getUpName()); 583 // check base URL 584 if (!targetDn.endsWith(candidateDN)) { 585 continue; 586 } 587 588 // check onelevel scope constraints 589 if (ldapUrl.getScope() == SearchControls.ONELEVEL_SCOPE) { 590 int targetDnSize = new LdapName(targetDn).size(); 591 int urlDnSize = new LdapName(candidateDN).size(); 592 if (targetDnSize - urlDnSize > 1) { 593 // target is not a direct child of the 594 // DN of the 595 // LDAP URL 596 continue; 597 } 598 } 599 600 // check that the target entry matches the 601 // filter 602 if (getFilterMatcher().match(targetAttributes, ldapUrl.getFilter())) { 603 // the target match the source url, add it 604 // to the 605 // collected ids 606 sourceIds.add(sourceAttributes.get(sourceSession.idAttribute).get().toString()); 607 } 608 } 609 } finally { 610 ldapUrls.close(); 611 } 612 } 613 } finally { 614 results.close(); 615 } 616 } catch (NamingException | LdapURLEncodingException e) { 617 throw new DirectoryException("error during reference search for " + targetId, e); 618 } 619 } 620 621 /* 622 * This kind of reference is not supported because Active Directory use filter expression not yet supported by 623 * LDAPFilterMatcher. See NXP-4562 624 */ 625 if (dynamicReferences != null && dynamicReferences.length > 0) { 626 log.error("This kind of reference is not supported."); 627 } 628 629 return new ArrayList<>(sourceIds); 630 } 631 632 /** 633 * Fetches both statically and dynamically defined references and merges the results. 634 * 635 * @see org.nuxeo.ecm.directory.Reference#getSourceIdsForTarget(String) 636 */ 637 @Override 638 // XXX: broken, use getLdapTargetIds for a proper implementation 639 public List<String> getTargetIdsForSource(String sourceId) { 640 String schemaName = getSourceDirectory().getSchema(); 641 try (Session session = getSourceDirectory().getSession()) { 642 try { 643 return BaseSession.toStringList(session.getEntry(sourceId).getProperty(schemaName, fieldName)); 644 } catch (PropertyException e) { 645 throw new DirectoryException(e); 646 } 647 } 648 } 649 650 /** 651 * Simple helper that replaces ", " by "," in the provided dn and returns the lower case version of the result for 652 * comparison purpose. 653 * 654 * @param dn the raw unnormalized dn 655 * @return lowercase version without whitespace after commas 656 */ 657 protected static String pseudoNormalizeDn(String dn) throws InvalidNameException { 658 LdapName ldapName = new LdapName(dn); 659 List<String> rdns = new ArrayList<>(); 660 for (Rdn rdn : ldapName.getRdns()) { 661 String value = rdn.getValue().toString().toLowerCase().replaceAll(",", "\\\\,"); 662 String rdnStr = rdn.getType().toLowerCase() + "=" + value; 663 rdns.add(0, rdnStr); 664 } 665 return StringUtils.join(rdns, ','); 666 } 667 668 /** 669 * Optimized method to spare a LDAP request when the caller is a LDAPSession object that has already fetched the 670 * LDAP Attribute instances. 671 * <p> 672 * This method should return the same results as the sister method: org.nuxeo 673 * .ecm.directory.Reference#getTargetIdsForSource(java.lang.String) 674 * 675 * @return target reference ids 676 */ 677 public List<String> getLdapTargetIds(Attributes attributes) { 678 679 Set<String> targetIds = new TreeSet<>(); 680 681 LDAPDirectory ldapTargetDirectory = (LDAPDirectory) getTargetDirectory(); 682 LDAPDirectoryDescriptor targetDirconfig = getTargetDirectoryDescriptor(); 683 String emptyRefMarker = ldapTargetDirectory.getDescriptor().getEmptyRefMarker(); 684 try (LDAPSession targetSession = ldapTargetDirectory.getSession()) { 685 String baseDn = pseudoNormalizeDn(targetDirconfig.getSearchBaseDn()); 686 687 // step #1: fetch ids referenced by static attributes 688 String staticAttributeId = getStaticAttributeId(); 689 Attribute staticAttribute = null; 690 if (staticAttributeId != null) { 691 staticAttribute = attributes.get(staticAttributeId); 692 } 693 694 if (staticAttribute != null && !staticAttributeIdIsDn) { 695 NamingEnumeration<?> staticContent = staticAttribute.getAll(); 696 try { 697 while (staticContent.hasMore()) { 698 String value = staticContent.next().toString(); 699 if (!emptyRefMarker.equals(value)) { 700 targetIds.add(value); 701 } 702 } 703 } finally { 704 staticContent.close(); 705 } 706 } 707 708 if (staticAttribute != null && staticAttributeIdIsDn) { 709 NamingEnumeration<?> targetDns = staticAttribute.getAll(); 710 try { 711 while (targetDns.hasMore()) { 712 String targetDn = targetDns.next().toString(); 713 714 if (!pseudoNormalizeDn(targetDn).endsWith(baseDn)) { 715 // optim: avoid network connections when obvious 716 if (log.isTraceEnabled()) { 717 log.trace(String.format("ignoring: dn='%s' (does not match '%s') for '%s'", targetDn, 718 baseDn, this)); 719 } 720 continue; 721 } 722 // find the id of the referenced entry 723 String id; 724 725 if (targetSession.rdnMatchesIdField()) { 726 // optim: do not fetch the entry to get its true id 727 // but 728 // guess it by reading the targetDn 729 LdapName name = new LdapName(targetDn); 730 String rdn = name.get(name.size() - 1); 731 int pos = rdn.indexOf("="); 732 id = rdn.substring(pos + 1); 733 } else { 734 id = getIdForDn(targetSession, targetDn); 735 if (id == null) { 736 log.warn(String.format( 737 "ignoring target '%s' (missing attribute '%s') while resolving reference '%s'", 738 targetDn, targetSession.idAttribute, this)); 739 continue; 740 } 741 } 742 if (forceDnConsistencyCheck) { 743 // check that the referenced entry is actually part 744 // of 745 // the target directory (takes care of the filters 746 // and 747 // the scope) 748 // this check can be very expensive on large groups 749 // and thus not enabled by default 750 if (!targetSession.hasEntry(id)) { 751 if (log.isTraceEnabled()) { 752 log.trace( 753 String.format( 754 "ignoring target '%s' when resolving '%s' (not part of target" 755 + " directory by forced DN consistency check)", 756 targetDn, this)); 757 } 758 continue; 759 } 760 } 761 // NXP-2461: check that id field is filled 762 if (id != null) { 763 targetIds.add(id); 764 } 765 } 766 } finally { 767 targetDns.close(); 768 } 769 } 770 // step #2: fetched dynamically referenced ids 771 String dynamicAttributeId = this.dynamicAttributeId; 772 Attribute dynamicAttribute = null; 773 if (dynamicAttributeId != null) { 774 dynamicAttribute = attributes.get(dynamicAttributeId); 775 } 776 if (dynamicAttribute != null) { 777 NamingEnumeration<?> rawldapUrls = dynamicAttribute.getAll(); 778 try { 779 while (rawldapUrls.hasMore()) { 780 LdapURL ldapUrl = new LdapURL(rawldapUrls.next().toString()); 781 String linkDn = pseudoNormalizeDn(ldapUrl.getDn().getUpName()); 782 String directoryDn = pseudoNormalizeDn(targetDirconfig.getSearchBaseDn()); 783 int scope = SearchControls.ONELEVEL_SCOPE; 784 int scopePart = ldapUrl.getScope(); 785 if (scopePart == SearchControls.SUBTREE_SCOPE) { 786 scope = SearchControls.SUBTREE_SCOPE; 787 } 788 if (!linkDn.endsWith(directoryDn) && !directoryDn.endsWith(linkDn)) { 789 // optim #1: if the dns do not match, abort 790 continue; 791 } else if (directoryDn.endsWith(linkDn) && linkDn.length() < directoryDn.length() 792 && scope == SearchControls.ONELEVEL_SCOPE) { 793 // optim #2: the link dn is pointing to elements 794 // that at 795 // upperlevel than directory elements 796 continue; 797 } else { 798 799 // Search for references elements 800 targetIds.addAll( 801 getReferencedElements(attributes, directoryDn, linkDn, ldapUrl.getFilter(), scope)); 802 803 } 804 } 805 } finally { 806 rawldapUrls.close(); 807 } 808 } 809 810 if (dynamicReferences != null && dynamicReferences.length > 0) { 811 812 // Only the first Dynamic Reference is used 813 LDAPDynamicReferenceDescriptor dynAtt = dynamicReferences[0]; 814 815 Attribute baseDnsAttribute = attributes.get(dynAtt.baseDN); 816 Attribute filterAttribute = attributes.get(dynAtt.filter); 817 818 if (baseDnsAttribute != null && filterAttribute != null) { 819 820 NamingEnumeration<?> baseDns = null; 821 NamingEnumeration<?> filters = null; 822 823 try { 824 // Get the BaseDN value from the descriptor 825 baseDns = baseDnsAttribute.getAll(); 826 String linkDnValue = baseDns.next().toString(); 827 baseDns.close(); 828 linkDnValue = pseudoNormalizeDn(linkDnValue); 829 830 // Get the filter value from the descriptor 831 filters = filterAttribute.getAll(); 832 String filterValue = filters.next().toString(); 833 filters.close(); 834 835 // Get the scope value from the descriptor 836 int scope = "subtree".equalsIgnoreCase(dynAtt.type) ? SearchControls.SUBTREE_SCOPE 837 : SearchControls.ONELEVEL_SCOPE; 838 839 String directoryDn = pseudoNormalizeDn(targetDirconfig.getSearchBaseDn()); 840 841 // if the dns match, and if the link dn is pointing to 842 // elements that at upperlevel than directory elements 843 if ((linkDnValue.endsWith(directoryDn) || directoryDn.endsWith(linkDnValue)) 844 && !(directoryDn.endsWith(linkDnValue) && linkDnValue.length() < directoryDn.length() 845 && scope == SearchControls.ONELEVEL_SCOPE)) { 846 847 // Correct the filter expression 848 filterValue = FilterExpressionCorrector.correctFilter(filterValue, FilterJobs.CORRECT_NOT); 849 850 // Search for references elements 851 targetIds.addAll( 852 getReferencedElements(attributes, directoryDn, linkDnValue, filterValue, scope)); 853 854 } 855 } finally { 856 if (baseDns != null) { 857 baseDns.close(); 858 } 859 860 if (filters != null) { 861 filters.close(); 862 } 863 } 864 865 } 866 867 } 868 // return merged attributes 869 return new ArrayList<>(targetIds); 870 } catch (NamingException | LdapURLEncodingException e) { 871 throw new DirectoryException("error computing LDAP references", e); 872 } 873 } 874 875 protected String getIdForDn(LDAPSession session, String dn) { 876 // the entry id is not based on the rdn, we thus need to 877 // fetch the LDAP entry to grab it 878 String[] attributeIdsToCollect = { session.idAttribute }; 879 Attributes entry; 880 try { 881 882 if (log.isDebugEnabled()) { 883 log.debug(String.format( 884 "LDAPReference.getIdForDn(session, %s): LDAP get dn='%s'" 885 + " attribute ids to collect='%s' [%s]", 886 dn, dn, StringUtils.join(attributeIdsToCollect, ", "), this)); 887 } 888 889 Name name = new CompositeName().add(dn); 890 entry = session.getContext().getAttributes(name, attributeIdsToCollect); 891 } catch (NamingException e) { 892 return null; 893 } 894 // NXP-2461: check that id field is filled 895 Attribute attr = entry.get(session.idAttribute); 896 if (attr != null) { 897 try { 898 return attr.get().toString(); 899 } catch (NamingException e) { 900 } 901 } 902 return null; 903 } 904 905 /** 906 * Retrieve the elements referenced by the filter/BaseDN/Scope request. 907 * 908 * @param attributes Attributes of the referencer element 909 * @param directoryDn Dn of the Directory 910 * @param linkDn Dn specified in the parent 911 * @param filter Filter expression specified in the parent 912 * @param scope scope for the search 913 * @return The list of the referenced elements. 914 */ 915 private Set<String> getReferencedElements(Attributes attributes, String directoryDn, String linkDn, String filter, 916 int scope) throws NamingException { 917 918 Set<String> targetIds = new TreeSet<>(); 919 920 LDAPDirectoryDescriptor targetDirconfig = getTargetDirectoryDescriptor(); 921 LDAPDirectory ldapTargetDirectory = (LDAPDirectory) getTargetDirectory(); 922 LDAPSession targetSession = ldapTargetDirectory.getSession(); 923 924 // use the most specific scope between the one specified in the 925 // Directory and the specified in the Parent 926 String dn = directoryDn.endsWith(linkDn) && directoryDn.length() > linkDn.length() ? directoryDn : linkDn; 927 928 // combine the ldapUrl search query with target 929 // directory own constraints 930 SearchControls scts = new SearchControls(); 931 932 // use the most specific scope 933 scts.setSearchScope(Math.min(scope, targetDirconfig.getSearchScope())); 934 935 // only fetch the ids of the targets 936 scts.setReturningAttributes(new String[] { targetSession.idAttribute }); 937 938 // combine the filter of the target directory with the 939 // provided filter if any 940 String targetFilter = targetDirconfig.getSearchFilter(); 941 if (filter == null || filter.length() == 0) { 942 filter = targetFilter; 943 } else if (targetFilter != null && targetFilter.length() > 0) { 944 filter = String.format("(&(%s)(%s))", targetFilter, filter); 945 } 946 947 // perform the request and collect the ids 948 if (log.isDebugEnabled()) { 949 log.debug(String.format( 950 "LDAPReference.getLdapTargetIds(%s): LDAP search dn='%s' " + " filter='%s' scope='%s' [%s]", 951 attributes, dn, dn, scts.getSearchScope(), this)); 952 } 953 954 Name name = new CompositeName().add(dn); 955 NamingEnumeration<SearchResult> results = targetSession.getContext().search(name, filter, scts); 956 try { 957 while (results.hasMore()) { 958 // NXP-2461: check that id field is filled 959 Attribute attr = results.next().getAttributes().get(targetSession.idAttribute); 960 if (attr != null) { 961 String collectedId = attr.get().toString(); 962 if (collectedId != null) { 963 targetIds.add(collectedId); 964 } 965 } 966 967 } 968 } finally { 969 results.close(); 970 } 971 972 return targetIds; 973 } 974 975 /** 976 * Remove existing statically defined links for the given source id (dynamic references remain unaltered) 977 * 978 * @see org.nuxeo.ecm.directory.Reference#removeLinksForSource(String) 979 */ 980 @Override 981 public void removeLinksForSource(String sourceId) { 982 LDAPDirectory ldapTargetDirectory = (LDAPDirectory) getTargetDirectory(); 983 LDAPDirectory ldapSourceDirectory = (LDAPDirectory) getSourceDirectory(); 984 String attributeId = getStaticAttributeId(); 985 try (LDAPSession sourceSession = ldapSourceDirectory.getSession(); 986 LDAPSession targetSession = ldapTargetDirectory.getSession()) { 987 if (sourceSession.isReadOnly() || attributeId == null) { 988 // do not try to do anything on a read only server or to a 989 // purely dynamic reference 990 return; 991 } 992 // get the dn of the entry that matches sourceId 993 SearchResult sourceLdapEntry = sourceSession.getLdapEntry(sourceId); 994 if (sourceLdapEntry == null) { 995 throw new DirectoryException( 996 String.format("cannot edit the links hold by missing entry '%s' in directory '%s'", sourceId, 997 ldapSourceDirectory.getName())); 998 } 999 String sourceDn = pseudoNormalizeDn(sourceLdapEntry.getNameInNamespace()); 1000 1001 Attribute oldAttr = sourceLdapEntry.getAttributes().get(attributeId); 1002 if (oldAttr == null) { 1003 // consider it as an empty attribute to simplify the following 1004 // code 1005 oldAttr = new BasicAttribute(attributeId); 1006 } 1007 Attribute attrToRemove = new BasicAttribute(attributeId); 1008 1009 NamingEnumeration<?> oldAttrs = oldAttr.getAll(); 1010 String targetBaseDn = pseudoNormalizeDn(ldapTargetDirectory.getDescriptor().getSearchBaseDn()); 1011 try { 1012 while (oldAttrs.hasMore()) { 1013 String targetKeyAttr = oldAttrs.next().toString(); 1014 1015 if (staticAttributeIdIsDn) { 1016 String dn = pseudoNormalizeDn(targetKeyAttr); 1017 if (forceDnConsistencyCheck) { 1018 String id = getIdForDn(targetSession, dn); 1019 if (id != null && targetSession.hasEntry(id)) { 1020 // this is an entry managed by the current 1021 // reference 1022 attrToRemove.add(dn); 1023 } 1024 } else if (dn.endsWith(targetBaseDn)) { 1025 // this is an entry managed by the current 1026 // reference 1027 attrToRemove.add(dn); 1028 } 1029 } else { 1030 attrToRemove.add(targetKeyAttr); 1031 } 1032 } 1033 } finally { 1034 oldAttrs.close(); 1035 } 1036 try { 1037 if (attrToRemove.size() == oldAttr.size()) { 1038 // use the empty ref marker to avoid empty attr 1039 String emptyRefMarker = ldapSourceDirectory.getDescriptor().getEmptyRefMarker(); 1040 Attributes emptyAttribute = new BasicAttributes(attributeId, emptyRefMarker); 1041 if (log.isDebugEnabled()) { 1042 log.debug(String.format( 1043 "LDAPReference.removeLinksForSource(%s): LDAP modifyAttributes key='%s' " 1044 + " mod_op='REPLACE_ATTRIBUTE' attrs='%s' [%s]", 1045 sourceId, sourceDn, emptyAttribute, this)); 1046 } 1047 sourceSession.getContext().modifyAttributes(sourceDn, DirContext.REPLACE_ATTRIBUTE, emptyAttribute); 1048 } else if (attrToRemove.size() > 0) { 1049 // remove the attribute managed by the current reference 1050 Attributes attrsToRemove = new BasicAttributes(); 1051 attrsToRemove.put(attrToRemove); 1052 if (log.isDebugEnabled()) { 1053 log.debug(String.format( 1054 "LDAPReference.removeLinksForSource(%s): LDAP modifyAttributes dn='%s' " 1055 + " mod_op='REMOVE_ATTRIBUTE' attrs='%s' [%s]", 1056 sourceId, sourceDn, attrsToRemove, this)); 1057 } 1058 sourceSession.getContext().modifyAttributes(sourceDn, DirContext.REMOVE_ATTRIBUTE, attrsToRemove); 1059 } 1060 } catch (SchemaViolationException e) { 1061 if (isDynamic()) { 1062 // we are editing an entry that has no static part 1063 log.warn(String.format("cannot remove dynamic reference in field %s for source %s", getFieldName(), 1064 sourceId)); 1065 } else { 1066 // this is a real schma configuration problem, wrapup the 1067 // exception 1068 throw new DirectoryException(e); 1069 } 1070 } 1071 } catch (NamingException e) { 1072 throw new DirectoryException("removeLinksForSource failed: " + e.getMessage(), e); 1073 } 1074 } 1075 1076 /** 1077 * Remove existing statically defined links for the given target id (dynamic references remain unaltered) 1078 * 1079 * @see org.nuxeo.ecm.directory.Reference#removeLinksForTarget(String) 1080 */ 1081 @Override 1082 public void removeLinksForTarget(String targetId) { 1083 if (!isStatic()) { 1084 // nothing to do: dynamic references cannot be updated 1085 return; 1086 } 1087 LDAPDirectory ldapTargetDirectory = (LDAPDirectory) getTargetDirectory(); 1088 LDAPDirectory ldapSourceDirectory = (LDAPDirectory) getSourceDirectory(); 1089 String attributeId = getStaticAttributeId(); 1090 try (LDAPSession targetSession = ldapTargetDirectory.getSession(); 1091 LDAPSession sourceSession = ldapSourceDirectory.getSession()) { 1092 if (!sourceSession.isReadOnly()) { 1093 // get the dn of the target that matches targetId 1094 String targetAttributeValue; 1095 1096 if (staticAttributeIdIsDn) { 1097 SearchResult targetLdapEntry = targetSession.getLdapEntry(targetId); 1098 if (targetLdapEntry == null) { 1099 String rdnAttribute = ldapTargetDirectory.getDescriptor().getRdnAttribute(); 1100 if (!rdnAttribute.equals(targetSession.idAttribute)) { 1101 log.warn(String.format( 1102 "cannot remove links to missing entry %s in directory %s for reference %s", 1103 targetId, ldapTargetDirectory.getName(), this)); 1104 return; 1105 } 1106 // the entry might have already been deleted, try to 1107 // re-forge it if possible (might not work if scope is 1108 // subtree) 1109 targetAttributeValue = String.format("%s=%s,%s", rdnAttribute, targetId, 1110 ldapTargetDirectory.getDescriptor().getSearchBaseDn()); 1111 } else { 1112 targetAttributeValue = pseudoNormalizeDn(targetLdapEntry.getNameInNamespace()); 1113 } 1114 } else { 1115 targetAttributeValue = targetId; 1116 } 1117 1118 // build a LDAP query to find entries that point to the target 1119 String searchFilter = String.format("(%s=%s)", attributeId, targetAttributeValue); 1120 String sourceFilter = ldapSourceDirectory.getBaseFilter(); 1121 1122 if (sourceFilter != null && !"".equals(sourceFilter)) { 1123 searchFilter = String.format("(&(%s)(%s))", searchFilter, sourceFilter); 1124 } 1125 1126 SearchControls scts = new SearchControls(); 1127 scts.setSearchScope(ldapSourceDirectory.getDescriptor().getSearchScope()); 1128 scts.setReturningAttributes(new String[] { attributeId }); 1129 1130 // find all source entries that point to the target key and 1131 // clean 1132 // those references 1133 if (log.isDebugEnabled()) { 1134 log.debug(String.format( 1135 "LDAPReference.removeLinksForTarget(%s): LDAP search baseDn='%s' " 1136 + " filter='%s' scope='%s' [%s]", 1137 targetId, sourceSession.searchBaseDn, searchFilter, scts.getSearchScope(), this)); 1138 } 1139 NamingEnumeration<SearchResult> results = sourceSession.getContext().search(sourceSession.searchBaseDn, 1140 searchFilter, scts); 1141 String emptyRefMarker = ldapSourceDirectory.getDescriptor().getEmptyRefMarker(); 1142 Attributes emptyAttribute = new BasicAttributes(attributeId, emptyRefMarker); 1143 1144 try { 1145 while (results.hasMore()) { 1146 SearchResult result = results.next(); 1147 Attributes attrs = result.getAttributes(); 1148 Attribute attr = attrs.get(attributeId); 1149 try { 1150 if (attr.size() == 1) { 1151 // the attribute holds the last reference, put 1152 // the 1153 // empty ref. marker before removing the 1154 // attribute 1155 // since empty attribute are often not allowed 1156 // by 1157 // the server schema 1158 if (log.isDebugEnabled()) { 1159 log.debug(String.format( 1160 "LDAPReference.removeLinksForTarget(%s): LDAP modifyAttributes key='%s' " 1161 + "mod_op='ADD_ATTRIBUTE' attrs='%s' [%s]", 1162 targetId, result.getNameInNamespace(), attrs, this)); 1163 } 1164 sourceSession.getContext().modifyAttributes(result.getNameInNamespace(), 1165 DirContext.ADD_ATTRIBUTE, emptyAttribute); 1166 } 1167 // remove the reference to the target key 1168 attrs = new BasicAttributes(); 1169 attr = new BasicAttribute(attributeId); 1170 attr.add(targetAttributeValue); 1171 attrs.put(attr); 1172 if (log.isDebugEnabled()) { 1173 log.debug(String.format( 1174 "LDAPReference.removeLinksForTarget(%s): LDAP modifyAttributes key='%s' " 1175 + "mod_op='REMOVE_ATTRIBUTE' attrs='%s' [%s]", 1176 targetId, result.getNameInNamespace(), attrs, this)); 1177 } 1178 sourceSession.getContext().modifyAttributes(result.getNameInNamespace(), 1179 DirContext.REMOVE_ATTRIBUTE, attrs); 1180 } catch (SchemaViolationException e) { 1181 if (isDynamic()) { 1182 // we are editing an entry that has no static 1183 // part 1184 log.warn(String.format("cannot remove dynamic reference in field %s for target %s", 1185 getFieldName(), targetId)); 1186 } else { 1187 // this is a real schema configuration problem, 1188 // wrapup the exception 1189 throw new DirectoryException(e); 1190 } 1191 } 1192 } 1193 } finally { 1194 results.close(); 1195 } 1196 } 1197 } catch (NamingException e) { 1198 throw new DirectoryException("removeLinksForTarget failed: " + e.getMessage(), e); 1199 } 1200 } 1201 1202 /** 1203 * Edit the list of statically defined references for a given target (dynamic references remain unaltered) 1204 * 1205 * @see org.nuxeo.ecm.directory.Reference#setSourceIdsForTarget(String, List) 1206 */ 1207 @Override 1208 public void setSourceIdsForTarget(String targetId, List<String> sourceIds) { 1209 removeLinksForTarget(targetId); 1210 addLinks(sourceIds, targetId); 1211 } 1212 1213 @Override 1214 public void setSourceIdsForTarget(String targetId, List<String> sourceIds, Session session) { 1215 setSourceIdsForTarget(targetId, sourceIds); 1216 } 1217 1218 /** 1219 * Set the list of statically defined references for a given source (dynamic references remain unaltered) 1220 * 1221 * @see org.nuxeo.ecm.directory.Reference#setTargetIdsForSource(String, List) 1222 */ 1223 @Override 1224 public void setTargetIdsForSource(String sourceId, List<String> targetIds) { 1225 removeLinksForSource(sourceId); 1226 addLinks(sourceId, targetIds); 1227 } 1228 1229 @Override 1230 public void setTargetIdsForSource(String sourceId, List<String> targetIds, Session session) { 1231 setTargetIdsForSource(sourceId, targetIds); 1232 } 1233 1234 @Override 1235 public void removeLinksForTarget(String targetId, Session session) { 1236 removeLinksForTarget(targetId); 1237 } 1238 1239 @Override 1240 public void removeLinksForSource(String sourceId, Session session) { 1241 removeLinksForSource(sourceId); 1242 } 1243 1244 @Override 1245 // to build helpful debug logs 1246 public String toString() { 1247 return String.format( 1248 "LDAPReference to resolve field='%s' of sourceDirectory='%s'" + " with targetDirectory='%s'" 1249 + " and staticAttributeId='%s', dynamicAttributeId='%s'", 1250 fieldName, sourceDirectoryName, targetDirectoryName, staticAttributeId, dynamicAttributeId); 1251 } 1252 1253 /** 1254 * @since 5.6 1255 */ 1256 @Override 1257 public LDAPReference clone() { 1258 try { 1259 // basic fields are already copied by super.clone() 1260 return (LDAPReference) super.clone(); 1261 } catch (CloneNotSupportedException e) { 1262 throw new AssertionError(e); 1263 } 1264 } 1265 1266 @Override 1267 public void addLinks(String sourceId, List<String> targetIds, Session session) { 1268 // TODO to use for optimization 1269 addLinks(sourceId, targetIds); 1270 } 1271 1272 @Override 1273 public void addLinks(List<String> sourceIds, String targetId, Session session) { 1274 // TODO to use for optimization 1275 addLinks(sourceIds, targetId); 1276 } 1277 1278}