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-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.server.workflowelement.localbackend;
018
019import java.util.List;
020import java.util.concurrent.atomic.AtomicBoolean;
021
022import org.forgerock.i18n.LocalizableMessage;
023import org.forgerock.i18n.LocalizableMessageDescriptor.Arg2;
024import org.forgerock.i18n.slf4j.LocalizedLogger;
025import org.forgerock.opendj.ldap.AttributeDescription;
026import org.forgerock.opendj.ldap.ByteString;
027import org.forgerock.opendj.ldap.DN;
028import org.forgerock.opendj.ldap.ResultCode;
029import org.opends.server.api.AccessControlHandler;
030import org.opends.server.api.Backend;
031import org.opends.server.api.ClientConnection;
032import org.opends.server.backends.ConfigurationBackend;
033import org.opends.server.controls.LDAPAssertionRequestControl;
034import org.opends.server.core.AccessControlConfigManager;
035import org.opends.server.core.CompareOperation;
036import org.opends.server.core.CompareOperationWrapper;
037import org.opends.server.core.DirectoryServer;
038import org.opends.server.types.Attribute;
039import org.opends.server.types.CanceledOperationException;
040import org.opends.server.types.Control;
041import org.opends.server.types.DirectoryException;
042import org.opends.server.types.Entry;
043import org.opends.server.types.Privilege;
044import org.opends.server.types.SearchFilter;
045import org.opends.server.types.operation.PostOperationCompareOperation;
046import org.opends.server.types.operation.PostResponseCompareOperation;
047import org.opends.server.types.operation.PreOperationCompareOperation;
048
049import static org.opends.messages.CoreMessages.*;
050import static org.opends.server.core.DirectoryServer.*;
051import static org.opends.server.types.AbstractOperation.*;
052import static org.opends.server.util.ServerConstants.*;
053import static org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement.*;
054
055/**
056 * This class defines an operation that may be used to determine whether a
057 * specified entry in the Directory Server contains a given attribute-value pair.
058 */
059public class LocalBackendCompareOperation
060       extends CompareOperationWrapper
061       implements PreOperationCompareOperation, PostOperationCompareOperation,
062                  PostResponseCompareOperation
063{
064  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
065
066  /** The backend in which the comparison is to be performed. */
067  private Backend<?> backend;
068  /** The client connection for this operation. */
069  private ClientConnection clientConnection;
070  /** The DN of the entry to compare. */
071  private DN entryDN;
072  /** The entry to be compared. */
073  private Entry entry;
074
075
076
077  /**
078   * Creates a new compare operation based on the provided compare operation.
079   *
080   * @param compare  the compare operation
081   */
082  public LocalBackendCompareOperation(CompareOperation compare)
083  {
084    super(compare);
085    LocalBackendWorkflowElement.attachLocalOperation (compare, this);
086  }
087
088
089
090  /**
091   * Retrieves the entry to target with the compare operation.
092   *
093   * @return  The entry to target with the compare operation, or
094   *          <CODE>null</CODE> if the entry is not yet available.
095   */
096  @Override
097  public Entry getEntryToCompare()
098  {
099    return entry;
100  }
101
102
103
104  /**
105   * Process this compare operation in a local backend.
106   *
107   * @param wfe
108   *          The local backend work-flow element.
109   * @throws CanceledOperationException
110   *           if this operation should be cancelled
111   */
112  public void processLocalCompare(LocalBackendWorkflowElement wfe)
113      throws CanceledOperationException
114  {
115    this.backend = wfe.getBackend();
116
117    clientConnection  = getClientConnection();
118
119    // Check for a request to cancel this operation.
120    checkIfCanceled(false);
121
122    try
123    {
124      AtomicBoolean executePostOpPlugins = new AtomicBoolean(false);
125      processCompare(executePostOpPlugins);
126
127      // Check for a request to cancel this operation.
128      checkIfCanceled(false);
129
130      // Invoke the post-operation compare plugins.
131      if (executePostOpPlugins.get())
132      {
133        processOperationResult(this, getPluginConfigManager().invokePostOperationComparePlugins(this));
134      }
135    }
136    finally
137    {
138      LocalBackendWorkflowElement.filterNonDisclosableMatchedDN(this);
139    }
140  }
141
142  private void processCompare(AtomicBoolean executePostOpPlugins)
143      throws CanceledOperationException
144  {
145    // Process the entry DN to convert it from the raw form to the form
146    // required for the rest of the compare processing.
147    entryDN = getEntryDN();
148    if (entryDN == null)
149    {
150      return;
151    }
152
153
154    // If the target entry is in the server configuration, then make sure the
155    // requester has the CONFIG_READ privilege.
156    if (DirectoryServer.getBackend(ConfigurationBackend.CONFIG_BACKEND_ID).handlesEntry(entryDN)
157        && !clientConnection.hasPrivilege(Privilege.CONFIG_READ, this))
158    {
159      appendErrorMessage(ERR_COMPARE_CONFIG_INSUFFICIENT_PRIVILEGES.get());
160      setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
161      return;
162    }
163
164    // Check for a request to cancel this operation.
165    checkIfCanceled(false);
166
167    try
168    {
169      // Get the entry. If it does not exist, then fail.
170      try
171      {
172        entry = DirectoryServer.getEntry(entryDN);
173        if (entry == null)
174        {
175          setResultCode(ResultCode.NO_SUCH_OBJECT);
176          appendErrorMessage(ERR_COMPARE_NO_SUCH_ENTRY.get(entryDN));
177
178          // See if one of the entry's ancestors exists.
179          setMatchedDN(findMatchedDN(entryDN));
180          return;
181        }
182      }
183      catch (DirectoryException de)
184      {
185        logger.traceException(de);
186
187        setResultCodeAndMessageNoInfoDisclosure(entry, entryDN,
188            de.getResultCode(), de.getMessageObject());
189        return;
190      }
191
192      // Check to see if there are any controls in the request. If so, then
193      // see if there is any special processing required.
194      handleRequestControls();
195
196
197      // Check to see if the client has permission to perform the
198      // compare.
199
200      // FIXME: for now assume that this will check all permission
201      // pertinent to the operation. This includes proxy authorization
202      // and any other controls specified.
203
204      // FIXME: earlier checks to see if the entry already exists may
205      // have already exposed sensitive information to the client.
206      try
207      {
208        if (!getAccessControlHandler().isAllowed(this))
209        {
210          setResultCodeAndMessageNoInfoDisclosure(entry, entryDN,
211              ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
212              ERR_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(entryDN));
213          return;
214        }
215      }
216      catch (DirectoryException e)
217      {
218        setResultCode(e.getResultCode());
219        appendErrorMessage(e.getMessageObject());
220        return;
221      }
222
223      // Check for a request to cancel this operation.
224      checkIfCanceled(false);
225
226
227      // Invoke the pre-operation compare plugins.
228      executePostOpPlugins.set(true);
229      if (!processOperationResult(this, getPluginConfigManager().invokePreOperationComparePlugins(this)))
230      {
231        return;
232      }
233
234      // Actually perform the compare operation.
235      AttributeDescription attrDesc = getAttributeDescription();
236      List<Attribute> attrList = entry.getAttribute(attrDesc);
237      if (attrList.isEmpty())
238      {
239        setResultCode(ResultCode.NO_SUCH_ATTRIBUTE);
240        Arg2<Object, Object> errorMsg = attrDesc.hasOptions()
241            ? WARN_COMPARE_OP_NO_SUCH_ATTR
242            : WARN_COMPARE_OP_NO_SUCH_ATTR_WITH_OPTIONS;
243        appendErrorMessage(errorMsg.get(entryDN, getRawAttributeType()));
244      }
245      else
246      {
247        ByteString value = getAssertionValue();
248        setResultCode(matchExists(attrList, value));
249      }
250    }
251    catch (DirectoryException de)
252    {
253      logger.traceException(de);
254      setResponseData(de);
255    }
256  }
257
258  private ResultCode matchExists(List<Attribute> attrList, ByteString value)
259  {
260    for (Attribute a : attrList)
261    {
262      if (a.contains(value))
263      {
264        return ResultCode.COMPARE_TRUE;
265      }
266    }
267    return ResultCode.COMPARE_FALSE;
268  }
269
270  private DirectoryException newDirectoryException(Entry entry,
271      ResultCode resultCode, LocalizableMessage message) throws DirectoryException
272  {
273    return LocalBackendWorkflowElement.newDirectoryException(this, entry, null,
274        resultCode, message, ResultCode.NO_SUCH_OBJECT,
275        ERR_COMPARE_NO_SUCH_ENTRY.get(entryDN));
276  }
277
278  private void setResultCodeAndMessageNoInfoDisclosure(Entry entry, DN entryDN,
279      ResultCode realResultCode, LocalizableMessage realMessage) throws DirectoryException
280  {
281    LocalBackendWorkflowElement.setResultCodeAndMessageNoInfoDisclosure(this,
282        entry, entryDN, realResultCode, realMessage, ResultCode.NO_SUCH_OBJECT,
283        ERR_COMPARE_NO_SUCH_ENTRY.get(entryDN));
284  }
285
286  /**
287   * Performs any processing required for the controls included in the request.
288   *
289   * @throws  DirectoryException  If a problem occurs that should prevent the
290   *                              operation from succeeding.
291   */
292  private void handleRequestControls() throws DirectoryException
293  {
294    LocalBackendWorkflowElement.evaluateProxyAuthControls(this);
295    LocalBackendWorkflowElement.removeAllDisallowedControls(entryDN, this);
296
297    for (Control c : getRequestControls())
298    {
299      final String oid = c.getOID();
300
301      if (OID_LDAP_ASSERTION.equals(oid))
302      {
303        LDAPAssertionRequestControl assertControl = getRequestControl(LDAPAssertionRequestControl.DECODER);
304
305        SearchFilter filter;
306        try
307        {
308          filter = assertControl.getSearchFilter();
309        }
310        catch (DirectoryException de)
311        {
312          logger.traceException(de);
313
314          throw newDirectoryException(entry, de.getResultCode(),
315              ERR_COMPARE_CANNOT_PROCESS_ASSERTION_FILTER.get(entryDN, de.getMessageObject()));
316        }
317
318        // Check if the current user has permission to make this determination.
319        if (!getAccessControlHandler().isAllowed(this, entry, filter))
320        {
321          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
322              ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
323        }
324
325        try
326        {
327          if (!filter.matchesEntry(entry))
328          {
329            throw newDirectoryException(entry, ResultCode.ASSERTION_FAILED, ERR_COMPARE_ASSERTION_FAILED.get(entryDN));
330          }
331        }
332        catch (DirectoryException de)
333        {
334          if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
335          {
336            throw de;
337          }
338
339          logger.traceException(de);
340
341          throw newDirectoryException(entry, de.getResultCode(),
342              ERR_COMPARE_CANNOT_PROCESS_ASSERTION_FILTER.get(entryDN, de.getMessageObject()));
343        }
344      }
345      else if (LocalBackendWorkflowElement.isProxyAuthzControl(oid))
346      {
347        continue;
348      }
349      else if (c.isCritical() && (backend == null || !backend.supportsControl(oid)))
350      {
351        throw new DirectoryException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
352            ERR_COMPARE_UNSUPPORTED_CRITICAL_CONTROL.get(entryDN, oid));
353      }
354    }
355  }
356
357  private AccessControlHandler<?> getAccessControlHandler()
358  {
359    return AccessControlConfigManager.getInstance().getAccessControlHandler();
360  }
361}