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