001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2008-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.workflowelement.localbackend; 018 019import java.util.HashSet; 020import java.util.List; 021import java.util.Map; 022import java.util.concurrent.atomic.AtomicBoolean; 023 024import org.forgerock.i18n.LocalizableMessage; 025import org.forgerock.i18n.LocalizableMessageBuilder; 026import org.forgerock.i18n.slf4j.LocalizedLogger; 027import org.forgerock.opendj.ldap.AVA; 028import org.forgerock.opendj.ldap.ByteString; 029import org.forgerock.opendj.ldap.DN; 030import org.forgerock.opendj.ldap.ResultCode; 031import org.forgerock.opendj.ldap.schema.AttributeType; 032import org.forgerock.opendj.ldap.schema.Syntax; 033import org.opends.server.api.AccessControlHandler; 034import org.opends.server.api.AuthenticationPolicy; 035import org.opends.server.api.Backend; 036import org.opends.server.api.ClientConnection; 037import org.opends.server.api.PasswordStorageScheme; 038import org.opends.server.api.PasswordValidator; 039import org.opends.server.api.SynchronizationProvider; 040import org.opends.server.controls.LDAPAssertionRequestControl; 041import org.opends.server.controls.LDAPPostReadRequestControl; 042import org.opends.server.controls.PasswordPolicyErrorType; 043import org.opends.server.controls.PasswordPolicyResponseControl; 044import org.opends.server.core.AccessControlConfigManager; 045import org.opends.server.core.AddOperation; 046import org.opends.server.core.AddOperationWrapper; 047import org.opends.server.core.DirectoryServer; 048import org.opends.server.core.PasswordPolicy; 049import org.opends.server.core.PersistentSearch; 050import org.opends.server.schema.AuthPasswordSyntax; 051import org.opends.server.schema.UserPasswordSyntax; 052import org.opends.server.types.Attribute; 053import org.opends.server.types.AttributeBuilder; 054import org.opends.server.types.Attributes; 055import org.opends.server.types.CanceledOperationException; 056import org.opends.server.types.Control; 057import org.opends.server.types.DirectoryException; 058import org.opends.server.types.Entry; 059import org.opends.server.types.LockManager.DNLock; 060import org.opends.server.types.ObjectClass; 061import org.opends.server.types.Privilege; 062import org.opends.server.types.SearchFilter; 063import org.opends.server.types.operation.PostOperationAddOperation; 064import org.opends.server.types.operation.PostResponseAddOperation; 065import org.opends.server.types.operation.PostSynchronizationAddOperation; 066import org.opends.server.types.operation.PreOperationAddOperation; 067import org.opends.server.util.TimeThread; 068 069import static org.opends.messages.CoreMessages.*; 070import static org.opends.server.config.ConfigConstants.*; 071import static org.opends.server.core.DirectoryServer.*; 072import static org.opends.server.types.AbstractOperation.*; 073import static org.opends.server.util.CollectionUtils.*; 074import static org.opends.server.util.ServerConstants.*; 075import static org.opends.server.util.StaticUtils.*; 076import static org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement.*; 077 078/** 079 * This class defines an operation used to add an entry in a local backend 080 * of the Directory Server. 081 */ 082public class LocalBackendAddOperation 083 extends AddOperationWrapper 084 implements PreOperationAddOperation, PostOperationAddOperation, 085 PostResponseAddOperation, PostSynchronizationAddOperation 086{ 087 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 088 089 /** The backend in which the entry is to be added. */ 090 private Backend<?> backend; 091 092 /** Indicates whether the request includes the LDAP no-op control. */ 093 private boolean noOp; 094 095 /** The DN of the entry to be added. */ 096 private DN entryDN; 097 098 /** The entry being added to the server. */ 099 private Entry entry; 100 101 /** The post-read request control included in the request, if applicable. */ 102 private LDAPPostReadRequestControl postReadRequest; 103 104 /** The set of object classes for the entry to add. */ 105 private Map<ObjectClass, String> objectClasses; 106 107 /** The set of operational attributes for the entry to add. */ 108 private Map<AttributeType, List<Attribute>> operationalAttributes; 109 110 /** The set of user attributes for the entry to add. */ 111 private Map<AttributeType, List<Attribute>> userAttributes; 112 113 /** 114 * Creates a new operation that may be used to add a new entry in a 115 * local backend of the Directory Server. 116 * 117 * @param add The operation to enhance. 118 */ 119 public LocalBackendAddOperation(AddOperation add) 120 { 121 super(add); 122 123 LocalBackendWorkflowElement.attachLocalOperation (add, this); 124 } 125 126 127 128 /** 129 * Retrieves the entry to be added to the server. Note that this will not be 130 * available to pre-parse plugins or during the conflict resolution portion of 131 * the synchronization processing. 132 * 133 * @return The entry to be added to the server, or <CODE>null</CODE> if it is 134 * not yet available. 135 */ 136 @Override 137 public final Entry getEntryToAdd() 138 { 139 return entry; 140 } 141 142 143 144 /** 145 * Process this add operation against a local backend. 146 * 147 * @param wfe 148 * The local backend work-flow element. 149 * @throws CanceledOperationException 150 * if this operation should be cancelled 151 */ 152 public void processLocalAdd(final LocalBackendWorkflowElement wfe) 153 throws CanceledOperationException 154 { 155 this.backend = wfe.getBackend(); 156 ClientConnection clientConnection = getClientConnection(); 157 158 // Check for a request to cancel this operation. 159 checkIfCanceled(false); 160 161 try 162 { 163 AtomicBoolean executePostOpPlugins = new AtomicBoolean(false); 164 processAdd(clientConnection, executePostOpPlugins); 165 166 // Invoke the post-operation or post-synchronization add plugins. 167 if (isSynchronizationOperation()) 168 { 169 if (getResultCode() == ResultCode.SUCCESS) 170 { 171 getPluginConfigManager().invokePostSynchronizationAddPlugins(this); 172 } 173 } 174 else if (executePostOpPlugins.get()) 175 { 176 // FIXME -- Should this also be done while holding the locks? 177 if (!processOperationResult(this, getPluginConfigManager().invokePostOperationAddPlugins(this))) 178 { 179 return; 180 } 181 } 182 } 183 finally 184 { 185 LocalBackendWorkflowElement.filterNonDisclosableMatchedDN(this); 186 } 187 188 // Register a post-response call-back which will notify persistent 189 // searches and change listeners. 190 if (getResultCode() == ResultCode.SUCCESS) 191 { 192 registerPostResponseCallback(new Runnable() 193 { 194 @Override 195 public void run() 196 { 197 for (PersistentSearch psearch : backend.getPersistentSearches()) 198 { 199 psearch.processAdd(entry); 200 } 201 } 202 }); 203 } 204 } 205 206 private void processAdd(ClientConnection clientConnection, 207 AtomicBoolean executePostOpPlugins) throws CanceledOperationException 208 { 209 // Process the entry DN and set of attributes to convert them from their 210 // raw forms as provided by the client to the forms required for the rest 211 // of the add processing. 212 entryDN = getEntryDN(); 213 if (entryDN == null) 214 { 215 return; 216 } 217 218 // Check for a request to cancel this operation. 219 checkIfCanceled(false); 220 221 // Grab a write lock on the target entry. We'll need to do this 222 // eventually anyway, and we want to make sure that the two locks are 223 // always released when exiting this method, no matter what. Since 224 // the entry shouldn't exist yet, locking earlier than necessary 225 // shouldn't cause a problem. 226 final DNLock entryLock = DirectoryServer.getLockManager().tryWriteLockEntry(entryDN); 227 try 228 { 229 if (entryLock == null) 230 { 231 setResultCode(ResultCode.BUSY); 232 appendErrorMessage(ERR_ADD_CANNOT_LOCK_ENTRY.get(entryDN)); 233 return; 234 } 235 236 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 237 if (parentDN == null && !DirectoryServer.isNamingContext(entryDN)) 238 { 239 if (entryDN.isRootDN()) 240 { 241 // This is not fine. The root DSE cannot be added. 242 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_ADD_CANNOT_ADD_ROOT_DSE.get()); 243 } 244 else 245 { 246 // The entry doesn't have a parent but isn't a suffix. This is not 247 // allowed. 248 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, ERR_ADD_ENTRY_NOT_SUFFIX.get(entryDN)); 249 } 250 } 251 252 // Check for a request to cancel this operation. 253 checkIfCanceled(false); 254 255 256 // Invoke any conflict resolution processing that might be needed by the 257 // synchronization provider. 258 for (SynchronizationProvider<?> provider : getSynchronizationProviders()) 259 { 260 try 261 { 262 if (!processOperationResult(this, provider.handleConflictResolution(this))) 263 { 264 return; 265 } 266 } 267 catch (DirectoryException de) 268 { 269 logger.error(ERR_ADD_SYNCH_CONFLICT_RESOLUTION_FAILED, 270 getConnectionID(), getOperationID(), getExceptionMessage(de)); 271 throw de; 272 } 273 } 274 275 objectClasses = getObjectClasses(); 276 userAttributes = getUserAttributes(); 277 operationalAttributes = getOperationalAttributes(); 278 279 if (objectClasses == null 280 || userAttributes == null 281 || operationalAttributes == null) 282 { 283 return; 284 } 285 286 // If the attribute type is marked "NO-USER-MODIFICATION" then fail 287 // unless this is an internal operation or is related to 288 // synchronization in some way. 289 // This must be done before running the password policy code 290 // and any other code that may add attributes marked as 291 // "NO-USER-MODIFICATION" 292 // 293 // Note that doing this checks at this time 294 // of the processing does not make it possible for pre-parse plugins 295 // to add NO-USER-MODIFICATION attributes to the entry. 296 if (checkHasReadOnlyAttributes(userAttributes) 297 || checkHasReadOnlyAttributes(operationalAttributes)) 298 { 299 return; 300 } 301 302 303 // Check to see if the entry already exists. We do this before 304 // checking whether the parent exists to ensure a referral entry 305 // above the parent results in a correct referral. 306 if (DirectoryServer.entryExists(entryDN)) 307 { 308 setResultCodeAndMessageNoInfoDisclosure(entryDN, 309 ResultCode.ENTRY_ALREADY_EXISTS, 310 ERR_ADD_ENTRY_ALREADY_EXISTS.get(entryDN)); 311 return; 312 } 313 314 // Get the parent entry, if it exists. 315 Entry parentEntry = null; 316 if (parentDN != null) 317 { 318 parentEntry = DirectoryServer.getEntry(parentDN); 319 320 if (parentEntry == null) 321 { 322 final DN matchedDN = findMatchedDN(parentDN); 323 setMatchedDN(matchedDN); 324 325 // The parent doesn't exist, so this add can't be successful. 326 if (matchedDN != null) 327 { 328 // check whether matchedDN allows to disclose info 329 setResultCodeAndMessageNoInfoDisclosure(matchedDN, 330 ResultCode.NO_SUCH_OBJECT, ERR_ADD_NO_PARENT.get(entryDN, parentDN)); 331 } 332 else 333 { 334 // no matched DN either, so let's return normal error code 335 setResultCode(ResultCode.NO_SUCH_OBJECT); 336 appendErrorMessage(ERR_ADD_NO_PARENT.get(entryDN, parentDN)); 337 } 338 return; 339 } 340 } 341 342 // Check to make sure that all of the RDN attributes are included as 343 // attribute values. If not, then either add them or report an error. 344 addRDNAttributesIfNecessary(); 345 346 // Add any superior objectclass(s) missing in an entries 347 // objectclass map. 348 addSuperiorObjectClasses(objectClasses); 349 350 // Create an entry object to encapsulate the set of attributes and 351 // objectclasses. 352 entry = new Entry(entryDN, objectClasses, userAttributes, 353 operationalAttributes); 354 355 // Check to see if the entry includes a privilege specification. If so, 356 // then the requester must have the PRIVILEGE_CHANGE privilege. 357 AttributeType privType = DirectoryServer.getAttributeType(OP_ATTR_PRIVILEGE_NAME); 358 if (entry.hasAttribute(privType) 359 && !clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE, this)) 360 { 361 appendErrorMessage(ERR_ADD_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES.get()); 362 setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); 363 return; 364 } 365 366 // If it's not a synchronization operation, then check 367 // to see if the entry contains one or more passwords and if they 368 // are valid in accordance with the password policies associated with 369 // the user. Also perform any encoding that might be required by 370 // password storage schemes. 371 if (!isSynchronizationOperation()) 372 { 373 handlePasswordPolicy(); 374 } 375 376 // If the server is configured to check schema and the 377 // operation is not a synchronization operation, 378 // check to see if the entry is valid according to the server schema, 379 // and also whether its attributes are valid according to their syntax. 380 if (DirectoryServer.checkSchema() && !isSynchronizationOperation()) 381 { 382 checkSchema(parentEntry); 383 } 384 385 // Get the backend in which the add is to be performed. 386 if (backend == null) 387 { 388 setResultCode(ResultCode.NO_SUCH_OBJECT); 389 appendErrorMessage(LocalizableMessage.raw("No backend for entry " + entryDN)); // TODO: i18n 390 return; 391 } 392 393 // Check to see if there are any controls in the request. If so, then 394 // see if there is any special processing required. 395 processControls(parentDN); 396 397 // Check to see if the client has permission to perform the add. 398 399 // FIXME: for now assume that this will check all permission 400 // pertinent to the operation. This includes proxy authorization 401 // and any other controls specified. 402 403 // FIXME: earlier checks to see if the entry already exists or 404 // if the parent entry does not exist may have already exposed 405 // sensitive information to the client. 406 try 407 { 408 if (!getAccessControlHandler().isAllowed(this)) 409 { 410 setResultCodeAndMessageNoInfoDisclosure(entryDN, 411 ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 412 ERR_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(entryDN)); 413 return; 414 } 415 } 416 catch (DirectoryException e) 417 { 418 setResultCode(e.getResultCode()); 419 appendErrorMessage(e.getMessageObject()); 420 return; 421 } 422 423 // Check for a request to cancel this operation. 424 checkIfCanceled(false); 425 426 // If the operation is not a synchronization operation, 427 // Invoke the pre-operation add plugins. 428 if (!isSynchronizationOperation()) 429 { 430 executePostOpPlugins.set(true); 431 if (!processOperationResult(this, getPluginConfigManager().invokePreOperationAddPlugins(this))) 432 { 433 return; 434 } 435 } 436 437 LocalBackendWorkflowElement.checkIfBackendIsWritable(backend, this, 438 entryDN, ERR_ADD_SERVER_READONLY, ERR_ADD_BACKEND_READONLY); 439 440 if (noOp) 441 { 442 appendErrorMessage(INFO_ADD_NOOP.get()); 443 setResultCode(ResultCode.NO_OPERATION); 444 } 445 else 446 { 447 for (SynchronizationProvider<?> provider : getSynchronizationProviders()) 448 { 449 try 450 { 451 if (!processOperationResult(this, provider.doPreOperation(this))) 452 { 453 return; 454 } 455 } 456 catch (DirectoryException de) 457 { 458 logger.error(ERR_ADD_SYNCH_PREOP_FAILED, getConnectionID(), 459 getOperationID(), getExceptionMessage(de)); 460 throw de; 461 } 462 } 463 464 backend.addEntry(entry, this); 465 } 466 467 LocalBackendWorkflowElement.addPostReadResponse(this, postReadRequest, 468 entry); 469 470 if (!noOp) 471 { 472 setResultCode(ResultCode.SUCCESS); 473 } 474 } 475 catch (DirectoryException de) 476 { 477 logger.traceException(de); 478 479 setResponseData(de); 480 } 481 finally 482 { 483 if (entryLock != null) 484 { 485 entryLock.unlock(); 486 } 487 processSynchPostOperationPlugins(); 488 } 489 } 490 491 492 493 private void processSynchPostOperationPlugins() 494 { 495 for (SynchronizationProvider<?> provider : getSynchronizationProviders()) 496 { 497 try 498 { 499 provider.doPostOperation(this); 500 } 501 catch (DirectoryException de) 502 { 503 logger.traceException(de); 504 logger.error(ERR_ADD_SYNCH_POSTOP_FAILED, getConnectionID(), 505 getOperationID(), getExceptionMessage(de)); 506 setResponseData(de); 507 break; 508 } 509 } 510 } 511 512 private boolean checkHasReadOnlyAttributes( 513 Map<AttributeType, List<Attribute>> attributes) throws DirectoryException 514 { 515 for (AttributeType at : attributes.keySet()) 516 { 517 if (at.isNoUserModification() 518 && !isInternalOperation() 519 && !isSynchronizationOperation()) 520 { 521 setResultCodeAndMessageNoInfoDisclosure(entryDN, 522 ResultCode.CONSTRAINT_VIOLATION, 523 ERR_ADD_ATTR_IS_NO_USER_MOD.get(entryDN, at.getNameOrOID())); 524 return true; 525 } 526 } 527 return false; 528 } 529 530 private DirectoryException newDirectoryException(DN entryDN, 531 ResultCode resultCode, LocalizableMessage message) throws DirectoryException 532 { 533 return LocalBackendWorkflowElement.newDirectoryException(this, null, 534 entryDN, resultCode, message, ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 535 ERR_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(entryDN)); 536 } 537 538 private void setResultCodeAndMessageNoInfoDisclosure(DN entryDN, 539 ResultCode resultCode, LocalizableMessage message) throws DirectoryException 540 { 541 LocalBackendWorkflowElement.setResultCodeAndMessageNoInfoDisclosure(this, 542 null, entryDN, resultCode, message, 543 ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 544 ERR_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(entryDN)); 545 } 546 547 548 549 /** 550 * Adds any missing RDN attributes to the entry. 551 * 552 * @throws DirectoryException If the entry is missing one or more RDN 553 * attributes and the server is configured to 554 * reject such entries. 555 */ 556 private void addRDNAttributesIfNecessary() throws DirectoryException 557 { 558 for (AVA ava : entryDN.rdn()) 559 { 560 AttributeType t = ava.getAttributeType(); 561 addRDNAttributesIfNecessary(t.isOperational() ? operationalAttributes : userAttributes, ava); 562 } 563 } 564 565 566 567 private void addRDNAttributesIfNecessary(Map<AttributeType, List<Attribute>> attributes, AVA ava) 568 throws DirectoryException 569 { 570 AttributeType t = ava.getAttributeType(); 571 String n = ava.getAttributeName(); 572 ByteString v = ava.getAttributeValue(); 573 final List<Attribute> attrList = attributes.get(t); 574 if (attrList == null) 575 { 576 if (!isSynchronizationOperation() 577 && !DirectoryServer.addMissingRDNAttributes()) 578 { 579 throw newDirectoryException(entryDN, ResultCode.CONSTRAINT_VIOLATION, 580 ERR_ADD_MISSING_RDN_ATTRIBUTE.get(entryDN, n)); 581 } 582 attributes.put(t, newArrayList(Attributes.create(t, n, v))); 583 return; 584 } 585 586 for (int j = 0; j < attrList.size(); j++) { 587 Attribute a = attrList.get(j); 588 if (a.hasOptions()) 589 { 590 continue; 591 } 592 593 if (!a.contains(v)) 594 { 595 AttributeBuilder builder = new AttributeBuilder(a); 596 builder.add(v); 597 attrList.set(j, builder.toAttribute()); 598 } 599 600 return; 601 } 602 603 // not found 604 if (!isSynchronizationOperation() && !DirectoryServer.addMissingRDNAttributes()) 605 { 606 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 607 ERR_ADD_MISSING_RDN_ATTRIBUTE.get(entryDN, n)); 608 } 609 attrList.add(Attributes.create(t, n, v)); 610 } 611 612 613 614 /** 615 * Adds the provided objectClass to the entry, along with its superior classes 616 * if appropriate. 617 * 618 * @param objectClass The objectclass to add to the entry. 619 */ 620 public final void addObjectClassChain(ObjectClass objectClass) 621 { 622 Map<ObjectClass, String> objectClasses = getObjectClasses(); 623 if (objectClasses != null){ 624 if (! objectClasses.containsKey(objectClass)) 625 { 626 objectClasses.put(objectClass, objectClass.getNameOrOID()); 627 } 628 629 for(ObjectClass superiorClass : objectClass.getSuperiorClasses()) 630 { 631 if (!objectClasses.containsKey(superiorClass)) 632 { 633 addObjectClassChain(superiorClass); 634 } 635 } 636 } 637 } 638 639 640 641 /** 642 * Performs all password policy processing necessary for the provided add 643 * operation. 644 * 645 * @throws DirectoryException If a problem occurs while performing password 646 * policy processing for the add operation. 647 */ 648 public final void handlePasswordPolicy() 649 throws DirectoryException 650 { 651 // Construct any virtual/collective attributes which might 652 // contain a value for the OP_ATTR_PWPOLICY_POLICY_DN attribute. 653 Entry copy = entry.duplicate(true); 654 AuthenticationPolicy policy = AuthenticationPolicy.forUser(copy, false); 655 if (!policy.isPasswordPolicy()) 656 { 657 // The entry doesn't have a locally managed password, so no action is 658 // required. 659 return; 660 } 661 PasswordPolicy passwordPolicy = (PasswordPolicy) policy; 662 663 // See if a password was specified. 664 AttributeType passwordAttribute = passwordPolicy.getPasswordAttribute(); 665 List<Attribute> attrList = entry.getAttribute(passwordAttribute); 666 if (attrList.isEmpty()) 667 { 668 // The entry doesn't have a password, so no action is required. 669 return; 670 } 671 else if (attrList.size() > 1) 672 { 673 // This must mean there are attribute options, which we won't allow for 674 // passwords. 675 LocalizableMessage message = ERR_PWPOLICY_ATTRIBUTE_OPTIONS_NOT_ALLOWED.get( 676 passwordAttribute.getNameOrOID()); 677 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 678 } 679 680 Attribute passwordAttr = attrList.get(0); 681 if (passwordAttr.hasOptions()) 682 { 683 LocalizableMessage message = ERR_PWPOLICY_ATTRIBUTE_OPTIONS_NOT_ALLOWED.get( 684 passwordAttribute.getNameOrOID()); 685 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 686 } 687 688 if (passwordAttr.isEmpty()) 689 { 690 // This will be treated the same as not having a password. 691 return; 692 } 693 694 if (!isInternalOperation() 695 && !passwordPolicy.isAllowMultiplePasswordValues() 696 && passwordAttr.size() > 1) 697 { 698 // FIXME -- What if they're pre-encoded and might all be the 699 // same? 700 addPWPolicyControl(PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED); 701 702 LocalizableMessage message = ERR_PWPOLICY_MULTIPLE_PW_VALUES_NOT_ALLOWED 703 .get(passwordAttribute.getNameOrOID()); 704 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 705 } 706 707 List<PasswordStorageScheme<?>> defaultStorageSchemes = 708 passwordPolicy.getDefaultPasswordStorageSchemes(); 709 AttributeBuilder builder = new AttributeBuilder(passwordAttr, true); 710 for (ByteString value : passwordAttr) 711 { 712 // See if the password is pre-encoded. 713 if (passwordPolicy.isAuthPasswordSyntax()) 714 { 715 if (AuthPasswordSyntax.isEncoded(value)) 716 { 717 if (isInternalOperation() 718 || passwordPolicy.isAllowPreEncodedPasswords()) 719 { 720 builder.add(value); 721 continue; 722 } 723 else 724 { 725 addPWPolicyControl(PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY); 726 727 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 728 ERR_PWPOLICY_PREENCODED_NOT_ALLOWED.get(passwordAttribute.getNameOrOID())); 729 } 730 } 731 } 732 else if (UserPasswordSyntax.isEncoded(value)) 733 { 734 if (isInternalOperation() 735 || passwordPolicy.isAllowPreEncodedPasswords()) 736 { 737 builder.add(value); 738 continue; 739 } 740 else 741 { 742 addPWPolicyControl(PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY); 743 744 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 745 ERR_PWPOLICY_PREENCODED_NOT_ALLOWED.get(passwordAttribute.getNameOrOID())); 746 } 747 } 748 749 750 // See if the password passes validation. We should only do this if 751 // validation should be performed for administrators. 752 if (! passwordPolicy.isSkipValidationForAdministrators()) 753 { 754 // There are never any current passwords for an add operation. 755 HashSet<ByteString> currentPasswords = new HashSet<>(0); 756 LocalizableMessageBuilder invalidReason = new LocalizableMessageBuilder(); 757 // Work on a copy of the entry without the password to avoid 758 // false positives from some validators. 759 copy.removeAttribute(passwordAttribute); 760 for (PasswordValidator<?> validator : 761 passwordPolicy.getPasswordValidators()) 762 { 763 if (! validator.passwordIsAcceptable(value, currentPasswords, this, 764 copy, invalidReason)) 765 { 766 addPWPolicyControl( 767 PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY); 768 769 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 770 ERR_PWPOLICY_VALIDATION_FAILED.get(passwordAttribute.getNameOrOID(), invalidReason)); 771 } 772 } 773 } 774 775 776 // Encode the password. 777 if (passwordPolicy.isAuthPasswordSyntax()) 778 { 779 for (PasswordStorageScheme<?> s : defaultStorageSchemes) 780 { 781 builder.add(s.encodeAuthPassword(value)); 782 } 783 } 784 else 785 { 786 for (PasswordStorageScheme<?> s : defaultStorageSchemes) 787 { 788 builder.add(s.encodePasswordWithScheme(value)); 789 } 790 } 791 } 792 793 794 // Put the new encoded values in the entry. 795 entry.replaceAttribute(builder.toAttribute()); 796 797 798 // Set the password changed time attribute. 799 Attribute changedTime = Attributes.create( 800 OP_ATTR_PWPOLICY_CHANGED_TIME, TimeThread.getGeneralizedTime()); 801 entry.putAttribute(changedTime.getAttributeDescription().getAttributeType(), newArrayList(changedTime)); 802 803 804 // If we should force change on add, then set the appropriate flag. 805 if (passwordPolicy.isForceChangeOnAdd()) 806 { 807 addPWPolicyControl(PasswordPolicyErrorType.CHANGE_AFTER_RESET); 808 809 Attribute reset = Attributes.create(OP_ATTR_PWPOLICY_RESET_REQUIRED, "TRUE"); 810 entry.putAttribute(reset.getAttributeDescription().getAttributeType(), newArrayList(reset)); 811 } 812 } 813 814 815 816 /** 817 * Adds a password policy response control if the corresponding request 818 * control was included. 819 * 820 * @param errorType The error type to use for the response control. 821 */ 822 private void addPWPolicyControl(PasswordPolicyErrorType errorType) 823 { 824 for (Control c : getRequestControls()) 825 { 826 if (OID_PASSWORD_POLICY_CONTROL.equals(c.getOID())) 827 { 828 addResponseControl(new PasswordPolicyResponseControl(null, 0, errorType)); 829 } 830 } 831 } 832 833 834 835 /** 836 * Verifies that the entry to be added conforms to the server schema. 837 * 838 * @param parentEntry The parent of the entry to add. 839 * 840 * @throws DirectoryException If the entry violates the server schema 841 * configuration. 842 */ 843 private void checkSchema(Entry parentEntry) throws DirectoryException 844 { 845 LocalizableMessageBuilder invalidReason = new LocalizableMessageBuilder(); 846 if (! entry.conformsToSchema(parentEntry, true, true, true, invalidReason)) 847 { 848 throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, 849 invalidReason.toMessage()); 850 } 851 852 invalidReason = new LocalizableMessageBuilder(); 853 checkAttributes(invalidReason, userAttributes); 854 checkAttributes(invalidReason, operationalAttributes); 855 856 857 // See if the entry contains any attributes or object classes marked 858 // OBSOLETE. If so, then reject the entry. 859 for (AttributeType at : userAttributes.keySet()) 860 { 861 if (at.isObsolete()) 862 { 863 throw newDirectoryException(entryDN, ResultCode.CONSTRAINT_VIOLATION, 864 WARN_ADD_ATTR_IS_OBSOLETE.get(entryDN, at.getNameOrOID())); 865 } 866 } 867 868 for (AttributeType at : operationalAttributes.keySet()) 869 { 870 if (at.isObsolete()) 871 { 872 throw newDirectoryException(entryDN, ResultCode.CONSTRAINT_VIOLATION, 873 WARN_ADD_ATTR_IS_OBSOLETE.get(entryDN, at.getNameOrOID())); 874 } 875 } 876 877 for (ObjectClass oc : objectClasses.keySet()) 878 { 879 if (oc.isObsolete()) 880 { 881 throw newDirectoryException(entryDN, ResultCode.CONSTRAINT_VIOLATION, 882 WARN_ADD_OC_IS_OBSOLETE.get(entryDN, oc.getNameOrOID())); 883 } 884 } 885 } 886 887 888 private void checkAttributes(LocalizableMessageBuilder invalidReason, 889 Map<AttributeType, List<Attribute>> attributes) throws DirectoryException 890 { 891 for (List<Attribute> attrList : attributes.values()) 892 { 893 for (Attribute a : attrList) 894 { 895 Syntax syntax = a.getAttributeDescription().getAttributeType().getSyntax(); 896 if (syntax != null) 897 { 898 for (ByteString v : a) 899 { 900 if (!syntax.valueIsAcceptable(v, invalidReason)) 901 { 902 LocalizableMessage message; 903 if (!syntax.isHumanReadable() || syntax.isBEREncodingRequired()) 904 { 905 // Value is not human-readable 906 message = WARN_ADD_OP_INVALID_SYNTAX_NO_VALUE. 907 get(entryDN, a.getName(), invalidReason); 908 } 909 else 910 { 911 message = WARN_ADD_OP_INVALID_SYNTAX. 912 get(entryDN, v, a.getName(), invalidReason); 913 } 914 915 switch (DirectoryServer.getSyntaxEnforcementPolicy()) 916 { 917 case REJECT: 918 throw new DirectoryException( 919 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 920 case WARN: 921 logger.error(message); 922 } 923 } 924 } 925 } 926 } 927 } 928 } 929 930 /** 931 * Processes the set of controls contained in the add request. 932 * 933 * @param parentDN The DN of the parent of the entry to add. 934 * 935 * @throws DirectoryException If there is a problem with any of the 936 * request controls. 937 */ 938 private void processControls(DN parentDN) throws DirectoryException 939 { 940 LocalBackendWorkflowElement.evaluateProxyAuthControls(this); 941 LocalBackendWorkflowElement.removeAllDisallowedControls(parentDN, this); 942 943 for (Control c : getRequestControls()) 944 { 945 final String oid = c.getOID(); 946 947 if (OID_LDAP_ASSERTION.equals(oid)) 948 { 949 // RFC 4528 mandates support for Add operation basically 950 // suggesting an assertion on self. As daft as it may be 951 // we gonna have to support this for RFC compliance. 952 LDAPAssertionRequestControl assertControl = getRequestControl(LDAPAssertionRequestControl.DECODER); 953 954 SearchFilter filter; 955 try 956 { 957 filter = assertControl.getSearchFilter(); 958 } 959 catch (DirectoryException de) 960 { 961 logger.traceException(de); 962 963 throw newDirectoryException(entryDN, de.getResultCode(), 964 ERR_ADD_CANNOT_PROCESS_ASSERTION_FILTER.get(entryDN, de.getMessageObject())); 965 } 966 967 // Check if the current user has permission to make this determination. 968 if (!getAccessControlHandler().isAllowed(this, entry, filter)) 969 { 970 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 971 ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid)); 972 } 973 974 try 975 { 976 if (!filter.matchesEntry(entry)) 977 { 978 throw newDirectoryException(entryDN, ResultCode.ASSERTION_FAILED, ERR_ADD_ASSERTION_FAILED.get(entryDN)); 979 } 980 } 981 catch (DirectoryException de) 982 { 983 if (de.getResultCode() == ResultCode.ASSERTION_FAILED) 984 { 985 throw de; 986 } 987 988 logger.traceException(de); 989 990 throw newDirectoryException(entryDN, de.getResultCode(), 991 ERR_ADD_CANNOT_PROCESS_ASSERTION_FILTER.get(entryDN, de.getMessageObject())); 992 } 993 } 994 else if (OID_LDAP_NOOP_OPENLDAP_ASSIGNED.equals(oid)) 995 { 996 noOp = true; 997 } 998 else if (OID_LDAP_READENTRY_POSTREAD.equals(oid)) 999 { 1000 postReadRequest = getRequestControl(LDAPPostReadRequestControl.DECODER); 1001 } 1002 else if (LocalBackendWorkflowElement.isProxyAuthzControl(oid)) 1003 { 1004 continue; 1005 } 1006 else if (OID_PASSWORD_POLICY_CONTROL.equals(oid)) 1007 { 1008 // We don't need to do anything here because it's already handled 1009 // in LocalBackendAddOperation.handlePasswordPolicy(). 1010 } 1011 else if (c.isCritical() && !backend.supportsControl(oid)) 1012 { 1013 throw newDirectoryException(entryDN, ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, 1014 ERR_ADD_UNSUPPORTED_CRITICAL_CONTROL.get(entryDN, oid)); 1015 } 1016 } 1017 } 1018 1019 private AccessControlHandler<?> getAccessControlHandler() 1020 { 1021 return AccessControlConfigManager.getInstance().getAccessControlHandler(); 1022 } 1023}