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}