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 2007-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2016 ForgeRock AS.
016 */
017package org.opends.server.core;
018
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.forgerock.i18n.LocalizedIllegalArgumentException;
025import org.forgerock.i18n.slf4j.LocalizedLogger;
026import org.forgerock.opendj.ldap.ByteString;
027import org.forgerock.opendj.ldap.DN;
028import org.forgerock.opendj.ldap.ResultCode;
029import org.forgerock.opendj.ldap.schema.AttributeType;
030import org.opends.server.api.ClientConnection;
031import org.opends.server.protocols.ldap.LDAPAttribute;
032import org.opends.server.protocols.ldap.LDAPResultCode;
033import org.opends.server.types.AbstractOperation;
034import org.opends.server.types.Attribute;
035import org.opends.server.types.AttributeBuilder;
036import org.opends.server.types.CancelResult;
037import org.opends.server.types.CanceledOperationException;
038import org.opends.server.types.Control;
039import org.opends.server.types.Entry;
040import org.opends.server.types.LDAPException;
041import org.opends.server.types.ObjectClass;
042import org.opends.server.types.Operation;
043import org.opends.server.types.OperationType;
044import org.opends.server.types.RawAttribute;
045import org.opends.server.types.operation.PostResponseAddOperation;
046import org.opends.server.types.operation.PreParseAddOperation;
047import org.opends.server.workflowelement.localbackend.LocalBackendAddOperation;
048
049import static org.opends.messages.CoreMessages.*;
050import static org.opends.server.config.ConfigConstants.*;
051import static org.opends.server.core.DirectoryServer.*;
052import static org.opends.server.loggers.AccessLogger.*;
053import static org.opends.server.util.CollectionUtils.*;
054import static org.opends.server.util.StaticUtils.*;
055import static org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement.*;
056
057/**
058 * This class defines an operation that may be used to add a new entry to the
059 * Directory Server.
060 */
061public class AddOperationBasis
062       extends AbstractOperation
063       implements PreParseAddOperation, AddOperation, PostResponseAddOperation
064{
065  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
066
067  /** The set of response controls to send to the client. */
068  private final ArrayList<Control> responseControls = new ArrayList<>();
069
070  /** The raw, unprocessed entry DN as provided in the request. This may or may not be a valid DN. */
071  private ByteString rawEntryDN;
072  /** The processed DN of the entry to add. */
073  private DN entryDN;
074  /** The proxied authorization target DN for this operation. */
075  private DN proxiedAuthorizationDN;
076
077  /**
078   * The set of attributes (including the objectclass attribute) in a raw,
079   * unprocessed form as provided in the request. One or more of these
080   * attributes may be invalid.
081   */
082  private List<RawAttribute> rawAttributes;
083  /** The set of operational attributes for the entry to add. */
084  private Map<AttributeType,List<Attribute>> operationalAttributes;
085  /** The set of user attributes for the entry to add. */
086  private Map<AttributeType,List<Attribute>> userAttributes;
087  /** The set of objectclasses for the entry to add. */
088  private Map<ObjectClass,String> objectClasses;
089
090  /** The flag indicates if an LDAP error was reported. */
091  private boolean ldapError;
092
093  /**
094   * Creates a new add operation with the provided information.
095   *
096   * @param  clientConnection  The client connection with which this operation
097   *                           is associated.
098   * @param  operationID       The operation ID for this operation.
099   * @param  messageID         The message ID of the request with which this
100   *                           operation is associated.
101   * @param  requestControls   The set of controls included in the request.
102   * @param  rawEntryDN        The raw DN of the entry to add from the client
103   *                           request.  This may or may not be a valid DN.
104   * @param  rawAttributes     The raw set of attributes from the client
105   *                           request (including the objectclass attribute).
106   *                           This may contain invalid attributes.
107   */
108  public AddOperationBasis(ClientConnection clientConnection, long operationID,
109                      int messageID, List<Control> requestControls,
110                      ByteString rawEntryDN, List<RawAttribute> rawAttributes)
111  {
112    super(clientConnection, operationID, messageID, requestControls);
113
114
115    this.rawEntryDN    = rawEntryDN;
116    this.rawAttributes = rawAttributes;
117
118    entryDN               = null;
119    userAttributes        = null;
120    operationalAttributes = null;
121    objectClasses         = null;
122  }
123
124
125
126  /**
127   * Creates a new add operation with the provided information.
128   *
129   * @param  clientConnection       The client connection with which this
130   *                                operation is associated.
131   * @param  operationID            The operation ID for this operation.
132   * @param  messageID              The message ID of the request with which
133   *                                this operation is associated.
134   * @param  requestControls        The set of controls included in the request.
135   * @param  entryDN                The DN for the entry.
136   * @param  objectClasses          The set of objectclasses for the entry.
137   * @param  userAttributes         The set of user attributes for the entry.
138   * @param  operationalAttributes  The set of operational attributes for the
139   *                                entry.
140   */
141  public AddOperationBasis(ClientConnection clientConnection, long operationID,
142                      int messageID, List<Control> requestControls,
143                      DN entryDN, Map<ObjectClass,String> objectClasses,
144                      Map<AttributeType,List<Attribute>> userAttributes,
145                      Map<AttributeType,List<Attribute>> operationalAttributes)
146  {
147    super(clientConnection, operationID, messageID, requestControls);
148
149
150    this.entryDN               = entryDN;
151    this.objectClasses         = objectClasses;
152    this.userAttributes        = userAttributes;
153    this.operationalAttributes = operationalAttributes;
154
155    rawEntryDN = ByteString.valueOfUtf8(entryDN.toString());
156
157    ArrayList<String> values = new ArrayList<>(objectClasses.values());
158    rawAttributes = new ArrayList<>();
159    rawAttributes.add(new LDAPAttribute(ATTR_OBJECTCLASS, values));
160    addAll(rawAttributes, userAttributes);
161    addAll(rawAttributes, operationalAttributes);
162  }
163
164  private void addAll(List<RawAttribute> rawAttributes, Map<AttributeType, List<Attribute>> attributesToAdd)
165  {
166    for (List<Attribute> attrList : attributesToAdd.values())
167    {
168      for (Attribute a : attrList)
169      {
170        rawAttributes.add(new LDAPAttribute(a));
171      }
172    }
173  }
174
175  @Override
176  public final ByteString getRawEntryDN()
177  {
178    return rawEntryDN;
179  }
180
181  @Override
182  public final void setRawEntryDN(ByteString rawEntryDN)
183  {
184    this.rawEntryDN = rawEntryDN;
185
186    entryDN = null;
187  }
188
189  @Override
190  public final DN getEntryDN()
191  {
192    try
193    {
194      if (entryDN == null)
195      {
196        entryDN = DN.valueOf(rawEntryDN);
197      }
198    }
199    catch (LocalizedIllegalArgumentException e)
200    {
201      logger.traceException(e);
202      setResultCode(ResultCode.INVALID_DN_SYNTAX);
203      appendErrorMessage(e.getMessageObject());
204    }
205    return entryDN;
206  }
207
208  @Override
209  public final List<RawAttribute> getRawAttributes()
210  {
211    return rawAttributes;
212  }
213
214  @Override
215  public final void addRawAttribute(RawAttribute rawAttribute)
216  {
217    rawAttributes.add(rawAttribute);
218
219    objectClasses         = null;
220    userAttributes        = null;
221    operationalAttributes = null;
222  }
223
224  @Override
225  public final void setRawAttributes(List<RawAttribute> rawAttributes)
226  {
227    this.rawAttributes = rawAttributes;
228
229    objectClasses         = null;
230    userAttributes        = null;
231    operationalAttributes = null;
232  }
233
234  @Override
235  public final Map<ObjectClass,String> getObjectClasses()
236  {
237    if (objectClasses == null){
238      computeObjectClassesAndAttributes();
239    }
240    return objectClasses;
241  }
242
243  @Override
244  public final void addObjectClass(ObjectClass objectClass, String name)
245  {
246    objectClasses.put(objectClass, name);
247  }
248
249  @Override
250  public final void removeObjectClass(ObjectClass objectClass)
251  {
252    objectClasses.remove(objectClass);
253  }
254
255  @Override
256  public final Map<AttributeType,List<Attribute>> getUserAttributes()
257  {
258    if (userAttributes == null){
259      computeObjectClassesAndAttributes();
260    }
261    return userAttributes;
262  }
263
264  @Override
265  public final Map<AttributeType,List<Attribute>> getOperationalAttributes()
266  {
267    if (operationalAttributes == null){
268      computeObjectClassesAndAttributes();
269    }
270    return operationalAttributes;
271  }
272
273  /**
274   * Build the objectclasses, the user attributes and the operational attributes
275   * if there are not already computed.
276   */
277  private final void computeObjectClassesAndAttributes()
278  {
279    if (!ldapError
280        && (objectClasses == null || userAttributes == null
281            || operationalAttributes == null))
282    {
283      objectClasses         = new HashMap<>();
284      userAttributes        = new HashMap<>();
285      operationalAttributes = new HashMap<>();
286
287      for (RawAttribute a : rawAttributes)
288      {
289        try
290        {
291          Attribute attr = a.toAttribute();
292          AttributeType attrType = attr.getAttributeDescription().getAttributeType();
293
294          // If the attribute type is marked "NO-USER-MODIFICATION" then fail
295          // unless this is an internal operation or is related to
296          // synchronization in some way.
297          if (attrType.isNoUserModification()
298              && !isInternalOperation()
299              && !isSynchronizationOperation())
300          {
301            throw new LDAPException(LDAPResultCode.UNWILLING_TO_PERFORM,
302                ERR_ADD_ATTR_IS_NO_USER_MOD.get(entryDN, attr.getName()));
303          }
304
305          if(attrType.getSyntax().isBEREncodingRequired())
306          {
307            if(!attr.hasOption("binary"))
308            {
309              //A binary option wasn't provided by the client so add it.
310              AttributeBuilder builder = new AttributeBuilder(attr);
311              builder.setOption("binary");
312              attr = builder.toAttribute();
313            }
314          }
315          else if (attr.hasOption("binary"))
316          {
317            // binary option is not honored for non-BER-encodable attributes.
318            throw new LDAPException(LDAPResultCode.UNDEFINED_ATTRIBUTE_TYPE,
319                ERR_ADD_ATTR_IS_INVALID_OPTION.get(entryDN, attr.getName()));
320          }
321
322          if (attrType.isObjectClass())
323          {
324            for (ByteString os : a.getValues())
325            {
326              String ocName = os.toString();
327              ObjectClass oc =
328                DirectoryServer.getObjectClass(toLowerCase(ocName));
329              if (oc == null)
330              {
331                oc = DirectoryServer.getDefaultObjectClass(ocName);
332              }
333
334              objectClasses.put(oc,ocName);
335            }
336          }
337          else if (attrType.isOperational())
338          {
339            List<Attribute> attrs = operationalAttributes.get(attrType);
340            if (attrs == null)
341            {
342              attrs = new ArrayList<>(1);
343              operationalAttributes.put(attrType, attrs);
344            }
345            attrs.add(attr);
346          }
347          else
348          {
349            List<Attribute> attrs = userAttributes.get(attrType);
350            if (attrs == null)
351            {
352              attrs = newArrayList(attr);
353              userAttributes.put(attrType, attrs);
354            }
355            else
356            {
357              // Check to see if any of the existing attributes in the list
358              // have the same set of options.  If so, then add the values
359              // to that attribute.
360              boolean attributeSeen = false;
361              for (int i = 0; i < attrs.size(); i++) {
362                Attribute ea = attrs.get(i);
363                if (ea.getAttributeDescription().equals(attr.getAttributeDescription()))
364                {
365                  AttributeBuilder builder = new AttributeBuilder(ea);
366                  builder.addAll(attr);
367                  attrs.set(i, builder.toAttribute());
368                  attributeSeen = true;
369                }
370              }
371
372              if (!attributeSeen)
373              {
374                // This is the first occurrence of the attribute and options.
375                attrs.add(attr);
376              }
377            }
378          }
379        }
380        catch (LDAPException le)
381        {
382          setResultCode(ResultCode.valueOf(le.getResultCode()));
383          appendErrorMessage(le.getMessageObject());
384
385          objectClasses = null;
386          userAttributes = null;
387          operationalAttributes = null;
388          ldapError = true;
389          return;
390        }
391      }
392    }
393  }
394
395  @Override
396  public final void setAttribute(AttributeType attributeType,
397                                 List<Attribute> attributeList)
398  {
399    Map<AttributeType, List<Attribute>> attributes =
400        getAttributes(attributeType.isOperational());
401    if (attributeList == null || attributeList.isEmpty())
402    {
403      attributes.remove(attributeType);
404    }
405    else
406    {
407      attributes.put(attributeType, attributeList);
408    }
409  }
410
411  @Override
412  public final void removeAttribute(AttributeType attributeType)
413  {
414    getAttributes(attributeType.isOperational()).remove(attributeType);
415  }
416
417  private Map<AttributeType, List<Attribute>> getAttributes(boolean isOperational)
418  {
419    if (isOperational)
420    {
421      return operationalAttributes;
422    }
423    return userAttributes;
424  }
425
426  @Override
427  public final OperationType getOperationType()
428  {
429    // Note that no debugging will be done in this method because it is a likely
430    // candidate for being called by the logging subsystem.
431
432    return OperationType.ADD;
433  }
434
435  @Override
436  public DN getProxiedAuthorizationDN()
437  {
438    return proxiedAuthorizationDN;
439  }
440
441  @Override
442  public final ArrayList<Control> getResponseControls()
443  {
444    return responseControls;
445  }
446
447  @Override
448  public final void addResponseControl(Control control)
449  {
450    responseControls.add(control);
451  }
452
453  @Override
454  public final void removeResponseControl(Control control)
455  {
456    responseControls.remove(control);
457  }
458
459  @Override
460  public final void toString(StringBuilder buffer)
461  {
462    buffer.append("AddOperation(connID=");
463    buffer.append(clientConnection.getConnectionID());
464    buffer.append(", opID=");
465    buffer.append(operationID);
466    buffer.append(", dn=");
467    buffer.append(rawEntryDN);
468    buffer.append(")");
469  }
470
471  @Override
472  public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
473  {
474    this.proxiedAuthorizationDN = proxiedAuthorizationDN;
475  }
476
477  @Override
478  public final void run()
479  {
480    setResultCode(ResultCode.UNDEFINED);
481
482    // Start the processing timer.
483    setProcessingStartTime();
484
485    logAddRequest(this);
486
487    // This flag is set to true as soon as a workflow has been executed.
488    boolean workflowExecuted = false;
489    try
490    {
491      // Check for and handle a request to cancel this operation.
492      checkIfCanceled(false);
493
494      // Invoke the pre-parse add plugins.
495      if (!processOperationResult(getPluginConfigManager().invokePreParseAddPlugins(this)))
496      {
497        return;
498      }
499
500      // Check for and handle a request to cancel this operation.
501      checkIfCanceled(false);
502
503      // Process the entry DN and set of attributes to convert them from their
504      // raw forms as provided by the client to the forms required for the rest
505      // of the add processing.
506      DN entryDN = getEntryDN();
507      if (entryDN == null){
508        return;
509      }
510
511      workflowExecuted = execute(this, entryDN);
512    }
513    catch(CanceledOperationException coe)
514    {
515      logger.traceException(coe);
516
517      setResultCode(ResultCode.CANCELLED);
518      cancelResult = new CancelResult(ResultCode.CANCELLED, null);
519
520      appendErrorMessage(coe.getCancelRequest().getCancelReason());
521    }
522    finally
523    {
524      // Stop the processing timer.
525      setProcessingStopTime();
526
527      // Log the add response message.
528      logAddResponse(this);
529
530      if(cancelRequest == null || cancelResult == null ||
531          cancelResult.getResultCode() != ResultCode.CANCELLED ||
532          cancelRequest.notifyOriginalRequestor() ||
533          DirectoryServer.notifyAbandonedOperations())
534      {
535        clientConnection.sendResponse(this);
536      }
537
538
539      // Invoke the post-response callbacks.
540      if (workflowExecuted) {
541        invokePostResponseCallbacks();
542      }
543
544      // Invoke the post-response add plugins.
545      invokePostResponsePlugins(workflowExecuted);
546
547      // If no cancel result, set it
548      if(cancelResult == null)
549      {
550        cancelResult = new CancelResult(ResultCode.TOO_LATE, null);
551      }
552    }
553  }
554
555
556  /**
557   * Invokes the post response plugins. If a workflow has been executed
558   * then invoke the post response plugins provided by the workflow
559   * elements of the workflow, otherwise invoke the post response plugins
560   * that have been registered with the current operation.
561   *
562   * @param workflowExecuted <code>true</code> if a workflow has been executed
563   */
564  @SuppressWarnings({ "unchecked", "rawtypes" })
565  private void invokePostResponsePlugins(boolean workflowExecuted)
566  {
567    // Invoke the post response plugins
568    if (workflowExecuted)
569    {
570      // Invoke the post response plugins that have been registered by
571      // the workflow elements
572      List<LocalBackendAddOperation> localOperations =
573          (List) getAttachment(Operation.LOCALBACKENDOPERATIONS);
574
575      if (localOperations != null)
576      {
577        for (LocalBackendAddOperation localOp : localOperations)
578        {
579          getPluginConfigManager().invokePostResponseAddPlugins(localOp);
580        }
581      }
582    }
583    else
584    {
585      // Invoke the post response plugins that have been registered with
586      // the current operation
587      getPluginConfigManager().invokePostResponseAddPlugins(this);
588    }
589  }
590
591  @Override
592  public void updateOperationErrMsgAndResCode()
593  {
594    DN entryDN = getEntryDN();
595    DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN);
596    if (parentDN == null)
597    {
598      // Either this entry is a suffix or doesn't belong in the directory.
599      if (DirectoryServer.isNamingContext(entryDN))
600      {
601        // This is fine.  This entry is one of the configured suffixes.
602        return;
603      }
604      if (entryDN.isRootDN())
605      {
606        // This is not fine.  The root DSE cannot be added.
607        setResultCode(ResultCode.UNWILLING_TO_PERFORM);
608        appendErrorMessage(ERR_ADD_CANNOT_ADD_ROOT_DSE.get());
609        return;
610      }
611      // The entry doesn't have a parent but isn't a suffix. This is not allowed.
612      setResultCode(ResultCode.NO_SUCH_OBJECT);
613      appendErrorMessage(ERR_ADD_ENTRY_NOT_SUFFIX.get(entryDN));
614      return;
615    }
616    // The suffix does not exist
617    setResultCode(ResultCode.NO_SUCH_OBJECT);
618    appendErrorMessage(ERR_ADD_ENTRY_UNKNOWN_SUFFIX.get(entryDN));
619  }
620
621
622  /**
623   * {@inheritDoc}
624   *
625   * This method always returns null.
626   */
627  @Override
628  public Entry getEntryToAdd()
629  {
630    return null;
631  }
632}