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