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 2006-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.server.core;
018
019import static org.opends.messages.CoreMessages.*;
020import static org.opends.server.core.DirectoryServer.*;
021import static org.opends.server.loggers.AccessLogger.*;
022import static org.opends.server.util.ServerConstants.*;
023
024import java.util.ArrayList;
025import java.util.Iterator;
026import java.util.List;
027
028import org.forgerock.i18n.slf4j.LocalizedLogger;
029import org.forgerock.opendj.ldap.ByteString;
030import org.forgerock.opendj.ldap.ResultCode;
031import org.opends.server.api.AccessControlHandler;
032import org.opends.server.api.ClientConnection;
033import org.opends.server.api.ExtendedOperationHandler;
034import org.opends.server.types.AbstractOperation;
035import org.opends.server.types.CancelResult;
036import org.opends.server.types.CanceledOperationException;
037import org.opends.server.types.Control;
038import org.forgerock.opendj.ldap.DN;
039import org.opends.server.types.DirectoryException;
040import org.opends.server.types.OperationType;
041import org.opends.server.types.operation.PostOperationExtendedOperation;
042import org.opends.server.types.operation.PostResponseExtendedOperation;
043import org.opends.server.types.operation.PreOperationExtendedOperation;
044import org.opends.server.types.operation.PreParseExtendedOperation;
045
046/** This class defines an extended operation, which can perform virtually any kind of task. */
047public class ExtendedOperationBasis
048       extends AbstractOperation
049       implements ExtendedOperation,
050                  PreParseExtendedOperation,
051                  PreOperationExtendedOperation,
052                  PostOperationExtendedOperation,
053                  PostResponseExtendedOperation
054{
055  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
056
057  /** The value for the request associated with this extended operation. */
058  private ByteString requestValue;
059
060  /** The value for the response associated with this extended operation. */
061  private ByteString responseValue;
062
063  /** The set of response controls for this extended operation. */
064  private List<Control> responseControls;
065
066  /** The OID for the request associated with this extended operation. */
067  private String requestOID;
068
069  /** The OID for the response associated with this extended operation. */
070  private String responseOID;
071
072  /**
073   * Creates a new extended operation with the provided information.
074   *
075   * @param  clientConnection  The client connection with which this operation
076   *                           is associated.
077   * @param  operationID       The operation ID for this operation.
078   * @param  messageID         The message ID of the request with which this
079   *                           operation is associated.
080   * @param  requestControls   The set of controls included in the request.
081   * @param  requestOID        The OID for the request associated with this
082   *                           extended operation.
083   * @param  requestValue      The value for the request associated with this
084   *                           extended operation.
085   */
086  public ExtendedOperationBasis(ClientConnection clientConnection,
087                           long operationID,
088                           int messageID, List<Control> requestControls,
089                           String requestOID, ByteString requestValue)
090  {
091    super(clientConnection, operationID, messageID, requestControls);
092
093    this.requestOID   = requestOID;
094    this.requestValue = requestValue;
095
096    responseOID      = null;
097    responseValue    = null;
098    responseControls = new ArrayList<>();
099    cancelRequest    = null;
100
101    if (requestOID.equals(OID_CANCEL_REQUEST))
102    {
103      cancelResult = new CancelResult(ResultCode.CANNOT_CANCEL,
104          ERR_CANNOT_CANCEL_CANCEL.get());
105    }
106    if(requestOID.equals(OID_START_TLS_REQUEST))
107    {
108      cancelResult = new CancelResult(ResultCode.CANNOT_CANCEL,
109          ERR_CANNOT_CANCEL_START_TLS.get());
110    }
111  }
112
113  @Override
114  public final String getRequestOID()
115  {
116    return requestOID;
117  }
118
119  /**
120   * Specifies the OID for the request associated with this extended operation.
121   * This should only be called by pre-parse plugins.
122   *
123   * @param  requestOID  The OID for the request associated with this extended
124   *                     operation.
125   */
126  @Override
127  public final void setRequestOID(String requestOID)
128  {
129    this.requestOID = requestOID;
130  }
131
132  @Override
133  public DN getProxiedAuthorizationDN()
134  {
135    return null;
136  }
137
138  @Override
139  public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
140  {
141  }
142
143  @Override
144  public final ByteString getRequestValue()
145  {
146    return requestValue;
147  }
148
149  /**
150   * Specifies the value for the request associated with this extended
151   * operation.  This should only be called by pre-parse plugins.
152   *
153   * @param  requestValue  The value for the request associated with this
154   *                       extended operation.
155   */
156  @Override
157  public final void setRequestValue(ByteString requestValue)
158  {
159    this.requestValue = requestValue;
160  }
161
162  @Override
163  public final String getResponseOID()
164  {
165    return responseOID;
166  }
167
168  @Override
169  public final void setResponseOID(String responseOID)
170  {
171    this.responseOID = responseOID;
172  }
173
174  @Override
175  public final ByteString getResponseValue()
176  {
177    return responseValue;
178  }
179
180  @Override
181  public final void setResponseValue(ByteString responseValue)
182  {
183    this.responseValue = responseValue;
184  }
185
186  @Override
187  public final OperationType getOperationType()
188  {
189    // Note that no debugging will be done in this method because it is a likely
190    // candidate for being called by the logging subsystem.
191    return OperationType.EXTENDED;
192  }
193
194  @Override
195  public final List<Control> getResponseControls()
196  {
197    return responseControls;
198  }
199
200  @Override
201  public final void addResponseControl(Control control)
202  {
203    responseControls.add(control);
204  }
205
206  @Override
207  public final void removeResponseControl(Control control)
208  {
209    responseControls.remove(control);
210  }
211
212  /**
213   * Performs the work of actually processing this operation.  This
214   * should include all processing for the operation, including
215   * invoking plugins, logging messages, performing access control,
216   * managing synchronization, and any other work that might need to
217   * be done in the course of processing.
218   */
219  @Override
220  public final void run()
221  {
222    setResultCode(ResultCode.UNDEFINED);
223
224    // Start the processing timer.
225    setProcessingStartTime();
226
227    logExtendedRequest(this);
228
229    try
230    {
231      // Check for and handle a request to cancel this operation.
232      checkIfCanceled(false);
233
234      // Invoke the pre-parse extended plugins.
235      if (!processOperationResult(getPluginConfigManager().invokePreParseExtendedPlugins(this)))
236      {
237        return;
238      }
239
240      checkIfCanceled(false);
241
242      // Get the extended operation handler for the request OID.  If there is
243      // none, then fail.
244      ExtendedOperationHandler<?> handler =
245           DirectoryServer.getExtendedOperationHandler(requestOID);
246      if (handler == null)
247      {
248        setResultCode(ResultCode.UNWILLING_TO_PERFORM);
249        appendErrorMessage(ERR_EXTENDED_NO_HANDLER.get(requestOID));
250        return;
251      }
252
253      // Look at the controls included in the request and ensure that all
254      // critical controls are supported by the handler.
255      for (Iterator<Control> iter = getRequestControls().iterator(); iter.hasNext();)
256      {
257        final Control c = iter.next();
258        try
259        {
260          if (!getAccessControlHandler().isAllowed(getAuthorizationDN(), this, c))
261          {
262            // As per RFC 4511 4.1.11.
263            if (c.isCritical())
264            {
265              setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
266              appendErrorMessage(ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(c.getOID()));
267            }
268            else
269            {
270              // We don't want to process this non-critical control, so remove it.
271              iter.remove();
272              continue;
273            }
274          }
275        }
276        catch (DirectoryException e)
277        {
278          setResultCode(e.getResultCode());
279          appendErrorMessage(e.getMessageObject());
280          return;
281        }
282
283        if (!c.isCritical())
284        {
285          // The control isn't critical, so we don't care if it's supported
286          // or not.
287        }
288        else if (!handler.supportsControl(c.getOID()))
289        {
290          setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
291          appendErrorMessage(ERR_EXTENDED_UNSUPPORTED_CRITICAL_CONTROL.get(requestOID, c.getOID()));
292          return;
293        }
294      }
295
296      // Check to see if the client has permission to perform the
297      // extended operation.
298
299      // FIXME: for now assume that this will check all permission
300      // pertinent to the operation. This includes proxy authorization
301      // and any other controls specified.
302      try
303      {
304        if (!getAccessControlHandler().isAllowed(this))
305        {
306          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
307          appendErrorMessage(ERR_EXTENDED_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(requestOID));
308          return;
309        }
310      }
311      catch (DirectoryException e)
312      {
313        setResultCode(e.getResultCode());
314        appendErrorMessage(e.getMessageObject());
315        return;
316      }
317
318      try
319      {
320        // Invoke the pre-operation extended plugins.
321        if (!processOperationResult(getPluginConfigManager().invokePreOperationExtendedPlugins(this)))
322        {
323          return;
324        }
325
326        checkIfCanceled(false);
327
328        // Actually perform the processing for this operation.
329        handler.processExtendedOperation(this);
330      }
331      finally
332      {
333        getPluginConfigManager().invokePostOperationExtendedPlugins(this);
334      }
335    }
336    catch(CanceledOperationException coe)
337    {
338      logger.traceException(coe);
339
340      setResultCode(ResultCode.CANCELLED);
341      cancelResult = new CancelResult(ResultCode.CANCELLED, null);
342
343      appendErrorMessage(coe.getCancelRequest().getCancelReason());
344    }
345    finally
346    {
347      // Stop the processing timer.
348      setProcessingStopTime();
349
350      // Log the extended response.
351      logExtendedResponse(this);
352
353      // Send the response to the client.
354      if(cancelRequest == null || cancelResult == null ||
355          cancelResult.getResultCode() != ResultCode.CANCELLED ||
356          cancelRequest.notifyOriginalRequestor() ||
357          DirectoryServer.notifyAbandonedOperations())
358      {
359        clientConnection.sendResponse(this);
360      }
361
362      if(requestOID.equals(OID_START_TLS_REQUEST))
363      {
364        clientConnection.finishStartTLS();
365      }
366
367      // Invoke the post-response extended plugins.
368      getPluginConfigManager().invokePostResponseExtendedPlugins(this);
369
370      // If no cancel result, set it
371      if(cancelResult == null)
372      {
373        cancelResult = new CancelResult(ResultCode.TOO_LATE, null);
374      }
375    }
376  }
377
378  private AccessControlHandler<?> getAccessControlHandler()
379  {
380    return AccessControlConfigManager.getInstance().getAccessControlHandler();
381  }
382
383  @Override
384  public final void toString(StringBuilder buffer)
385  {
386    buffer.append("ExtendedOperation(connID=");
387    buffer.append(clientConnection.getConnectionID());
388    buffer.append(", opID=");
389    buffer.append(operationID);
390    buffer.append(", oid=");
391    buffer.append(requestOID);
392    buffer.append(")");
393  }
394}