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.AttributeDescription;
027import org.forgerock.opendj.ldap.ByteString;
028import org.forgerock.opendj.ldap.DN;
029import org.forgerock.opendj.ldap.ResultCode;
030import org.forgerock.opendj.ldap.schema.AttributeType;
031import org.opends.server.api.ClientConnection;
032import org.opends.server.protocols.ldap.LDAPAttribute;
033import org.opends.server.protocols.ldap.LDAPResultCode;
034import org.opends.server.types.AbstractOperation;
035import org.opends.server.types.Attribute;
036import org.opends.server.types.AttributeBuilder;
037import org.opends.server.types.CancelResult;
038import org.opends.server.types.CanceledOperationException;
039import org.opends.server.types.Control;
040import org.opends.server.types.Entry;
041import org.opends.server.types.LDAPException;
042import org.forgerock.opendj.ldap.schema.ObjectClass;
043import org.opends.server.types.Operation;
044import org.opends.server.types.OperationType;
045import org.opends.server.types.RawAttribute;
046import org.opends.server.types.operation.PostResponseAddOperation;
047import org.opends.server.types.operation.PreParseAddOperation;
048import org.opends.server.workflowelement.localbackend.LocalBackendAddOperation;
049
050import static org.opends.messages.CoreMessages.*;
051import static org.opends.server.config.ConfigConstants.*;
052import static org.opends.server.core.DirectoryServer.*;
053import static org.opends.server.loggers.AccessLogger.*;
054import static org.opends.server.util.CollectionUtils.*;
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          AttributeDescription attrDesc = attr.getAttributeDescription();
293          AttributeType attrType = attrDesc.getAttributeType();
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          if (attrType.isNoUserModification()
299              && !isInternalOperation()
300              && !isSynchronizationOperation())
301          {
302            throw new LDAPException(LDAPResultCode.UNWILLING_TO_PERFORM,
303                ERR_ADD_ATTR_IS_NO_USER_MOD.get(entryDN, attrDesc));
304          }
305
306          boolean hasBinaryOption = attrDesc.hasOption("binary");
307          if (attrType.getSyntax().isBEREncodingRequired())
308          {
309            if (!hasBinaryOption)
310            {
311              //A binary option wasn't provided by the client so add it.
312              AttributeBuilder builder = new AttributeBuilder(attr);
313              builder.setOption("binary");
314              attr = builder.toAttribute();
315            }
316          }
317          else if (hasBinaryOption)
318          {
319            // binary option is not honored for non-BER-encodable attributes.
320            throw new LDAPException(LDAPResultCode.UNDEFINED_ATTRIBUTE_TYPE,
321                ERR_ADD_ATTR_IS_INVALID_OPTION.get(entryDN, attrDesc));
322          }
323
324          if (attrType.isObjectClass())
325          {
326            for (ByteString os : a.getValues())
327            {
328              String ocName = os.toString();
329              objectClasses.put(getSchema().getObjectClass(ocName), 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.getAttributeDescription().equals(attrDesc))
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 = DirectoryServer.getParentDNInSuffix(entryDN);
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}