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