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