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 <ogrisel@nuxeo.com> 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() throws DirectoryException { 131 return getStaticAttributeId() != null; 132 } 133 134 public String getStaticAttributeId() throws DirectoryException { 135 return getStaticAttributeId(null); 136 } 137 138 public String getStaticAttributeId(DirectoryFieldMapper sourceFM) throws DirectoryException { 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() throws DirectoryException { 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() throws DirectoryException { 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() throws DirectoryException { 212 return (LDAPDirectory) getTargetDirectory(); 213 } 214 215 protected LDAPDirectory getSourceLDAPDirectory() throws DirectoryException { 216 return (LDAPDirectory) getSourceDirectory(); 217 } 218 219 protected LDAPDirectoryDescriptor getTargetDirectoryDescriptor() throws DirectoryException { 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) throws DirectoryException { 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 = (LDAPSession) ldapTargetDirectory.getSession(); 251 LDAPSession sourceSession = (LDAPSession) 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.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) throws DirectoryException { 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 = (LDAPSession) ldapTargetDirectory.getSession(); 350 LDAPSession sourceSession = (LDAPSession) 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) throws DirectoryException { 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 = (LDAPSession) 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 = (LDAPSession) 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 = (LDAPSession) ldapSourceDirectory.getSession(); 533 LDAPSession targetSession = (LDAPSession) 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 @SuppressWarnings("unchecked") 640 public List<String> getTargetIdsForSource(String sourceId) throws DirectoryException { 641 String schemaName = getSourceDirectory().getSchema(); 642 try (Session session = getSourceDirectory().getSession()) { 643 try { 644 return (List<String>) session.getEntry(sourceId).getProperty(schemaName, fieldName); 645 } catch (PropertyException e) { 646 throw new DirectoryException(e); 647 } 648 } 649 } 650 651 /** 652 * Simple helper that replaces ", " by "," in the provided dn and returns the lower case version of the result for 653 * comparison purpose. 654 * 655 * @param dn the raw unnormalized dn 656 * @return lowercase version without whitespace after commas 657 */ 658 protected static String pseudoNormalizeDn(String dn) throws InvalidNameException { 659 LdapName ldapName = new LdapName(dn); 660 List<String> rdns = new ArrayList<>(); 661 for (Rdn rdn : ldapName.getRdns()) { 662 String value = rdn.getValue().toString().toLowerCase().replaceAll(",", "\\\\,"); 663 String rdnStr = rdn.getType().toLowerCase() + "=" + value; 664 rdns.add(0, rdnStr); 665 } 666 return StringUtils.join(rdns, ','); 667 } 668 669 /** 670 * Optimized method to spare a LDAP request when the caller is a LDAPSession object that has already fetched the 671 * LDAP Attribute instances. 672 * <p> 673 * This method should return the same results as the sister method: org.nuxeo 674 * .ecm.directory.Reference#getTargetIdsForSource(java.lang.String) 675 * 676 * @return target reference ids 677 */ 678 public List<String> getLdapTargetIds(Attributes attributes) throws DirectoryException { 679 680 Set<String> targetIds = new TreeSet<>(); 681 682 LDAPDirectory ldapTargetDirectory = (LDAPDirectory) getTargetDirectory(); 683 LDAPDirectoryDescriptor targetDirconfig = getTargetDirectoryDescriptor(); 684 String emptyRefMarker = ldapTargetDirectory.getDescriptor().getEmptyRefMarker(); 685 try (LDAPSession targetSession = (LDAPSession) ldapTargetDirectory.getSession()) { 686 String baseDn = pseudoNormalizeDn(targetDirconfig.getSearchBaseDn()); 687 688 // step #1: fetch ids referenced by static attributes 689 String staticAttributeId = getStaticAttributeId(); 690 Attribute staticAttribute = null; 691 if (staticAttributeId != null) { 692 staticAttribute = attributes.get(staticAttributeId); 693 } 694 695 if (staticAttribute != null && !staticAttributeIdIsDn) { 696 NamingEnumeration<?> staticContent = staticAttribute.getAll(); 697 try { 698 while (staticContent.hasMore()) { 699 String value = staticContent.next().toString(); 700 if (!emptyRefMarker.equals(value)) { 701 targetIds.add(value); 702 } 703 } 704 } finally { 705 staticContent.close(); 706 } 707 } 708 709 if (staticAttribute != null && staticAttributeIdIsDn) { 710 NamingEnumeration<?> targetDns = staticAttribute.getAll(); 711 try { 712 while (targetDns.hasMore()) { 713 String targetDn = targetDns.next().toString(); 714 715 if (!pseudoNormalizeDn(targetDn).endsWith(baseDn)) { 716 // optim: avoid network connections when obvious 717 if (log.isTraceEnabled()) { 718 log.trace(String.format("ignoring: dn='%s' (does not match '%s') for '%s'", targetDn, 719 baseDn, this)); 720 } 721 continue; 722 } 723 // find the id of the referenced entry 724 String id; 725 726 if (targetSession.rdnMatchesIdField()) { 727 // optim: do not fetch the entry to get its true id 728 // but 729 // guess it by reading the targetDn 730 LdapName name = new LdapName(targetDn); 731 String rdn = name.get(name.size() - 1); 732 int pos = rdn.indexOf("="); 733 id = rdn.substring(pos + 1); 734 } else { 735 id = getIdForDn(targetSession, targetDn); 736 if (id == null) { 737 log.warn(String.format( 738 "ignoring target '%s' (missing attribute '%s') while resolving reference '%s'", 739 targetDn, targetSession.idAttribute, this)); 740 continue; 741 } 742 } 743 if (forceDnConsistencyCheck) { 744 // check that the referenced entry is actually part 745 // of 746 // the target directory (takes care of the filters 747 // and 748 // the scope) 749 // this check can be very expensive on large groups 750 // and thus not enabled by default 751 if (!targetSession.hasEntry(id)) { 752 if (log.isTraceEnabled()) { 753 log.trace( 754 String.format( 755 "ignoring target '%s' when resolving '%s' (not part of target" 756 + " directory by forced DN consistency check)", 757 targetDn, this)); 758 } 759 continue; 760 } 761 } 762 // NXP-2461: check that id field is filled 763 if (id != null) { 764 targetIds.add(id); 765 } 766 } 767 } finally { 768 targetDns.close(); 769 } 770 } 771 // step #2: fetched dynamically referenced ids 772 String dynamicAttributeId = this.dynamicAttributeId; 773 Attribute dynamicAttribute = null; 774 if (dynamicAttributeId != null) { 775 dynamicAttribute = attributes.get(dynamicAttributeId); 776 } 777 if (dynamicAttribute != null) { 778 NamingEnumeration<?> rawldapUrls = dynamicAttribute.getAll(); 779 try { 780 while (rawldapUrls.hasMore()) { 781 LdapURL ldapUrl = new LdapURL(rawldapUrls.next().toString()); 782 String linkDn = pseudoNormalizeDn(ldapUrl.getDn().getUpName()); 783 String directoryDn = pseudoNormalizeDn(targetDirconfig.getSearchBaseDn()); 784 int scope = SearchControls.ONELEVEL_SCOPE; 785 int scopePart = ldapUrl.getScope(); 786 if (scopePart == SearchControls.SUBTREE_SCOPE) { 787 scope = SearchControls.SUBTREE_SCOPE; 788 } 789 if (!linkDn.endsWith(directoryDn) && !directoryDn.endsWith(linkDn)) { 790 // optim #1: if the dns do not match, abort 791 continue; 792 } else if (directoryDn.endsWith(linkDn) && linkDn.length() < directoryDn.length() 793 && scope == SearchControls.ONELEVEL_SCOPE) { 794 // optim #2: the link dn is pointing to elements 795 // that at 796 // upperlevel than directory elements 797 continue; 798 } else { 799 800 // Search for references elements 801 targetIds.addAll( 802 getReferencedElements(attributes, directoryDn, linkDn, ldapUrl.getFilter(), scope)); 803 804 } 805 } 806 } finally { 807 rawldapUrls.close(); 808 } 809 } 810 811 if (dynamicReferences != null && dynamicReferences.length > 0) { 812 813 // Only the first Dynamic Reference is used 814 LDAPDynamicReferenceDescriptor dynAtt = dynamicReferences[0]; 815 816 Attribute baseDnsAttribute = attributes.get(dynAtt.baseDN); 817 Attribute filterAttribute = attributes.get(dynAtt.filter); 818 819 if (baseDnsAttribute != null && filterAttribute != null) { 820 821 NamingEnumeration<?> baseDns = null; 822 NamingEnumeration<?> filters = null; 823 824 try { 825 // Get the BaseDN value from the descriptor 826 baseDns = baseDnsAttribute.getAll(); 827 String linkDnValue = baseDns.next().toString(); 828 baseDns.close(); 829 linkDnValue = pseudoNormalizeDn(linkDnValue); 830 831 // Get the filter value from the descriptor 832 filters = filterAttribute.getAll(); 833 String filterValue = filters.next().toString(); 834 filters.close(); 835 836 // Get the scope value from the descriptor 837 int scope = "subtree".equalsIgnoreCase(dynAtt.type) ? SearchControls.SUBTREE_SCOPE 838 : SearchControls.ONELEVEL_SCOPE; 839 840 String directoryDn = pseudoNormalizeDn(targetDirconfig.getSearchBaseDn()); 841 842 // if the dns match, and if the link dn is pointing to 843 // elements that at upperlevel than directory elements 844 if ((linkDnValue.endsWith(directoryDn) || directoryDn.endsWith(linkDnValue)) 845 && !(directoryDn.endsWith(linkDnValue) && linkDnValue.length() < directoryDn.length() 846 && scope == SearchControls.ONELEVEL_SCOPE)) { 847 848 // Correct the filter expression 849 filterValue = FilterExpressionCorrector.correctFilter(filterValue, FilterJobs.CORRECT_NOT); 850 851 // Search for references elements 852 targetIds.addAll( 853 getReferencedElements(attributes, directoryDn, linkDnValue, filterValue, scope)); 854 855 } 856 } finally { 857 if (baseDns != null) { 858 baseDns.close(); 859 } 860 861 if (filters != null) { 862 filters.close(); 863 } 864 } 865 866 } 867 868 } 869 // return merged attributes 870 return new ArrayList<>(targetIds); 871 } catch (NamingException | LdapURLEncodingException e) { 872 throw new DirectoryException("error computing LDAP references", e); 873 } 874 } 875 876 protected String getIdForDn(LDAPSession session, String dn) { 877 // the entry id is not based on the rdn, we thus need to 878 // fetch the LDAP entry to grab it 879 String[] attributeIdsToCollect = { session.idAttribute }; 880 Attributes entry; 881 try { 882 883 if (log.isDebugEnabled()) { 884 log.debug(String.format( 885 "LDAPReference.getIdForDn(session, %s): LDAP get dn='%s'" 886 + " attribute ids to collect='%s' [%s]", 887 dn, dn, StringUtils.join(attributeIdsToCollect, ", "), this)); 888 } 889 890 Name name = new CompositeName().add(dn); 891 entry = session.getContext().getAttributes(name, attributeIdsToCollect); 892 } catch (NamingException e) { 893 return null; 894 } 895 // NXP-2461: check that id field is filled 896 Attribute attr = entry.get(session.idAttribute); 897 if (attr != null) { 898 try { 899 return attr.get().toString(); 900 } catch (NamingException e) { 901 } 902 } 903 return null; 904 } 905 906 /** 907 * Retrieve the elements referenced by the filter/BaseDN/Scope request. 908 * 909 * @param attributes Attributes of the referencer element 910 * @param directoryDn Dn of the Directory 911 * @param linkDn Dn specified in the parent 912 * @param filter Filter expression specified in the parent 913 * @param scope scope for the search 914 * @return The list of the referenced elements. 915 */ 916 private Set<String> getReferencedElements(Attributes attributes, String directoryDn, String linkDn, String filter, 917 int scope) throws DirectoryException, NamingException { 918 919 Set<String> targetIds = new TreeSet<>(); 920 921 LDAPDirectoryDescriptor targetDirconfig = getTargetDirectoryDescriptor(); 922 LDAPDirectory ldapTargetDirectory = (LDAPDirectory) getTargetDirectory(); 923 LDAPSession targetSession = (LDAPSession) ldapTargetDirectory.getSession(); 924 925 // use the most specific scope between the one specified in the 926 // Directory and the specified in the Parent 927 String dn = directoryDn.endsWith(linkDn) && directoryDn.length() > linkDn.length() ? directoryDn : linkDn; 928 929 // combine the ldapUrl search query with target 930 // directory own constraints 931 SearchControls scts = new SearchControls(); 932 933 // use the most specific scope 934 scts.setSearchScope(Math.min(scope, targetDirconfig.getSearchScope())); 935 936 // only fetch the ids of the targets 937 scts.setReturningAttributes(new String[] { targetSession.idAttribute }); 938 939 // combine the filter of the target directory with the 940 // provided filter if any 941 String targetFilter = targetDirconfig.getSearchFilter(); 942 if (filter == null || filter.length() == 0) { 943 filter = targetFilter; 944 } else if (targetFilter != null && targetFilter.length() > 0) { 945 filter = String.format("(&(%s)(%s))", targetFilter, filter); 946 } 947 948 // perform the request and collect the ids 949 if (log.isDebugEnabled()) { 950 log.debug(String.format( 951 "LDAPReference.getLdapTargetIds(%s): LDAP search dn='%s' " + " filter='%s' scope='%s' [%s]", 952 attributes, dn, dn, scts.getSearchScope(), this)); 953 } 954 955 Name name = new CompositeName().add(dn); 956 NamingEnumeration<SearchResult> results = targetSession.getContext().search(name, filter, scts); 957 try { 958 while (results.hasMore()) { 959 // NXP-2461: check that id field is filled 960 Attribute attr = results.next().getAttributes().get(targetSession.idAttribute); 961 if (attr != null) { 962 String collectedId = attr.get().toString(); 963 if (collectedId != null) { 964 targetIds.add(collectedId); 965 } 966 } 967 968 } 969 } finally { 970 results.close(); 971 } 972 973 return targetIds; 974 } 975 976 /** 977 * Remove existing statically defined links for the given source id (dynamic references remain unaltered) 978 * 979 * @see org.nuxeo.ecm.directory.Reference#removeLinksForSource(String) 980 */ 981 @Override 982 public void removeLinksForSource(String sourceId) throws DirectoryException { 983 LDAPDirectory ldapTargetDirectory = (LDAPDirectory) getTargetDirectory(); 984 LDAPDirectory ldapSourceDirectory = (LDAPDirectory) getSourceDirectory(); 985 String attributeId = getStaticAttributeId(); 986 try (LDAPSession sourceSession = (LDAPSession) ldapSourceDirectory.getSession(); 987 LDAPSession targetSession = (LDAPSession) ldapTargetDirectory.getSession()) { 988 if (sourceSession.isReadOnly() || attributeId == null) { 989 // do not try to do anything on a read only server or to a 990 // purely dynamic reference 991 return; 992 } 993 // get the dn of the entry that matches sourceId 994 SearchResult sourceLdapEntry = sourceSession.getLdapEntry(sourceId); 995 if (sourceLdapEntry == null) { 996 throw new DirectoryException( 997 String.format("cannot edit the links hold by missing entry '%s' in directory '%s'", sourceId, 998 ldapSourceDirectory.getName())); 999 } 1000 String sourceDn = pseudoNormalizeDn(sourceLdapEntry.getNameInNamespace()); 1001 1002 Attribute oldAttr = sourceLdapEntry.getAttributes().get(attributeId); 1003 if (oldAttr == null) { 1004 // consider it as an empty attribute to simplify the following 1005 // code 1006 oldAttr = new BasicAttribute(attributeId); 1007 } 1008 Attribute attrToRemove = new BasicAttribute(attributeId); 1009 1010 NamingEnumeration<?> oldAttrs = oldAttr.getAll(); 1011 String targetBaseDn = pseudoNormalizeDn(ldapTargetDirectory.getDescriptor().getSearchBaseDn()); 1012 try { 1013 while (oldAttrs.hasMore()) { 1014 String targetKeyAttr = oldAttrs.next().toString(); 1015 1016 if (staticAttributeIdIsDn) { 1017 String dn = pseudoNormalizeDn(targetKeyAttr); 1018 if (forceDnConsistencyCheck) { 1019 String id = getIdForDn(targetSession, dn); 1020 if (id != null && targetSession.hasEntry(id)) { 1021 // this is an entry managed by the current 1022 // reference 1023 attrToRemove.add(dn); 1024 } 1025 } else if (dn.endsWith(targetBaseDn)) { 1026 // this is an entry managed by the current 1027 // reference 1028 attrToRemove.add(dn); 1029 } 1030 } else { 1031 attrToRemove.add(targetKeyAttr); 1032 } 1033 } 1034 } finally { 1035 oldAttrs.close(); 1036 } 1037 try { 1038 if (attrToRemove.size() == oldAttr.size()) { 1039 // use the empty ref marker to avoid empty attr 1040 String emptyRefMarker = ldapSourceDirectory.getDescriptor().getEmptyRefMarker(); 1041 Attributes emptyAttribute = new BasicAttributes(attributeId, emptyRefMarker); 1042 if (log.isDebugEnabled()) { 1043 log.debug(String.format( 1044 "LDAPReference.removeLinksForSource(%s): LDAP modifyAttributes key='%s' " 1045 + " mod_op='REPLACE_ATTRIBUTE' attrs='%s' [%s]", 1046 sourceId, sourceDn, emptyAttribute, this)); 1047 } 1048 sourceSession.getContext().modifyAttributes(sourceDn, DirContext.REPLACE_ATTRIBUTE, emptyAttribute); 1049 } else if (attrToRemove.size() > 0) { 1050 // remove the attribute managed by the current reference 1051 Attributes attrsToRemove = new BasicAttributes(); 1052 attrsToRemove.put(attrToRemove); 1053 if (log.isDebugEnabled()) { 1054 log.debug(String.format( 1055 "LDAPReference.removeLinksForSource(%s): LDAP modifyAttributes dn='%s' " 1056 + " mod_op='REMOVE_ATTRIBUTE' attrs='%s' [%s]", 1057 sourceId, sourceDn, attrsToRemove, this)); 1058 } 1059 sourceSession.getContext().modifyAttributes(sourceDn, DirContext.REMOVE_ATTRIBUTE, attrsToRemove); 1060 } 1061 } catch (SchemaViolationException e) { 1062 if (isDynamic()) { 1063 // we are editing an entry that has no static part 1064 log.warn(String.format("cannot remove dynamic reference in field %s for source %s", getFieldName(), 1065 sourceId)); 1066 } else { 1067 // this is a real schma configuration problem, wrapup the 1068 // exception 1069 throw new DirectoryException(e); 1070 } 1071 } 1072 } catch (NamingException e) { 1073 throw new DirectoryException("removeLinksForSource failed: " + e.getMessage(), e); 1074 } 1075 } 1076 1077 /** 1078 * Remove existing statically defined links for the given target id (dynamic references remain unaltered) 1079 * 1080 * @see org.nuxeo.ecm.directory.Reference#removeLinksForTarget(String) 1081 */ 1082 @Override 1083 public void removeLinksForTarget(String targetId) throws DirectoryException { 1084 if (!isStatic()) { 1085 // nothing to do: dynamic references cannot be updated 1086 return; 1087 } 1088 LDAPDirectory ldapTargetDirectory = (LDAPDirectory) getTargetDirectory(); 1089 LDAPDirectory ldapSourceDirectory = (LDAPDirectory) getSourceDirectory(); 1090 String attributeId = getStaticAttributeId(); 1091 try (LDAPSession targetSession = (LDAPSession) ldapTargetDirectory.getSession(); 1092 LDAPSession sourceSession = (LDAPSession) ldapSourceDirectory.getSession()) { 1093 if (!sourceSession.isReadOnly()) { 1094 // get the dn of the target that matches targetId 1095 String targetAttributeValue; 1096 1097 if (staticAttributeIdIsDn) { 1098 SearchResult targetLdapEntry = targetSession.getLdapEntry(targetId); 1099 if (targetLdapEntry == null) { 1100 String rdnAttribute = ldapTargetDirectory.getDescriptor().getRdnAttribute(); 1101 if (!rdnAttribute.equals(targetSession.idAttribute)) { 1102 log.warn(String.format( 1103 "cannot remove links to missing entry %s in directory %s for reference %s", 1104 targetId, ldapTargetDirectory.getName(), this)); 1105 return; 1106 } 1107 // the entry might have already been deleted, try to 1108 // re-forge it if possible (might not work if scope is 1109 // subtree) 1110 targetAttributeValue = String.format("%s=%s,%s", rdnAttribute, targetId, 1111 ldapTargetDirectory.getDescriptor().getSearchBaseDn()); 1112 } else { 1113 targetAttributeValue = pseudoNormalizeDn(targetLdapEntry.getNameInNamespace()); 1114 } 1115 } else { 1116 targetAttributeValue = targetId; 1117 } 1118 1119 // build a LDAP query to find entries that point to the target 1120 String searchFilter = String.format("(%s=%s)", attributeId, targetAttributeValue); 1121 String sourceFilter = ldapSourceDirectory.getBaseFilter(); 1122 1123 if (sourceFilter != null && !"".equals(sourceFilter)) { 1124 searchFilter = String.format("(&(%s)(%s))", searchFilter, sourceFilter); 1125 } 1126 1127 SearchControls scts = new SearchControls(); 1128 scts.setSearchScope(ldapSourceDirectory.getDescriptor().getSearchScope()); 1129 scts.setReturningAttributes(new String[] { attributeId }); 1130 1131 // find all source entries that point to the target key and 1132 // clean 1133 // those references 1134 if (log.isDebugEnabled()) { 1135 log.debug(String.format( 1136 "LDAPReference.removeLinksForTarget(%s): LDAP search baseDn='%s' " 1137 + " filter='%s' scope='%s' [%s]", 1138 targetId, sourceSession.searchBaseDn, searchFilter, scts.getSearchScope(), this)); 1139 } 1140 NamingEnumeration<SearchResult> results = sourceSession.getContext().search(sourceSession.searchBaseDn, 1141 searchFilter, scts); 1142 String emptyRefMarker = ldapSourceDirectory.getDescriptor().getEmptyRefMarker(); 1143 Attributes emptyAttribute = new BasicAttributes(attributeId, emptyRefMarker); 1144 1145 try { 1146 while (results.hasMore()) { 1147 SearchResult result = results.next(); 1148 Attributes attrs = result.getAttributes(); 1149 Attribute attr = attrs.get(attributeId); 1150 try { 1151 if (attr.size() == 1) { 1152 // the attribute holds the last reference, put 1153 // the 1154 // empty ref. marker before removing the 1155 // attribute 1156 // since empty attribute are often not allowed 1157 // by 1158 // the server schema 1159 if (log.isDebugEnabled()) { 1160 log.debug(String.format( 1161 "LDAPReference.removeLinksForTarget(%s): LDAP modifyAttributes key='%s' " 1162 + "mod_op='ADD_ATTRIBUTE' attrs='%s' [%s]", 1163 targetId, result.getNameInNamespace(), attrs, this)); 1164 } 1165 sourceSession.getContext().modifyAttributes(result.getNameInNamespace(), 1166 DirContext.ADD_ATTRIBUTE, emptyAttribute); 1167 } 1168 // remove the reference to the target key 1169 attrs = new BasicAttributes(); 1170 attr = new BasicAttribute(attributeId); 1171 attr.add(targetAttributeValue); 1172 attrs.put(attr); 1173 if (log.isDebugEnabled()) { 1174 log.debug(String.format( 1175 "LDAPReference.removeLinksForTarget(%s): LDAP modifyAttributes key='%s' " 1176 + "mod_op='REMOVE_ATTRIBUTE' attrs='%s' [%s]", 1177 targetId, result.getNameInNamespace(), attrs, this)); 1178 } 1179 sourceSession.getContext().modifyAttributes(result.getNameInNamespace(), 1180 DirContext.REMOVE_ATTRIBUTE, attrs); 1181 } catch (SchemaViolationException e) { 1182 if (isDynamic()) { 1183 // we are editing an entry that has no static 1184 // part 1185 log.warn(String.format("cannot remove dynamic reference in field %s for target %s", 1186 getFieldName(), targetId)); 1187 } else { 1188 // this is a real schema configuration problem, 1189 // wrapup the exception 1190 throw new DirectoryException(e); 1191 } 1192 } 1193 } 1194 } finally { 1195 results.close(); 1196 } 1197 } 1198 } catch (NamingException e) { 1199 throw new DirectoryException("removeLinksForTarget failed: " + e.getMessage(), e); 1200 } 1201 } 1202 1203 /** 1204 * Edit the list of statically defined references for a given target (dynamic references remain unaltered) 1205 * 1206 * @see org.nuxeo.ecm.directory.Reference#setSourceIdsForTarget(String, List) 1207 */ 1208 @Override 1209 public void setSourceIdsForTarget(String targetId, List<String> sourceIds) throws DirectoryException { 1210 removeLinksForTarget(targetId); 1211 addLinks(sourceIds, targetId); 1212 } 1213 1214 @Override 1215 public void setSourceIdsForTarget(String targetId, List<String> sourceIds, Session session) 1216 throws DirectoryException { 1217 setSourceIdsForTarget(targetId, sourceIds); 1218 } 1219 1220 /** 1221 * Set the list of statically defined references for a given source (dynamic references remain unaltered) 1222 * 1223 * @see org.nuxeo.ecm.directory.Reference#setTargetIdsForSource(String, List) 1224 */ 1225 @Override 1226 public void setTargetIdsForSource(String sourceId, List<String> targetIds) throws DirectoryException { 1227 removeLinksForSource(sourceId); 1228 addLinks(sourceId, targetIds); 1229 } 1230 1231 @Override 1232 public void setTargetIdsForSource(String sourceId, List<String> targetIds, Session session) 1233 throws DirectoryException { 1234 setTargetIdsForSource(sourceId, targetIds); 1235 } 1236 1237 @Override 1238 public void removeLinksForTarget(String targetId, Session session) throws DirectoryException { 1239 removeLinksForTarget(targetId); 1240 } 1241 1242 @Override 1243 public void removeLinksForSource(String sourceId, Session session) throws DirectoryException { 1244 removeLinksForSource(sourceId); 1245 } 1246 1247 @Override 1248 // to build helpful debug logs 1249 public String toString() { 1250 return String.format( 1251 "LDAPReference to resolve field='%s' of sourceDirectory='%s'" + " with targetDirectory='%s'" 1252 + " and staticAttributeId='%s', dynamicAttributeId='%s'", 1253 fieldName, sourceDirectoryName, targetDirectoryName, staticAttributeId, dynamicAttributeId); 1254 } 1255 1256 /** 1257 * @since 5.6 1258 */ 1259 @Override 1260 public LDAPReference clone() { 1261 try { 1262 // basic fields are already copied by super.clone() 1263 return (LDAPReference) super.clone(); 1264 } catch (CloneNotSupportedException e) { 1265 throw new AssertionError(e); 1266 } 1267 } 1268 1269 @Override 1270 public void addLinks(String sourceId, List<String> targetIds, Session session) throws DirectoryException { 1271 // TODO to use for optimization 1272 addLinks(sourceId, targetIds); 1273 } 1274 1275 @Override 1276 public void addLinks(List<String> sourceIds, String targetId, Session session) throws DirectoryException { 1277 // TODO to use for optimization 1278 addLinks(sourceIds, targetId); 1279 } 1280 1281}