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