mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

matthew_swift
23.44.2009 898c594c58fc6f019e732c46d14fda4cb8be05b5
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java
@@ -24,60 +24,101 @@
 *
 *      Copyright 2008-2009 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import org.opends.messages.Message;
import static org.opends.server.authorization.dseecompat.Aci.*;
import static org.opends.server.config.ConfigConstants.ATTR_AUTHZ_GLOBAL_ACI;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.messages.AccessControlMessages.*;
import static org.opends.server.schema.SchemaConstants.SYNTAX_DN_OID;
import static org.opends.server.authorization.dseecompat.Aci.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.schema.SchemaConstants.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.toLowerCase;
import java.util.*;
import static org.opends.server.util.StaticUtils.*;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.concurrent.locks.Lock;
import org.opends.messages.Message;
import org.opends.server.admin.std.server.DseeCompatAccessControlHandlerCfg;
import org.opends.server.api.AccessControlHandler;
import org.opends.server.api.ClientConnection;
import org.opends.server.backends.jeb.EntryContainer;
import org.opends.server.config.ConfigException;
import org.opends.server.core.*;
import org.opends.server.controls.GetEffectiveRightsRequestControl;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ExtendedOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.protocols.ldap.LDAPControl;
import org.opends.server.types.*;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeBuilder;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.AttributeValues;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.Control;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LockManager;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.Operation;
import org.opends.server.types.Privilege;
import org.opends.server.types.RDN;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
import org.opends.server.types.SearchScope;
import org.opends.server.workflowelement.localbackend.*;
import org.opends.server.controls.GetEffectiveRightsRequestControl;
import org.opends.server.backends.jeb.EntryContainer;
/**
 * The AciHandler class performs the main processing for the dseecompat package.
 * The AciHandler class performs the main processing for the dseecompat
 * package.
 */
public class AciHandler
       extends AccessControlHandler<DseeCompatAccessControlHandlerCfg>
public class AciHandler extends
    AccessControlHandler<DseeCompatAccessControlHandlerCfg>
{
  /**
   * The tracer object for the debug logger.
   * String used to save a resource entry containing all the attributes
   * in the SearchOperation attachment list. This is only used during
   * geteffectiverights read right processing when all of an entry'ss
   * attributes need to examined.
   */
  private static final DebugTracer TRACER = getTracer();
  public static final String ALL_ATTRS_RESOURCE_ENTRY =
      "allAttrsResourceEntry";
  /**
   * The list that holds that ACIs keyed by the DN of the entry
   * holding the ACI.
   * String used to indicate that the evaluating ACI had a all
   * operational attributes targetattr match (targetattr="+").
   */
  private AciList aciList;
  public static final String ALL_OP_ATTRS_MATCHED = "allOpAttrsMatched";
  /**
   * The listener that handles ACI changes caused by LDAP operations, ACI
   * decode failure alert logging and backend initialization ACI list
   * adjustment.
   * String used to indicate that the evaluating ACI had a all user
   * attributes targetattr match (targetattr="*").
   */
  private AciListenerManager aciListenerMgr;
  public static final String ALL_USER_ATTRS_MATCHED =
      "allUserAttrsMatched";
  /**
   * String used to save the original authorization entry in an
   * operation attachment if a proxied authorization control was seen.
   */
  public static final String ORIG_AUTH_ENTRY = "origAuthorizationEntry";
  /**
   * Attribute type corresponding to "aci" attribute.
@@ -85,122 +126,156 @@
  static AttributeType aciType;
  /**
   * Attribute type corresponding to global "ds-cfg-global-aci" attribute.
   * Attribute type corresponding to global "ds-cfg-global-aci"
   * attribute.
   */
  static AttributeType globalAciType;
  /**
   * Attribute type corresponding to "debugsearchindex" attribute.
   */
  static AttributeType debugSearchIndex;
  private static AttributeType debugSearchIndex;
  /*
   * DN corresponding to "debugsearchindex" attribute type.
   */
  private static DN debugSearchIndexDN;
  /**
   * Attribute type corresponding to the "ref" attribute type. Used in the
   * search reference access check.
   * Attribute type corresponding to the "ref" attribute type. Used in
   * the search reference access check.
   */
  static AttributeType refAttrType;
 /*
  * DN corresponding to "debugsearchindex" attribute type.
  */
  static DN debugSearchIndexDN;
  private static AttributeType refAttrType;
  /**
   * String used to save the original authorization entry in an operation
   * attachment if a proxied authorization control was seen.
   * The tracer object for the debug logger.
   */
  public static final String ORIG_AUTH_ENTRY="origAuthorizationEntry";
  private static final DebugTracer TRACER = getTracer();
  /**
   * String used to save a resource entry containing all the attributes in
   * the SearchOperation attachment list. This is only used during
   * geteffectiverights read right processing when all of an entry'ss
   * attributes need to examined.
   */
  public static final String ALL_ATTRS_RESOURCE_ENTRY = "allAttrsResourceEntry";
  static
  {
    initStatics();
  }
  /**
   * String used to indicate that the evaluating ACI had a all user attributes
   * targetattr match (targetattr="*").
   */
   public static final String ALL_USER_ATTRS_MATCHED = "allUserAttrsMatched";
  /**
   * String used to indicate that the evaluating ACI had a all operational
   * attributes targetattr match (targetattr="+").
   */
   public static final String ALL_OP_ATTRS_MATCHED = "allOpAttrsMatched";
   static {
     initStatics();
   }
  // We initialize these for each new AciHandler so that we can clear out
  // the stale references that can occur during an in-core restart.
  // We initialize these for each new AciHandler so that we can clear
  // out the stale references that can occur during an in-core restart.
  private static void initStatics()
  {
    if((aciType = DirectoryServer.getAttributeType("aci")) == null)
    if ((aciType = DirectoryServer.getAttributeType("aci")) == null)
    {
      aciType = DirectoryServer.getDefaultAttributeType("aci");
    }
    if((globalAciType =
            DirectoryServer.getAttributeType(ATTR_AUTHZ_GLOBAL_ACI)) == null)
    if ((globalAciType =
        DirectoryServer.getAttributeType(ATTR_AUTHZ_GLOBAL_ACI)) == null)
    {
      globalAciType =
              DirectoryServer.getDefaultAttributeType(ATTR_AUTHZ_GLOBAL_ACI);
          DirectoryServer
              .getDefaultAttributeType(ATTR_AUTHZ_GLOBAL_ACI);
    }
     if((debugSearchIndex =
          DirectoryServer.
              getAttributeType(EntryContainer.ATTR_DEBUG_SEARCH_INDEX)) == null)
     {
       debugSearchIndex =
       DirectoryServer.
               getDefaultAttributeType(EntryContainer.ATTR_DEBUG_SEARCH_INDEX);
     }
    if ((debugSearchIndex =
        DirectoryServer
            .getAttributeType(EntryContainer.ATTR_DEBUG_SEARCH_INDEX)) == null)
    {
      debugSearchIndex =
          DirectoryServer
              .getDefaultAttributeType(EntryContainer.ATTR_DEBUG_SEARCH_INDEX);
    }
     if((refAttrType =
             DirectoryServer.
                     getAttributeType(ATTR_REFERRAL_URL)) == null) {
       refAttrType =
               DirectoryServer.
                       getDefaultAttributeType(ATTR_REFERRAL_URL);
     }
     try {
       debugSearchIndexDN=DN.decode("cn=debugsearch");
     } catch (DirectoryException ex) {
       //Should never happen.
     }
    if ((refAttrType =
        DirectoryServer.getAttributeType(ATTR_REFERRAL_URL)) == null)
    {
      refAttrType =
          DirectoryServer.getDefaultAttributeType(ATTR_REFERRAL_URL);
    }
    try
    {
      debugSearchIndexDN = DN.decode("cn=debugsearch");
    }
    catch (DirectoryException ex)
    {
      // Should never happen.
    }
  }
  /**
   * The list that holds that ACIs keyed by the DN of the entry holding
   * the ACI.
   */
  private AciList aciList;
  /**
   * The listener that handles ACI changes caused by LDAP operations,
   * ACI decode failure alert logging and backend initialization ACI
   * list adjustment.
   */
  private AciListenerManager aciListenerMgr;
  /**
   * Creates a new DSEE-compatible access control handler.
   */
  public AciHandler()
  {
    // No implementation required.  All initialization should be done in the
    // intializeAccessControlHandler method.
    // No implementation required. All initialization should be done in
    // the intializeAccessControlHandler method.
  }
  /**
   * {@inheritDoc}
  /*
   * TODO Rename this method. Needs to be changed in SearchOperation. I
   * find the name of the filterEntry method to be misleading because it
   * works on a search operation but has nothing to do with the search
   * filter. Something like "removeDisallowedAttributes" would be
   * clearer.
   */
  @Override()
  public void initializeAccessControlHandler(
                   DseeCompatAccessControlHandlerCfg configuration)
         throws ConfigException, InitializationException
  /**
   * Checks access on each attribute in an entry. It removes those
   * attributes that fail access check.
   *
   * @param operation
   *          The search operation class containing information to check
   *          access on.
   * @param entry
   *          The entry containing the attributes.
   * @return The entry to return minus filtered attributes.
   */
  @Override
  public SearchResultEntry filterEntry(SearchOperation operation,
      SearchResultEntry entry)
  {
    initStatics();
    DN configurationDN=configuration.dn();
    aciList = new AciList(configurationDN);
    aciListenerMgr = new AciListenerManager(aciList, configurationDN);
    processGlobalAcis(configuration);
    processConfigAcis();
    DirectoryServer.registerSupportedControl(OID_GET_EFFECTIVE_RIGHTS);
    AciLDAPOperationContainer operationContainer =
        new AciLDAPOperationContainer(operation, (ACI_READ), entry);
    // Proxy access check has already been done for this entry in the
    // maySend method, set the seen flag to true to bypass any proxy
    // check.
    operationContainer.setSeenEntry(true);
    SearchResultEntry returnEntry;
    boolean skipCheck = skipAccessCheck(operation);
    if (!skipCheck)
    {
      returnEntry = accessAllowedAttrs(operationContainer);
    }
    else
    {
      returnEntry = entry;
    }
    if (operationContainer.hasGetEffectiveRightsControl())
    {
      returnEntry =
          AciEffectiveRights.addRightsToEntry(this, operation
              .getAttributes(), operationContainer, returnEntry,
              skipCheck);
    }
    return returnEntry;
  }
@@ -213,1086 +288,1357 @@
  {
    aciListenerMgr.finalizeListenerManager();
    AciEffectiveRights.finalizeOnShutdown();
    DirectoryServer.deregisterSupportedControl(OID_GET_EFFECTIVE_RIGHTS);
    DirectoryServer
        .deregisterSupportedControl(OID_GET_EFFECTIVE_RIGHTS);
  }
    /**
     * Process all global ACI attribute types found in the configuration
     * entry and adds them to that ACI list cache. It also logs messages about
     * the number of ACI attribute types added to the cache. This method is
     * called once at startup.  It also will put the server into  lockdown
     * mode if needed.
     *
     * @param configuration   The config handler containing the ACI
     *  configuration information.
     * @throws InitializationException If there is an error reading
     * the global ACIs from the configuration entry.
     */
    private void processGlobalAcis(
            DseeCompatAccessControlHandlerCfg configuration)
            throws InitializationException {
      SortedSet<Aci> globalAcis = configuration.getGlobalACI();
      try {
        if (globalAcis != null)   {
          aciList.addAci(DN.nullDN(),globalAcis);
          Message message = INFO_ACI_ADD_LIST_GLOBAL_ACIS.get(
                  Integer.toString(globalAcis.size()));
          logError(message);
        }
      }  catch (Exception e) {
        if (debugEnabled())
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        Message message = INFO_ACI_HANDLER_FAIL_PROCESS_GLOBAL_ACI.
                get(String.valueOf(configuration.dn()));
        throw new InitializationException(message, e);
      }
    }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializeAccessControlHandler(
      DseeCompatAccessControlHandlerCfg configuration)
      throws ConfigException, InitializationException
  {
    initStatics();
    DN configurationDN = configuration.dn();
    aciList = new AciList(configurationDN);
    aciListenerMgr = new AciListenerManager(aciList, configurationDN);
    processGlobalAcis(configuration);
    processConfigAcis();
    DirectoryServer.registerSupportedControl(OID_GET_EFFECTIVE_RIGHTS);
  }
    /**
     * Process all ACIs under the "cn=config" naming context and adds them to
     * the ACI list cache. It also logs messages about the number of ACIs added
     * to the cache. This method is called once at startup.  It will put the
     * server in lockdown mode if needed.
     *
     * @throws InitializationException If there is an error searching for
     * the ACIs in the naming context.
     */
    private void processConfigAcis() throws InitializationException {
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isAllowed(DN entryDN, Operation op, Control control)
  {
    boolean ret;
    if (!(ret = skipAccessCheck(op)))
    {
      Entry e = new Entry(entryDN, null, null, null);
      AciLDAPOperationContainer operationContainer =
          new AciLDAPOperationContainer(op, e, control,
              (ACI_READ | ACI_CONTROL));
      ret = accessAllowed(operationContainer);
    }
    if (control.getOID().equals(OID_PROXIED_AUTH_V2)
        || control.getOID().equals(OID_PROXIED_AUTH_V1))
    {
      op.setAttachment(ORIG_AUTH_ENTRY, op.getAuthorizationEntry());
    }
    else if (control.getOID().equals(OID_GET_EFFECTIVE_RIGHTS))
    {
      try
      {
        DN configDN=DN.decode("cn=config");
        LinkedHashSet<String> attrs = new LinkedHashSet<String>(1);
        attrs.add("aci");
        LinkedList<Message>failedACIMsgs=new LinkedList<Message>();
        InternalClientConnection conn =
                InternalClientConnection.getRootConnection();
        InternalSearchOperation op = conn.processSearch(configDN,
                SearchScope.WHOLE_SUBTREE,
                DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                SearchFilter.createFilterFromString("aci=*"), attrs);
        if(!op.getSearchEntries().isEmpty()) {
          int validAcis =
                  aciList.addAci(op.getSearchEntries(), failedACIMsgs);
          if(!failedACIMsgs.isEmpty())
            aciListenerMgr.logMsgsSetLockDownMode(failedACIMsgs);
          Message message = INFO_ACI_ADD_LIST_ACIS.get(
                  Integer.toString(validAcis), String.valueOf(configDN));
          logError(message);
        }
      } catch (DirectoryException e) {
        Message message = INFO_ACI_HANDLER_FAIL_PROCESS_ACI.get();
        throw new InitializationException(message, e);
      }
    }
    /**
     * Checks to see if a LDAP modification is allowed access.
     *
     * @param container  The structure containing the LDAP modifications
     * @param operation The operation to check modify privileges on.
     * operation to check and the evaluation context to apply the check against.
     * @param skipAccessCheck True if access checking should be skipped.
     * @return  True if access is allowed.
     */
    private boolean aciCheckMods(AciLDAPOperationContainer container,
                                 LocalBackendModifyOperation operation,
                                 boolean skipAccessCheck) {
        Entry resourceEntry=container.getResourceEntry();
        DN dn=resourceEntry.getDN();
        List<Modification> modifications=container.getModifications();
        for(Modification m : modifications) {
            Attribute modAttr=m.getAttribute();
            AttributeType modAttrType=modAttr.getAttributeType();
            if(modAttrType.equals(aciType)) {
              /*
               * Check that the operation has modify privileges if
               * it contains an "aci" attribute type.
               */
              if (!operation.getClientConnection().
                   hasPrivilege(Privilege.MODIFY_ACL, operation)) {
                Message message = INFO_ACI_MODIFY_FAILED_PRIVILEGE.
                    get(String.valueOf(container.getResourceDN()),
                        String.valueOf(container.getClientDN()));
                logError(message);
                return false;
              }
            }
            //This access check handles the case where all attributes of this
            //type are being replaced or deleted. If only a subset is being
            //deleted than this access check is skipped.
            ModificationType modType=m.getModificationType();
            if((modType == ModificationType.DELETE &&
                modAttr.isEmpty()) ||
               (modType == ModificationType.REPLACE ||
                modType == ModificationType.INCREMENT)) {
                                /*
                 * Check if we have rights to delete all values of
                 * an attribute type in the resource entry.
                 */
              if(resourceEntry.hasAttribute(modAttrType)) {
                container.setCurrentAttributeType(modAttrType);
                List<Attribute> attrList =
                   resourceEntry.getAttribute(modAttrType,modAttr.getOptions());
                if(attrList != null) {
                  for (Attribute a : attrList) {
                    for (AttributeValue v : a) {
                      container.setCurrentAttributeValue(v);
                      container.setRights(ACI_WRITE_DELETE);
                      if(!skipAccessCheck &&
                              !accessAllowed(container))
                        return false;
                    }
                  }
                }
              }
            }
            if(!modAttr.isEmpty()) {
               for(AttributeValue v : modAttr) {
                   container.setCurrentAttributeType(modAttrType);
                   switch (m.getModificationType())
                   {
                     case ADD:
                     case REPLACE:
                       container.setCurrentAttributeValue(v);
                       container.setRights(ACI_WRITE_ADD);
                       if(!skipAccessCheck && !accessAllowed(container))
                           return false;
                       break;
                     case DELETE:
                       container.setCurrentAttributeValue(v);
                       container.setRights(ACI_WRITE_DELETE);
                       if(!skipAccessCheck && !accessAllowed(container))
                           return false;
                       break;
                     case INCREMENT:
                       Entry modifiedEntry = operation.getModifiedEntry();
                       List<Attribute> modifiedAttrs =
                            modifiedEntry.getAttribute(modAttrType,
                                                       modAttr.getOptions());
                       if (modifiedAttrs != null)
                       {
                         for (Attribute attr : modifiedAttrs)
                         {
                           for (AttributeValue val : attr)
                           {
                             container.setCurrentAttributeValue(val);
                             container.setRights(ACI_WRITE_ADD);
                             if(!skipAccessCheck && !accessAllowed(container))
                                 return false;
                           }
                         }
                       }
                       break;
                   }
                  /*
                   Check if the modification type has an "aci" attribute type.
                   If so, check the syntax of that attribute value. Fail the
                   the operation if the syntax check fails.
                   */
                   if(modAttrType.equals(aciType)  ||
                      modAttrType.equals(globalAciType)) {
                       try {
                           //A global ACI needs a NULL DN, not the DN of the
                           //modification.
                           if(modAttrType.equals(globalAciType))
                               dn=DN.nullDN();
                           Aci.decode(v.getValue(),dn);
                       } catch (AciException ex) {
                           Message message = WARN_ACI_MODIFY_FAILED_DECODE.get(
                               String.valueOf(dn), ex.getMessage());
                           logError(message);
                           return false;
                       }
                   }
               }
            }
        }
        return true;
    }
    /**
     * Performs the test of the deny and allow access lists using the
     * provided evaluation context. The deny list is checked first.
     *
     * @param evalCtx  The evaluation context to use.
     * @return  True if access is allowed.
     */
    private boolean testApplicableLists(AciEvalContext evalCtx) {
        EnumEvalResult res;
        evalCtx.setEvalReason(EnumEvalReason.NO_REASON);
        LinkedList<Aci>denys=evalCtx.getDenyList();
        LinkedList<Aci>allows=evalCtx.getAllowList();
        //If allows list is empty and not doing geteffectiverights return
        //false.
        if(allows.isEmpty() && !(evalCtx.isGetEffectiveRightsEval() &&
              !evalCtx.hasRights(ACI_SELF) &&
              evalCtx.isTargAttrFilterMatchAciEmpty())) {
          evalCtx.setEvalReason(EnumEvalReason.NO_ALLOW_ACIS);
          evalCtx.setDecidingAci(null);
          return false;
        }
        evalCtx.setDenyEval(true);
        for(Aci denyAci : denys) {
           res=Aci.evaluate(evalCtx, denyAci);
            //Failure could be returned if a system limit is hit or
            //search fails
           if(res.equals(EnumEvalResult.FAIL)) {
              evalCtx.setEvalReason(EnumEvalReason.EVALUATED_DENY_ACI);
              evalCtx.setDecidingAci(denyAci);
              return false;
          } else if (res.equals(EnumEvalResult.TRUE)) {
              if(evalCtx.isGetEffectiveRightsEval() &&
                 !evalCtx.hasRights(ACI_SELF) &&
                 !evalCtx.isTargAttrFilterMatchAciEmpty()) {
                  //Iterate to next only if deny ACI contains a targattrfilters
                  //keyword.
                  if(AciEffectiveRights.setTargAttrAci(evalCtx, denyAci, true))
                    continue;
                evalCtx.setEvalReason(EnumEvalReason.EVALUATED_DENY_ACI);
                evalCtx.setDecidingAci(denyAci);
                return false;
              } else {
                evalCtx.setEvalReason(EnumEvalReason.EVALUATED_DENY_ACI);
                evalCtx.setDecidingAci(denyAci);
                return false;
              }
           }
        }
        //Now check the allows -- flip the deny flag to false first.
        evalCtx.setDenyEval(false);
        for(Aci allowAci : allows) {
        res=Aci.evaluate(evalCtx, allowAci);
          if(res.equals(EnumEvalResult.TRUE)) {
            if(evalCtx.isGetEffectiveRightsEval() &&
               !evalCtx.hasRights(ACI_SELF) &&
               !evalCtx.isTargAttrFilterMatchAciEmpty()) {
               //Iterate to next only if deny ACI contains a targattrfilters
               //keyword.
               if(AciEffectiveRights.setTargAttrAci(evalCtx, allowAci, false))
                  continue;
               evalCtx.setEvalReason(EnumEvalReason.EVALUATED_ALLOW_ACI);
               evalCtx.setDecidingAci(allowAci);
               return true;
            } else {
              evalCtx.setEvalReason(EnumEvalReason.EVALUATED_ALLOW_ACI);
              evalCtx.setDecidingAci(allowAci);
              return true;
            }
          }
        }
        //Nothing matched fall through.
        evalCtx.setEvalReason(EnumEvalReason.NO_MATCHED_ALLOWS_ACIS);
        evalCtx.setDecidingAci(null);
        return false;
    }
    /**
     * Creates the allow and deny ACI lists based on the provided target
     * match context. These lists are stored in the evaluation context.
     * @param candidates  List of all possible ACI candidates.
     * @param targetMatchCtx Target matching context to use for testing each
     * ACI.
     */
    private void createApplicableList(LinkedList<Aci> candidates,
                                      AciTargetMatchContext targetMatchCtx)
    {
        LinkedList<Aci>denys=new LinkedList<Aci>();
        LinkedList<Aci>allows=new LinkedList<Aci>();
        for(Aci aci : candidates) {
            if(Aci.isApplicable(aci, targetMatchCtx)) {
                if (aci.hasAccessType(EnumAccessType.DENY)) {
                    denys.add(aci);
                }
                if(aci.hasAccessType(EnumAccessType.ALLOW)) {
                   allows.add(aci);
                }
            }
           if(targetMatchCtx.getTargAttrFiltersMatch())
              targetMatchCtx.setTargAttrFiltersMatch(false);
        }
        targetMatchCtx.setAllowList(allows);
        targetMatchCtx.setDenyList(denys);
    }
    /**
     * Check to see if the client entry has BYPASS_ACL privileges
     * for this operation.
     * @param operation The operation to check privileges on.
     * @return True if access checking can be skipped because
     * the operation client connection has BYPASS_ACL privileges.
     */
    private boolean skipAccessCheck(Operation operation) {
        return operation.getClientConnection().
                hasPrivilege(Privilege.BYPASS_ACL, operation);
    }
    /**
     * Check to see if the specified entry has the specified privilege.
     *
     * @param e The entry to check privileges on.
     * @return  {@code true} if the entry has the
     *          specified privilege, or {@code false} if not.
     */
    private boolean skipAccessCheck(Entry e) {
        return ClientConnection.hasPrivilege(e, Privilege.BYPASS_ACL);
    }
    /**
     * Check access using the specified container. This container will have all
     * of the information to gather applicable ACIs and perform evaluation on
     * them.
     *
     * @param container An ACI operation container which has all of the
     * information needed to check access.
     *
     * @return True if access is allowed.
     */
     boolean accessAllowed(AciContainer container)
    {
        DN dn = container.getResourceEntry().getDN();
        //For ACI_WRITE_ADD and ACI_WRITE_DELETE set the ACI_WRITE
        //right.
        if(container.hasRights(ACI_WRITE_ADD) ||
           container.hasRights(ACI_WRITE_DELETE))
                container.setRights(container.getRights() | ACI_WRITE);
        //Check if the ACI_SELF right needs to be set (selfwrite right).
        //Only done if the right is ACI_WRITE,  an attribute value is set and
        //that attribute value is a DN.
        if((container.getCurrentAttributeValue() != null) &&
           (container.hasRights(ACI_WRITE)) &&
           (isAttributeDN(container.getCurrentAttributeType())))  {
          String DNString=null;
          try {
            DNString =
                container.getCurrentAttributeValue().getValue().toString();
            DN tmpDN = DN.decode(DNString);
            //Have a valid DN, compare to clientDN to see if the ACI_SELF
            //right should be set.
            if(tmpDN.equals(container.getClientDN())) {
              container.setRights(container.getRights() | ACI_SELF);
            }
          } catch (DirectoryException ex) {
             //Log a message and keep going.
             Message message = WARN_ACI_NOT_VALID_DN.get(DNString);
             logError(message);
          }
        }
        //Check proxy authorization only if the entry has not already been
        //processed (working on a new entry). If working on a new entry, then
        //only do a proxy check if the right is not set to ACI_PROXY and the
        //proxied authorization control has been decoded.
        if(!container.hasSeenEntry()) {
          if(container.isProxiedAuthorization() &&
             !container.hasRights(ACI_PROXY) &&
             !container.hasRights(ACI_SKIP_PROXY_CHECK)) {
              int currentRights=container.getRights();
              //Save the current rights so they can be put back if on success.
              container.setRights(ACI_PROXY);
              //Switch to the original authorization entry, not the proxied one.
              container.useOrigAuthorizationEntry(true);
              if(!accessAllowed(container))
                  return false;
              //Access is ok, put the original rights back.
              container.setRights(currentRights);
              //Put the proxied authorization entry back to the current
              //authorization entry.
              container.useOrigAuthorizationEntry(false);
          }
          //Set the seen flag so proxy processing is not performed for this
          //entry again.
          container.setSeenEntry(true);
       }
        /*
         * First get all allowed candidate ACIs.
         */
        LinkedList<Aci>candidates = aciList.getCandidateAcis(dn);
        /*
         * Create an applicable list of ACIs by target matching each
         * candidate ACI against the container's target match view.
         */
        createApplicableList(candidates,container);
        /*
         * Evaluate the applicable list.
         */
        boolean ret=testApplicableLists(container);
        //Build summary string if doing geteffectiverights eval.
        if(container.isGetEffectiveRightsEval())
          AciEffectiveRights.createSummary(container, ret, "main");
        return ret;
    }
    /**
     * Check if the specified attribute type is a DN by checking if its syntax
     * OID is equal to the DN syntax OID.
     * @param attribute The attribute type to check.
     * @return True if the attribute type syntax OID is equal to a DN syntax
     *         OID.
     */
    private boolean isAttributeDN(AttributeType attribute) {
      return (attribute.getSyntaxOID().equals(SYNTAX_DN_OID));
    }
    /**
     * Performs an access check against all of the attributes of an entry.
     * The attributes that fail access are removed from the entry. This method
     * performs the processing needed for the filterEntry method processing.
     *
     * @param container The search or compare container which has all of the
     * information needed to filter the attributes for this entry.
     * @return The  entry to send back to the client, minus any attribute
     * types that failed access check.
     */
    private SearchResultEntry
    accessAllowedAttrs(AciLDAPOperationContainer container) {
        Entry e=container.getResourceEntry();
        List<AttributeType> typeList=getAllAttrs(e);
        for(AttributeType attrType : typeList) {
            if(container.hasAllUserAttributes() && !attrType.isOperational())
                continue;
            if(container.hasAllOpAttributes() && attrType.isOperational())
                continue;
            container.setCurrentAttributeType(attrType);
            if(!accessAllowed(container))
                e.removeAttribute(attrType);
        }
        return container.getSearchResultEntry();
    }
    /**
     * Gathers all of the attribute types in an entry along with the
     * "objectclass" attribute type in a List. The "objectclass" attribute is
     * added to the list first so it is evaluated first.
     *
     * @param e Entry to gather the attributes for.
     * @return List containing the attribute types.
     */
    private List<AttributeType> getAllAttrs(Entry e) {
        Map<AttributeType,List<Attribute>> attrMap = e.getUserAttributes();
        Map<AttributeType,List<Attribute>> opAttrMap =
                                                   e.getOperationalAttributes();
        List<AttributeType> typeList=new LinkedList<AttributeType>();
        Attribute attr=e.getObjectClassAttribute();
        /*
         * When a search is not all attributes returned, the "objectclass"
         * attribute type is missing from the entry.
         */
        if(attr != null) {
           AttributeType ocType=attr.getAttributeType();
           typeList.add(ocType);
        }
        typeList.addAll(attrMap.keySet());
        typeList.addAll(opAttrMap.keySet());
        return typeList;
    }
    /*
     * TODO Evaluate performance of this method.
     * TODO Evaluate security concerns of this method. Logic from this method
     * taken almost directly from DS6 implementation.
     *
     *  I find the work done in the accessAllowedEntry method, particularly
     *  with regard to the entry test evaluation, to be very confusing and
     *  potentially pretty inefficient.  I'm also concerned that the "return
     *  "true" inside the for loop could potentially allow access when it
     *  should be denied.
     */
    /**
     * Check if access is allowed on an entry. Access is checked by iterating
     * through each attribute of an entry, starting with the "objectclass"
     * attribute type.
     *
     * If access is allowed on the entry based on one of it's attribute types,
     * then a possible second access check is performed. This second check is
     * only performed if an entry test ACI was found during the earlier
     * successful access check. An entry test ACI has no "targetattrs" keyword,
     * so allowing access based on an attribute type only would be incorrect.
     *
     * @param container ACI search container containing all of the information
     * needed to check access.
     *
     * @return True if access is allowed.
     */
     boolean accessAllowedEntry(AciLDAPOperationContainer container) {
        boolean ret=false;
        //set flag that specifies this is the first attribute evaluated
        //in the entry
        container.setIsFirstAttribute(true);
        List<AttributeType> typeList=getAllAttrs(container.getResourceEntry());
        for(AttributeType attrType : typeList) {
            container.setCurrentAttributeType(attrType);
            /*
             * Check if access is allowed. If true, then check to see if an
             * entry test rule was found (no targetattrs) during target match
             * evaluation. If such a rule was found, set the current attribute
             * type to "null" and check access again so that rule is applied.
             */
            if(accessAllowed(container)) {
                if(container.hasEntryTestRule()) {
                    container.setCurrentAttributeType(null);
                    if(!accessAllowed(container)) {
                        /*
                         * If we failed because of a deny permission-bind rule,
                         * we need to stop and return false.
                         */
                        if(container.isDenyEval()) {
                            return false;
                        }
                        /*
                         * If we failed because there was no explicit
                         * allow rule, then we grant implicit access to the
                         * entry.
                         */
                    }
                }
                return true;
            }
        }
        return ret;
    }
    /**
     * Test the attribute types of the search filter for access. This method
     * supports the search right.
     *
     * @param container  The container used in the access evaluation.
     * @param filter The filter to check access on.
     * @return  True if all attribute types in the filter have access.
     * @throws DirectoryException If there is a problem matching the entry
     *                            using the provided filter.
     */
    private boolean
    testFilter(AciLDAPOperationContainer container, SearchFilter filter)
    throws DirectoryException {
        boolean ret=true;
        //If the resource entry has a dn equal to "cn=debugsearch" and it
        //contains the special attribute type "debugsearchindex", then the
        //resource entry is a pseudo entry created for debug purposes. Return
        //true if that is the case.
        if(debugSearchIndexDN.equals(container.getResourceDN()) &&
           container.getResourceEntry().hasAttribute(debugSearchIndex))
          return true;
        switch (filter.getFilterType()) {
            case AND:
            case OR: {
                for (SearchFilter f : filter.getFilterComponents())
                    if(!testFilter(container, f))
                        return false ;
                break;
            }
            case NOT: {
                ret=false;
                SearchFilter f = filter.getNotComponent();
                if(f.matchesEntry(container.getResourceEntry()))
                  ret=true;
                if(ret)
                  ret=testFilter(container, f);
                ret=!ret;
                break;
            }
            default: {
                AttributeType attrType=filter.getAttributeType();
                container.setCurrentAttributeType(attrType);
                ret=accessAllowed(container);
            }
        }
        return ret;
    }
    /**
     * Check access using the accessAllowed method. The
     * LDAP add, compare, modify and delete operations use this function.
     * The other supported LDAP operations have more specialized checks.
     * @param operationContainer  The container containing the information
     * needed to evaluate this operation.
     * @param operation The operation being evaluated.
     * @return True if this operation is allowed access.
     */
    private boolean isAllowed(AciLDAPOperationContainer operationContainer,
                              Operation operation) {
        return skipAccessCheck(operation) || accessAllowed(operationContainer);
    }
    /**
     * Evaluate an entry to be added to see if it has any "aci"
     * attribute type. If it does, examines each "aci" attribute type
     * value for syntax errors. All of the "aci" attribute type values
     * must pass syntax check for the add operation to proceed. Any
     * entry with an "aci" attribute type must have "modify-acl"
     * privileges.
     *
     * @param entry  The entry to be examined.
     * @param operation The operation to to check privileges on.
     * @param clientDN The authorization DN.
     * @return True if the entry has no ACI attributes or if all of the "aci"
     * attributes values pass ACI syntax checking.
     */
    private boolean
       verifySyntax(Entry entry, Operation operation, DN clientDN) {
      if(entry.hasOperationalAttribute(aciType)) {
        /*
         * Check that the operation has "modify-acl" privileges since the
         * entry to be added has an "aci" attribute type.
         */
        if (!operation.getClientConnection().
             hasPrivilege(Privilege.MODIFY_ACL, operation))  {
          Message message = INFO_ACI_ADD_FAILED_PRIVILEGE.get(
              String.valueOf(entry.getDN()), String.valueOf(clientDN));
          logError(message);
          return false;
        }
        List<Attribute> attributeList =
             entry.getOperationalAttribute(aciType, null);
        for (Attribute attribute : attributeList)
        GetEffectiveRightsRequestControl getEffectiveRightsControl;
        if (control instanceof LDAPControl)
        {
          for (AttributeValue value : attribute)
          {
            try {
              DN dn=entry.getDN();
              Aci.decode(value.getValue(),dn);
            } catch (AciException ex) {
              Message message = WARN_ACI_ADD_FAILED_DECODE.get(
                  String.valueOf(entry.getDN()), ex.getMessage());
              logError(message);
              return false;
            }
          }
          getEffectiveRightsControl =
              GetEffectiveRightsRequestControl.DECODER.decode(control
                  .isCritical(), ((LDAPControl) control).getValue());
        }
      }
    return true;
  }
    /**
     * Check access on add operations.
     *
     * @param operation The add operation to check access on.
     * @return  True if access is allowed.
     */
    public boolean isAllowed(LocalBackendAddOperation operation) {
        AciLDAPOperationContainer operationContainer =
                new AciLDAPOperationContainer(operation, ACI_ADD);
        boolean ret=isAllowed(operationContainer,operation);
        //LDAP add needs a verify ACI syntax step in case any
        //"aci" attribute types are being added.
        if(ret)
          ret=verifySyntax(operation.getEntryToAdd(), operation,
                           operationContainer.getClientDN());
        return ret;
    }
   /**
     * Check access on compare operations. Note that the attribute
     * type is unavailable at this time, so this method partially
     * parses the raw attribute string to get the base attribute
     * type. Options are ignored.
     *
     * @param operation The compare operation to check access on.
     * @return  True if access is allowed.
     */
   public boolean isAllowed(LocalBackendCompareOperation operation) {
       AciLDAPOperationContainer operationContainer =
               new AciLDAPOperationContainer(operation, ACI_COMPARE);
       String baseName;
       String rawAttributeType=operation.getRawAttributeType();
       int  semicolonPosition=rawAttributeType.indexOf(';');
       if (semicolonPosition > 0)
         baseName =
             toLowerCase(rawAttributeType.substring(0, semicolonPosition));
       else
         baseName = toLowerCase(rawAttributeType);
       AttributeType attributeType;
       if((attributeType =
           DirectoryServer.getAttributeType(baseName)) == null)
           attributeType = DirectoryServer.getDefaultAttributeType(baseName);
       AttributeValue attributeValue =
           AttributeValues.create(attributeType,
               operation.getAssertionValue());
       operationContainer.setCurrentAttributeType(attributeType);
       operationContainer.setCurrentAttributeValue(attributeValue);
       return isAllowed(operationContainer, operation);
   }
   /**
     * Check access on delete operations.
     *
     * @param operation The delete operation to check access on.
     * @return  True if access is allowed.
     */
   public boolean isAllowed(LocalBackendDeleteOperation operation) {
       AciLDAPOperationContainer operationContainer=
               new AciLDAPOperationContainer(operation, ACI_DELETE);
       return isAllowed(operationContainer, operation);
   }
   /**
    * Check access on modify operations.
    *
    * @param operation The modify operation to check access on.
    * @return  True if access is allowed.
    */
  public boolean isAllowed(LocalBackendModifyOperation operation) {
      AciLDAPOperationContainer operationContainer=
              new AciLDAPOperationContainer(operation, ACI_NULL);
      return aciCheckMods(operationContainer, operation,
                          skipAccessCheck(operation));
  }
  /**
   * Checks access on a search operation.
   * @param operation The search operation class containing information to
   * check the access on.
   * @param entry  The entry to evaluate access.
   * @return   True if access is allowed.
   */
  public boolean
  maySend(SearchOperation operation, SearchResultEntry entry) {
      AciLDAPOperationContainer operationContainer =
              new AciLDAPOperationContainer(operation,
                      (ACI_SEARCH), entry);
      boolean ret;
      if(!(ret=skipAccessCheck(operation))) {
          try {
            ret=testFilter(operationContainer, operation.getFilter());
          } catch (DirectoryException ex)  {
            ret=false;
          }
          if (ret) {
              operationContainer.clearEvalAttributes(ACI_NULL);
              operationContainer.setRights(ACI_READ);
              ret=accessAllowedEntry(operationContainer);
            if(ret) {
              if(!operationContainer.hasEvalUserAttributes())
                operation.setAttachment(ALL_USER_ATTRS_MATCHED,
                        ALL_USER_ATTRS_MATCHED);
              if(!operationContainer.hasEvalOpAttributes())
                operation.setAttachment(ALL_OP_ATTRS_MATCHED,
                        ALL_OP_ATTRS_MATCHED);
            }
          }
      }
      //Save a copy of the full resource entry for possible
      //userattr bind rule or geteffectiveright's evaluations in the filterEnty
      //method.
      operation.setAttachment(ALL_ATTRS_RESOURCE_ENTRY, entry );
      return ret;
  }
  /*
   * TODO Rename this method. Needs to be changed in SearchOperation.
   *
   * I find the name of the filterEntry method to be misleading because
   * it works on a search operation but has nothing to do with the search
   * filter.  Something like "removeDisallowedAttributes" would be clearer.
   */
  /**
   * Checks access on each attribute in an entry. It removes those attributes
   * that fail access check.
   *
   * @param operation The search operation class containing information to
   * check access on.
   * @param entry   The entry containing the attributes.
   * @return    The entry to return minus filtered attributes.
   */
  public SearchResultEntry filterEntry(SearchOperation operation,
                                       SearchResultEntry entry) {
      AciLDAPOperationContainer operationContainer =
              new AciLDAPOperationContainer(operation,
                                            (ACI_READ), entry);
      //Proxy access check has already been done for this entry in the maySend
      //method, set the seen flag to true to bypass any proxy check.
      operationContainer.setSeenEntry(true);
      SearchResultEntry returnEntry;
      boolean skipCheck=skipAccessCheck(operation);
      if(!skipCheck) {
          returnEntry=accessAllowedAttrs(operationContainer);
      } else
          returnEntry=entry;
      if(operationContainer.hasGetEffectiveRightsControl()) {
          returnEntry =
            AciEffectiveRights.addRightsToEntry(this, operation.getAttributes(),
                                               operationContainer, returnEntry,
                                               skipCheck);
      }
      return returnEntry;
  }
  /**
   * Perform all needed RDN checks for the modifyDN operation. The old RDN is
   * not equal to the new RDN. The access checks are:
   *
   *  - Verify WRITE access to the original entry.
   *  - Verfiy WRITE_ADD access on each RDN component of the new RDN. The
   *    WRITE_ADD access is used because this access could be restricted by
   *    the targattrfilters keyword.
   *  - If the deleteOLDRDN flag is set, verify WRITE_DELETE access on the
   *    old RDN. The WRITE_DELETE access is used because this access could be
   *    restricted by the targattrfilters keyword.
   *
   * @param operation   The ModifyDN operation class containing information to
   * check access on.
   * @param oldRDN      The old RDN component.
   * @param newRDN      The new RDN component.
   * @return True if access is allowed.
   */
  private boolean aciCheckRDNs(LocalBackendModifyDNOperation operation,
                               RDN oldRDN,
                               RDN newRDN) {
      boolean ret;
      AciLDAPOperationContainer operationContainer =
              new AciLDAPOperationContainer(operation, (ACI_WRITE),
                      operation.getOriginalEntry());
      ret=accessAllowed(operationContainer);
      if(ret)
          ret=checkRDN(ACI_WRITE_ADD, newRDN, operationContainer);
      if(ret && operation.deleteOldRDN()) {
          ret =
            checkRDN(ACI_WRITE_DELETE, oldRDN, operationContainer);
      }
      return ret;
  }
  /**
   * Check access on each attribute-value pair component of the specified RDN.
   * There may be more than one attribute-value pair if the RDN is multi-valued.
   *
   * @param right  The access right to check for.
   * @param rdn  The RDN to examine the attribute-value pairs of.
   * @param container The container containing the information needed to
   * evaluate the specified RDN.
   * @return  True if access is allowed for all attribute-value pairs.
   */
  private boolean checkRDN(int right, RDN rdn, AciContainer container) {
        boolean ret=false;
        int numAVAs = rdn.getNumValues();
        container.setRights(right);
        for (int i = 0; i < numAVAs; i++){
            AttributeType type=rdn.getAttributeType(i);
            AttributeValue value=rdn.getAttributeValue(i);
            container.setCurrentAttributeType(type);
            container.setCurrentAttributeValue(value);
            if(!(ret=accessAllowed(container)))
                break;
        else
        {
          getEffectiveRightsControl =
              (GetEffectiveRightsRequestControl) control;
        }
        return ret;
  }
  /**
   * Check access on the new superior entry if it exists. If the entry does not
   * exist or the DN cannot be locked then false is returned.
   *
   * @param superiorDN The DN of the new superior entry.
   * @param op The modifyDN operation to check access on.
   * @return True if access is granted to the new superior entry.
   * @throws DirectoryException  If a problem occurs while trying to
   *                             retrieve the new superior entry.
   */
  private boolean aciCheckSuperiorEntry(DN superiorDN,
      LocalBackendModifyDNOperation op)
  throws DirectoryException {
    boolean ret=false;
    Lock entryLock = null;
    for (int i=0; i < 3; i++)  {
      entryLock = LockManager.lockRead(superiorDN);
      if (entryLock != null)
        break;
    }
    if (entryLock == null) {
      Message message = WARN_ACI_HANDLER_CANNOT_LOCK_NEW_SUPERIOR_USER.get(
          String.valueOf(superiorDN));
      logError(message);
      return false;
    }
    try {
      Entry superiorEntry=DirectoryServer.getEntry(superiorDN);
      if(superiorEntry!= null) {
        AciLDAPOperationContainer operationContainer =
                new AciLDAPOperationContainer(op, (ACI_IMPORT),
                        superiorEntry);
        ret=accessAllowed(operationContainer);
        op.setAttachment(OID_GET_EFFECTIVE_RIGHTS,
            getEffectiveRightsControl);
      }
    }  finally {
          LockManager.unlock(superiorDN, entryLock);
      catch (DirectoryException de)
      {
        Message message =
            WARN_ACI_SYNTAX_DECODE_EFFECTIVERIGHTS_FAIL.get(de
                .getMessage());
        logError(message);
        ret = false;
      }
    }
    return ret;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isAllowed(ExtendedOperation operation)
  {
    boolean ret;
    if (!(ret = skipAccessCheck(operation)))
    {
      Entry e =
          new Entry(operation.getAuthorizationDN(), null, null, null);
      AciLDAPOperationContainer operationContainer =
          new AciLDAPOperationContainer(operation, e,
              (ACI_READ | ACI_EXT_OP));
      ret = accessAllowed(operationContainer);
    }
    return ret;
  }
  /**
   * Check access on add operations.
   *
   * @param operation
   *          The add operation to check access on.
   * @return True if access is allowed.
   */
  @Override
  public boolean isAllowed(LocalBackendAddOperation operation)
  {
    AciLDAPOperationContainer operationContainer =
        new AciLDAPOperationContainer(operation, ACI_ADD);
    boolean ret = isAllowed(operationContainer, operation);
    // LDAP add needs a verify ACI syntax step in case any
    // "aci" attribute types are being added.
    if (ret)
    {
      ret =
          verifySyntax(operation.getEntryToAdd(), operation,
              operationContainer.getClientDN());
    }
    return ret;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isAllowed(LocalBackendBindOperation bindOperation)
  {
    // Not planned to be implemented.
    return true;
  }
  /**
   * Check access on compare operations. Note that the attribute type is
   * unavailable at this time, so this method partially parses the raw
   * attribute string to get the base attribute type. Options are
   * ignored.
   *
   * @param operation
   *          The compare operation to check access on.
   * @return True if access is allowed.
   */
  @Override
  public boolean isAllowed(LocalBackendCompareOperation operation)
  {
    AciLDAPOperationContainer operationContainer =
        new AciLDAPOperationContainer(operation, ACI_COMPARE);
    String baseName;
    String rawAttributeType = operation.getRawAttributeType();
    int semicolonPosition = rawAttributeType.indexOf(';');
    if (semicolonPosition > 0)
    {
      baseName =
          toLowerCase(rawAttributeType.substring(0, semicolonPosition));
    }
    else
    {
      baseName = toLowerCase(rawAttributeType);
    }
    AttributeType attributeType;
    if ((attributeType = DirectoryServer.getAttributeType(baseName)) == null)
    {
      attributeType = DirectoryServer.getDefaultAttributeType(baseName);
    }
    AttributeValue attributeValue =
        AttributeValues.create(attributeType, operation
            .getAssertionValue());
    operationContainer.setCurrentAttributeType(attributeType);
    operationContainer.setCurrentAttributeValue(attributeValue);
    return isAllowed(operationContainer, operation);
  }
  /**
   * Check access on delete operations.
   *
   * @param operation
   *          The delete operation to check access on.
   * @return True if access is allowed.
   */
  @Override
  public boolean isAllowed(LocalBackendDeleteOperation operation)
  {
    AciLDAPOperationContainer operationContainer =
        new AciLDAPOperationContainer(operation, ACI_DELETE);
    return isAllowed(operationContainer, operation);
  }
  /**
   * Checks access on a modifyDN operation.
   *
   * @param operation The modifyDN operation to check access on.
   * @param operation
   *          The modifyDN operation to check access on.
   * @return True if access is allowed.
   */
  @Override
  public boolean isAllowed(LocalBackendModifyDNOperation operation)
  {
    boolean ret = true;
    DN newSuperiorDN;
    RDN oldRDN = operation.getOriginalEntry().getDN().getRDN();
    RDN newRDN = operation.getNewRDN();
    if (!skipAccessCheck(operation))
    {
      // If this is a modifyDN move to a new superior, then check if the
      // superior DN has import accesss.
      if ((newSuperiorDN = operation.getNewSuperior()) != null)
      {
        try
        {
          ret = aciCheckSuperiorEntry(newSuperiorDN, operation);
        }
        catch (DirectoryException ex)
        {
          ret = false;
        }
      }
      boolean rdnEquals = oldRDN.equals(newRDN);
      // Perform the RDN access checks only if the RDNs are not equal.
      if (ret && !rdnEquals)
      {
        ret = aciCheckRDNs(operation, oldRDN, newRDN);
      }
      // If this is a modifyDN move to a new superior, then check if the
      // original entry DN has export access.
      if (ret && (newSuperiorDN != null))
      {
        AciLDAPOperationContainer operationContainer =
            new AciLDAPOperationContainer(operation, (ACI_EXPORT),
                operation.getOriginalEntry());
        // The RDNs are not equal, skip the proxy check since it was
        // already performed in the aciCheckRDNs call above.
        if (!rdnEquals)
        {
          operationContainer.setSeenEntry(true);
        }
        ret = accessAllowed(operationContainer);
      }
    }
    return ret;
  }
  /**
   * Check access on modify operations.
   *
   * @param operation
   *          The modify operation to check access on.
   * @return True if access is allowed.
   */
  public boolean isAllowed(LocalBackendModifyDNOperation operation) {
      boolean ret=true;
      DN newSuperiorDN;
      RDN oldRDN=operation.getOriginalEntry().getDN().getRDN();
      RDN newRDN=operation.getNewRDN();
      if(!skipAccessCheck(operation)) {
          //If this is a modifyDN move to a new superior, then check if the
          //superior DN has import accesss.
          if((newSuperiorDN=operation.getNewSuperior()) != null) {
             try {
               ret=aciCheckSuperiorEntry(newSuperiorDN, operation);
             } catch (DirectoryException ex) {
               ret=false;
             }
          }
          boolean rdnEquals=oldRDN.equals(newRDN);
          //Perform the RDN access checks only if the RDNs are not equal.
          if(ret && !rdnEquals)
              ret=aciCheckRDNs(operation, oldRDN, newRDN);
          //If this is a modifyDN move to a new superior, then check if the
          //original entry DN has export access.
          if(ret && (newSuperiorDN != null)) {
              AciLDAPOperationContainer operationContainer =
                      new AciLDAPOperationContainer(operation, (ACI_EXPORT),
                                             operation.getOriginalEntry());
                 //The RDNs are not equal, skip the proxy check since it was
                 //already performed in the aciCheckRDNs call above.
                 if(!rdnEquals)
                     operationContainer.setSeenEntry(true);
                 ret=accessAllowed(operationContainer);
          }
      }
      return ret;
  @Override
  public boolean isAllowed(LocalBackendModifyOperation operation)
  {
    AciLDAPOperationContainer operationContainer =
        new AciLDAPOperationContainer(operation, ACI_NULL);
    return aciCheckMods(operationContainer, operation,
        skipAccessCheck(operation));
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isAllowed(DN entryDN, Operation op, Control control) {
    boolean ret;
    if(!(ret=skipAccessCheck(op))) {
      Entry e = new Entry(entryDN, null, null, null);
      AciLDAPOperationContainer operationContainer =
              new AciLDAPOperationContainer(op, e, control,
                                            (ACI_READ | ACI_CONTROL));
      ret=accessAllowed(operationContainer);
    }
    if(control.getOID().equals(OID_PROXIED_AUTH_V2) ||
            control.getOID().equals(OID_PROXIED_AUTH_V1))
      op.setAttachment(ORIG_AUTH_ENTRY, op.getAuthorizationEntry());
    else if(control.getOID().equals(OID_GET_EFFECTIVE_RIGHTS)) {
      try {
        GetEffectiveRightsRequestControl getEffectiveRightsControl;
        if(control instanceof LDAPControl)
        {
          getEffectiveRightsControl = GetEffectiveRightsRequestControl.DECODER
              .decode(control.isCritical(), ((LDAPControl) control).getValue());
        }
        else
        {
          getEffectiveRightsControl = (GetEffectiveRightsRequestControl)control;
        }
        op.setAttachment(OID_GET_EFFECTIVE_RIGHTS, getEffectiveRightsControl);
      } catch  (DirectoryException de)  {
        Message message =
            WARN_ACI_SYNTAX_DECODE_EFFECTIVERIGHTS_FAIL.get(de.getMessage());
        logError(message);
        ret=false;
      }
    }
    return ret;
  public boolean isAllowed(LocalBackendSearchOperation searchOperation)
  {
    // Not planned to be implemented.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isAllowed(ExtendedOperation operation) {
  public boolean mayProxy(Entry proxyUser, Entry proxiedUser,
      Operation op)
  {
    boolean ret;
    if(!(ret=skipAccessCheck(operation))) {
      Entry e = new Entry(operation.getAuthorizationDN(), null, null, null);
    if (!(ret = skipAccessCheck(proxyUser)))
    {
      AuthenticationInfo authInfo =
          new AuthenticationInfo(proxyUser, DirectoryServer
              .isRootDN(proxyUser.getDN()));
      AciLDAPOperationContainer operationContainer =
         new AciLDAPOperationContainer(operation, e, (ACI_READ | ACI_EXT_OP));
      ret=accessAllowed(operationContainer);
          new AciLDAPOperationContainer(op, proxiedUser, authInfo,
              ACI_PROXY);
      ret = accessAllowedEntry(operationContainer);
    }
    return ret;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean maySend(DN dn, SearchOperation operation,
                         SearchResultReference reference) {
      SearchResultReference reference)
  {
    boolean ret;
    if(!(ret=skipAccessCheck(operation))) {
    if (!(ret = skipAccessCheck(operation)))
    {
      Entry e = new Entry(dn, null, null, null);
      AttributeBuilder builder =
        new AttributeBuilder(refAttrType, ATTR_REFERRAL_URL);
          new AttributeBuilder(refAttrType, ATTR_REFERRAL_URL);
      List<String> URLStrings = reference.getReferralURLs();
      // Load the values, a bind rule might want to evaluate them.
      for (String URLString : URLStrings) {
      for (String URLString : URLStrings)
      {
        builder.add(AttributeValues.create(refAttrType, URLString));
      }
      e.addAttribute(builder.toAttribute(),null);
      SearchResultEntry se=new  SearchResultEntry(e);
      e.addAttribute(builder.toAttribute(), null);
      SearchResultEntry se = new SearchResultEntry(e);
      AciLDAPOperationContainer operationContainer =
              new AciLDAPOperationContainer(operation,
                                           (ACI_READ), se);
          new AciLDAPOperationContainer(operation, (ACI_READ), se);
      operationContainer.setCurrentAttributeType(refAttrType);
      ret=accessAllowed(operationContainer);
      ret = accessAllowed(operationContainer);
    }
    return ret;
  }
  /**
   * {@inheritDoc}
   * Checks access on a search operation.
   *
   * @param operation
   *          The search operation class containing information to check
   *          the access on.
   * @param entry
   *          The entry to evaluate access.
   * @return True if access is allowed.
   */
  @Override
  public boolean
  mayProxy(Entry proxyUser, Entry proxiedUser, Operation op) {
      boolean ret;
      if(!(ret=skipAccessCheck(proxyUser))) {
          AuthenticationInfo authInfo =
              new AuthenticationInfo(proxyUser,
                     DirectoryServer.isRootDN(proxyUser.getDN()));
          AciLDAPOperationContainer operationContainer =
              new AciLDAPOperationContainer(op, proxiedUser,
                                            authInfo, ACI_PROXY);
          ret=accessAllowedEntry(operationContainer);
  public boolean maySend(SearchOperation operation,
      SearchResultEntry entry)
  {
    AciLDAPOperationContainer operationContainer =
        new AciLDAPOperationContainer(operation, (ACI_SEARCH), entry);
    boolean ret;
    if (!(ret = skipAccessCheck(operation)))
    {
      try
      {
        ret = testFilter(operationContainer, operation.getFilter());
      }
      return ret;
      catch (DirectoryException ex)
      {
        ret = false;
      }
      if (ret)
      {
        operationContainer.clearEvalAttributes(ACI_NULL);
        operationContainer.setRights(ACI_READ);
        ret = accessAllowedEntry(operationContainer);
        if (ret)
        {
          if (!operationContainer.hasEvalUserAttributes())
          {
            operation.setAttachment(ALL_USER_ATTRS_MATCHED,
                ALL_USER_ATTRS_MATCHED);
          }
          if (!operationContainer.hasEvalOpAttributes())
          {
            operation.setAttachment(ALL_OP_ATTRS_MATCHED,
                ALL_OP_ATTRS_MATCHED);
          }
        }
      }
    }
    // Save a copy of the full resource entry for possible
    // userattr bind rule or geteffectiveright's evaluations in the
    // filterEnty method.
    operation.setAttachment(ALL_ATTRS_RESOURCE_ENTRY, entry);
    return ret;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isAllowed(LocalBackendBindOperation bindOperation) {
      //Not planned to be implemented.
      return true;
  }
  /**
   * {@inheritDoc}
   * Check access using the specified container. This container will
   * have all of the information to gather applicable ACIs and perform
   * evaluation on them.
   *
   * @param container
   *          An ACI operation container which has all of the
   *          information needed to check access.
   * @return True if access is allowed.
   */
  @Override
  public boolean isAllowed(LocalBackendSearchOperation searchOperation) {
      //Not planned to be implemented.
  boolean accessAllowed(AciContainer container)
  {
    DN dn = container.getResourceEntry().getDN();
    // For ACI_WRITE_ADD and ACI_WRITE_DELETE set the ACI_WRITE
    // right.
    if (container.hasRights(ACI_WRITE_ADD)
        || container.hasRights(ACI_WRITE_DELETE))
    {
      container.setRights(container.getRights() | ACI_WRITE);
    }
    // Check if the ACI_SELF right needs to be set (selfwrite right).
    // Only done if the right is ACI_WRITE, an attribute value is set
    // and that attribute value is a DN.
    if ((container.getCurrentAttributeValue() != null)
        && (container.hasRights(ACI_WRITE))
        && (isAttributeDN(container.getCurrentAttributeType())))
    {
      String DNString = null;
      try
      {
        DNString =
            container.getCurrentAttributeValue().getValue().toString();
        DN tmpDN = DN.decode(DNString);
        // Have a valid DN, compare to clientDN to see if the ACI_SELF
        // right should be set.
        if (tmpDN.equals(container.getClientDN()))
        {
          container.setRights(container.getRights() | ACI_SELF);
        }
      }
      catch (DirectoryException ex)
      {
        // Log a message and keep going.
        Message message = WARN_ACI_NOT_VALID_DN.get(DNString);
        logError(message);
      }
    }
    // Check proxy authorization only if the entry has not already been
    // processed (working on a new entry). If working on a new entry,
    // then only do a proxy check if the right is not set to ACI_PROXY
    // and the proxied authorization control has been decoded.
    if (!container.hasSeenEntry())
    {
      if (container.isProxiedAuthorization()
          && !container.hasRights(ACI_PROXY)
          && !container.hasRights(ACI_SKIP_PROXY_CHECK))
      {
        int currentRights = container.getRights();
        // Save the current rights so they can be put back if on
        // success.
        container.setRights(ACI_PROXY);
        // Switch to the original authorization entry, not the proxied
        // one.
        container.useOrigAuthorizationEntry(true);
        if (!accessAllowed(container))
        {
          return false;
        }
        // Access is ok, put the original rights back.
        container.setRights(currentRights);
        // Put the proxied authorization entry back to the current
        // authorization entry.
        container.useOrigAuthorizationEntry(false);
      }
      // Set the seen flag so proxy processing is not performed for this
      // entry again.
      container.setSeenEntry(true);
    }
    /*
     * First get all allowed candidate ACIs.
     */
    LinkedList<Aci> candidates = aciList.getCandidateAcis(dn);
    /*
     * Create an applicable list of ACIs by target matching each
     * candidate ACI against the container's target match view.
     */
    createApplicableList(candidates, container);
    /*
     * Evaluate the applicable list.
     */
    boolean ret = testApplicableLists(container);
    // Build summary string if doing geteffectiverights eval.
    if (container.isGetEffectiveRightsEval())
    {
      AciEffectiveRights.createSummary(container, ret, "main");
    }
    return ret;
  }
  /*
   * TODO Evaluate performance of this method. TODO Evaluate security
   * concerns of this method. Logic from this method taken almost
   * directly from DS6 implementation. I find the work done in the
   * accessAllowedEntry method, particularly with regard to the entry
   * test evaluation, to be very confusing and potentially pretty
   * inefficient. I'm also concerned that the "return "true" inside the
   * for loop could potentially allow access when it should be denied.
   */
  /**
   * Check if access is allowed on an entry. Access is checked by
   * iterating through each attribute of an entry, starting with the
   * "objectclass" attribute type. If access is allowed on the entry
   * based on one of it's attribute types, then a possible second access
   * check is performed. This second check is only performed if an entry
   * test ACI was found during the earlier successful access check. An
   * entry test ACI has no "targetattrs" keyword, so allowing access
   * based on an attribute type only would be incorrect.
   *
   * @param container
   *          ACI search container containing all of the information
   *          needed to check access.
   * @return True if access is allowed.
   */
  boolean accessAllowedEntry(AciLDAPOperationContainer container)
  {
    boolean ret = false;
    // set flag that specifies this is the first attribute evaluated
    // in the entry
    container.setIsFirstAttribute(true);
    List<AttributeType> typeList =
        getAllAttrs(container.getResourceEntry());
    for (AttributeType attrType : typeList)
    {
      container.setCurrentAttributeType(attrType);
      /*
       * Check if access is allowed. If true, then check to see if an
       * entry test rule was found (no targetattrs) during target match
       * evaluation. If such a rule was found, set the current attribute
       * type to "null" and check access again so that rule is applied.
       */
      if (accessAllowed(container))
      {
        if (container.hasEntryTestRule())
        {
          container.setCurrentAttributeType(null);
          if (!accessAllowed(container))
          {
            /*
             * If we failed because of a deny permission-bind rule, we
             * need to stop and return false.
             */
            if (container.isDenyEval())
            {
              return false;
            }
            /*
             * If we failed because there was no explicit allow rule,
             * then we grant implicit access to the entry.
             */
          }
        }
        return true;
      }
    }
    return ret;
  }
  /**
   * Performs an access check against all of the attributes of an entry.
   * The attributes that fail access are removed from the entry. This
   * method performs the processing needed for the filterEntry method
   * processing.
   *
   * @param container
   *          The search or compare container which has all of the
   *          information needed to filter the attributes for this
   *          entry.
   * @return The entry to send back to the client, minus any attribute
   *         types that failed access check.
   */
  private SearchResultEntry accessAllowedAttrs(
      AciLDAPOperationContainer container)
  {
    Entry e = container.getResourceEntry();
    List<AttributeType> typeList = getAllAttrs(e);
    for (AttributeType attrType : typeList)
    {
      if (container.hasAllUserAttributes() && !attrType.isOperational())
      {
        continue;
      }
      if (container.hasAllOpAttributes() && attrType.isOperational())
      {
        continue;
      }
      container.setCurrentAttributeType(attrType);
      if (!accessAllowed(container))
      {
        e.removeAttribute(attrType);
      }
    }
    return container.getSearchResultEntry();
  }
  /**
   * Checks to see if a LDAP modification is allowed access.
   *
   * @param container
   *          The structure containing the LDAP modifications
   * @param operation
   *          The operation to check modify privileges on. operation to
   *          check and the evaluation context to apply the check
   *          against.
   * @param skipAccessCheck
   *          True if access checking should be skipped.
   * @return True if access is allowed.
   */
  private boolean aciCheckMods(AciLDAPOperationContainer container,
      LocalBackendModifyOperation operation, boolean skipAccessCheck)
  {
    Entry resourceEntry = container.getResourceEntry();
    DN dn = resourceEntry.getDN();
    List<Modification> modifications = container.getModifications();
    for (Modification m : modifications)
    {
      Attribute modAttr = m.getAttribute();
      AttributeType modAttrType = modAttr.getAttributeType();
      if (modAttrType.equals(aciType))
      {
        /*
         * Check that the operation has modify privileges if it contains
         * an "aci" attribute type.
         */
        if (!operation.getClientConnection().hasPrivilege(
            Privilege.MODIFY_ACL, operation))
        {
          Message message =
              INFO_ACI_MODIFY_FAILED_PRIVILEGE.get(String
                  .valueOf(container.getResourceDN()), String
                  .valueOf(container.getClientDN()));
          logError(message);
          return false;
        }
      }
      // This access check handles the case where all attributes of this
      // type are being replaced or deleted. If only a subset is being
      // deleted than this access check is skipped.
      ModificationType modType = m.getModificationType();
      if (((modType == ModificationType.DELETE) && modAttr.isEmpty())
          || ((modType == ModificationType.REPLACE)
              || (modType == ModificationType.INCREMENT)))
      {
        /*
         * Check if we have rights to delete all values of an attribute
         * type in the resource entry.
         */
        if (resourceEntry.hasAttribute(modAttrType))
        {
          container.setCurrentAttributeType(modAttrType);
          List<Attribute> attrList =
              resourceEntry.getAttribute(modAttrType, modAttr
                  .getOptions());
          if (attrList != null)
          {
            for (Attribute a : attrList)
            {
              for (AttributeValue v : a)
              {
                container.setCurrentAttributeValue(v);
                container.setRights(ACI_WRITE_DELETE);
                if (!skipAccessCheck && !accessAllowed(container))
                {
                  return false;
                }
              }
            }
          }
        }
      }
      if (!modAttr.isEmpty())
      {
        for (AttributeValue v : modAttr)
        {
          container.setCurrentAttributeType(modAttrType);
          switch (m.getModificationType())
          {
          case ADD:
          case REPLACE:
            container.setCurrentAttributeValue(v);
            container.setRights(ACI_WRITE_ADD);
            if (!skipAccessCheck && !accessAllowed(container))
            {
              return false;
            }
            break;
          case DELETE:
            container.setCurrentAttributeValue(v);
            container.setRights(ACI_WRITE_DELETE);
            if (!skipAccessCheck && !accessAllowed(container))
            {
              return false;
            }
            break;
          case INCREMENT:
            Entry modifiedEntry = operation.getModifiedEntry();
            List<Attribute> modifiedAttrs =
                modifiedEntry.getAttribute(modAttrType, modAttr
                    .getOptions());
            if (modifiedAttrs != null)
            {
              for (Attribute attr : modifiedAttrs)
              {
                for (AttributeValue val : attr)
                {
                  container.setCurrentAttributeValue(val);
                  container.setRights(ACI_WRITE_ADD);
                  if (!skipAccessCheck && !accessAllowed(container))
                  {
                    return false;
                  }
                }
              }
            }
            break;
          }
          /*
           * Check if the modification type has an "aci" attribute type.
           * If so, check the syntax of that attribute value. Fail the
           * the operation if the syntax check fails.
           */
          if (modAttrType.equals(aciType)
              || modAttrType.equals(globalAciType))
          {
            try
            {
              // A global ACI needs a NULL DN, not the DN of the
              // modification.
              if (modAttrType.equals(globalAciType))
              {
                dn = DN.nullDN();
              }
              Aci.decode(v.getValue(), dn);
            }
            catch (AciException ex)
            {
              Message message =
                  WARN_ACI_MODIFY_FAILED_DECODE.get(String.valueOf(dn),
                      ex.getMessage());
              logError(message);
              return false;
            }
          }
        }
      }
    }
    return true;
  }
  /**
   * Perform all needed RDN checks for the modifyDN operation. The old
   * RDN is not equal to the new RDN. The access checks are: - Verify
   * WRITE access to the original entry. - Verfiy WRITE_ADD access on
   * each RDN component of the new RDN. The WRITE_ADD access is used
   * because this access could be restricted by the targattrfilters
   * keyword. - If the deleteOLDRDN flag is set, verify WRITE_DELETE
   * access on the old RDN. The WRITE_DELETE access is used because this
   * access could be restricted by the targattrfilters keyword.
   *
   * @param operation
   *          The ModifyDN operation class containing information to
   *          check access on.
   * @param oldRDN
   *          The old RDN component.
   * @param newRDN
   *          The new RDN component.
   * @return True if access is allowed.
   */
  private boolean aciCheckRDNs(LocalBackendModifyDNOperation operation,
      RDN oldRDN, RDN newRDN)
  {
    boolean ret;
    AciLDAPOperationContainer operationContainer =
        new AciLDAPOperationContainer(operation, (ACI_WRITE), operation
            .getOriginalEntry());
    ret = accessAllowed(operationContainer);
    if (ret)
    {
      ret = checkRDN(ACI_WRITE_ADD, newRDN, operationContainer);
    }
    if (ret && operation.deleteOldRDN())
    {
      ret = checkRDN(ACI_WRITE_DELETE, oldRDN, operationContainer);
    }
    return ret;
  }
  /**
   * Check access on the new superior entry if it exists. If the entry
   * does not exist or the DN cannot be locked then false is returned.
   *
   * @param superiorDN
   *          The DN of the new superior entry.
   * @param op
   *          The modifyDN operation to check access on.
   * @return True if access is granted to the new superior entry.
   * @throws DirectoryException
   *           If a problem occurs while trying to retrieve the new
   *           superior entry.
   */
  private boolean aciCheckSuperiorEntry(DN superiorDN,
      LocalBackendModifyDNOperation op) throws DirectoryException
  {
    boolean ret = false;
    Lock entryLock = null;
    for (int i = 0; i < 3; i++)
    {
      entryLock = LockManager.lockRead(superiorDN);
      if (entryLock != null)
      {
        break;
      }
    }
    if (entryLock == null)
    {
      Message message =
          WARN_ACI_HANDLER_CANNOT_LOCK_NEW_SUPERIOR_USER.get(String
              .valueOf(superiorDN));
      logError(message);
      return false;
    }
    try
    {
      Entry superiorEntry = DirectoryServer.getEntry(superiorDN);
      if (superiorEntry != null)
      {
        AciLDAPOperationContainer operationContainer =
            new AciLDAPOperationContainer(op, (ACI_IMPORT),
                superiorEntry);
        ret = accessAllowed(operationContainer);
      }
    }
    finally
    {
      LockManager.unlock(superiorDN, entryLock);
    }
    return ret;
  }
  /**
   * Check access on each attribute-value pair component of the
   * specified RDN. There may be more than one attribute-value pair if
   * the RDN is multi-valued.
   *
   * @param right
   *          The access right to check for.
   * @param rdn
   *          The RDN to examine the attribute-value pairs of.
   * @param container
   *          The container containing the information needed to
   *          evaluate the specified RDN.
   * @return True if access is allowed for all attribute-value pairs.
   */
  private boolean checkRDN(int right, RDN rdn, AciContainer container)
  {
    boolean ret = false;
    int numAVAs = rdn.getNumValues();
    container.setRights(right);
    for (int i = 0; i < numAVAs; i++)
    {
      AttributeType type = rdn.getAttributeType(i);
      AttributeValue value = rdn.getAttributeValue(i);
      container.setCurrentAttributeType(type);
      container.setCurrentAttributeValue(value);
      if (!(ret = accessAllowed(container)))
      {
        break;
      }
    }
    return ret;
  }
  /**
   * Creates the allow and deny ACI lists based on the provided target
   * match context. These lists are stored in the evaluation context.
   *
   * @param candidates
   *          List of all possible ACI candidates.
   * @param targetMatchCtx
   *          Target matching context to use for testing each ACI.
   */
  private void createApplicableList(LinkedList<Aci> candidates,
      AciTargetMatchContext targetMatchCtx)
  {
    LinkedList<Aci> denys = new LinkedList<Aci>();
    LinkedList<Aci> allows = new LinkedList<Aci>();
    for (Aci aci : candidates)
    {
      if (Aci.isApplicable(aci, targetMatchCtx))
      {
        if (aci.hasAccessType(EnumAccessType.DENY))
        {
          denys.add(aci);
        }
        if (aci.hasAccessType(EnumAccessType.ALLOW))
        {
          allows.add(aci);
        }
      }
      if (targetMatchCtx.getTargAttrFiltersMatch())
      {
        targetMatchCtx.setTargAttrFiltersMatch(false);
      }
    }
    targetMatchCtx.setAllowList(allows);
    targetMatchCtx.setDenyList(denys);
  }
  /**
   * Gathers all of the attribute types in an entry along with the
   * "objectclass" attribute type in a List. The "objectclass" attribute
   * is added to the list first so it is evaluated first.
   *
   * @param e
   *          Entry to gather the attributes for.
   * @return List containing the attribute types.
   */
  private List<AttributeType> getAllAttrs(Entry e)
  {
    Map<AttributeType, List<Attribute>> attrMap = e.getUserAttributes();
    Map<AttributeType, List<Attribute>> opAttrMap =
        e.getOperationalAttributes();
    List<AttributeType> typeList = new LinkedList<AttributeType>();
    Attribute attr = e.getObjectClassAttribute();
    /*
     * When a search is not all attributes returned, the "objectclass"
     * attribute type is missing from the entry.
     */
    if (attr != null)
    {
      AttributeType ocType = attr.getAttributeType();
      typeList.add(ocType);
    }
    typeList.addAll(attrMap.keySet());
    typeList.addAll(opAttrMap.keySet());
    return typeList;
  }
  /**
   * Check access using the accessAllowed method. The LDAP add, compare,
   * modify and delete operations use this function. The other supported
   * LDAP operations have more specialized checks.
   *
   * @param operationContainer
   *          The container containing the information needed to
   *          evaluate this operation.
   * @param operation
   *          The operation being evaluated.
   * @return True if this operation is allowed access.
   */
  private boolean isAllowed(
      AciLDAPOperationContainer operationContainer, Operation operation)
  {
    return skipAccessCheck(operation)
        || accessAllowed(operationContainer);
  }
  /**
   * Check if the specified attribute type is a DN by checking if its
   * syntax OID is equal to the DN syntax OID.
   *
   * @param attribute
   *          The attribute type to check.
   * @return True if the attribute type syntax OID is equal to a DN
   *         syntax OID.
   */
  private boolean isAttributeDN(AttributeType attribute)
  {
    return (attribute.getSyntaxOID().equals(SYNTAX_DN_OID));
  }
  /**
   * Process all ACIs under the "cn=config" naming context and adds them
   * to the ACI list cache. It also logs messages about the number of
   * ACIs added to the cache. This method is called once at startup. It
   * will put the server in lockdown mode if needed.
   *
   * @throws InitializationException
   *           If there is an error searching for the ACIs in the naming
   *           context.
   */
  private void processConfigAcis() throws InitializationException
  {
    try
    {
      DN configDN = DN.decode("cn=config");
      LinkedHashSet<String> attrs = new LinkedHashSet<String>(1);
      attrs.add("aci");
      LinkedList<Message> failedACIMsgs = new LinkedList<Message>();
      InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
      InternalSearchOperation op =
          conn.processSearch(configDN, SearchScope.WHOLE_SUBTREE,
              DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
              SearchFilter.createFilterFromString("aci=*"), attrs);
      if (!op.getSearchEntries().isEmpty())
      {
        int validAcis =
            aciList.addAci(op.getSearchEntries(), failedACIMsgs);
        if (!failedACIMsgs.isEmpty())
        {
          aciListenerMgr.logMsgsSetLockDownMode(failedACIMsgs);
        }
        Message message =
            INFO_ACI_ADD_LIST_ACIS.get(Integer.toString(validAcis),
                String.valueOf(configDN));
        logError(message);
      }
    }
    catch (DirectoryException e)
    {
      Message message = INFO_ACI_HANDLER_FAIL_PROCESS_ACI.get();
      throw new InitializationException(message, e);
    }
  }
  /**
   * Process all global ACI attribute types found in the configuration
   * entry and adds them to that ACI list cache. It also logs messages
   * about the number of ACI attribute types added to the cache. This
   * method is called once at startup. It also will put the server into
   * lockdown mode if needed.
   *
   * @param configuration
   *          The config handler containing the ACI configuration
   *          information.
   * @throws InitializationException
   *           If there is an error reading the global ACIs from the
   *           configuration entry.
   */
  private void processGlobalAcis(
      DseeCompatAccessControlHandlerCfg configuration)
      throws InitializationException
  {
    SortedSet<Aci> globalAcis = configuration.getGlobalACI();
    try
    {
      if (globalAcis != null)
      {
        aciList.addAci(DN.nullDN(), globalAcis);
        Message message =
            INFO_ACI_ADD_LIST_GLOBAL_ACIS.get(Integer
                .toString(globalAcis.size()));
        logError(message);
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      Message message =
          INFO_ACI_HANDLER_FAIL_PROCESS_GLOBAL_ACI.get(String
              .valueOf(configuration.dn()));
      throw new InitializationException(message, e);
    }
  }
  /**
   * Check to see if the specified entry has the specified privilege.
   *
   * @param e
   *          The entry to check privileges on.
   * @return {@code true} if the entry has the specified privilege, or
   *         {@code false} if not.
   */
  private boolean skipAccessCheck(Entry e)
  {
    return ClientConnection.hasPrivilege(e, Privilege.BYPASS_ACL);
  }
  /**
   * Check to see if the client entry has BYPASS_ACL privileges for this
   * operation.
   *
   * @param operation
   *          The operation to check privileges on.
   * @return True if access checking can be skipped because the
   *         operation client connection has BYPASS_ACL privileges.
   */
  private boolean skipAccessCheck(Operation operation)
  {
    return operation.getClientConnection().hasPrivilege(
        Privilege.BYPASS_ACL, operation);
  }
  /**
   * Performs the test of the deny and allow access lists using the
   * provided evaluation context. The deny list is checked first.
   *
   * @param evalCtx
   *          The evaluation context to use.
   * @return True if access is allowed.
   */
  private boolean testApplicableLists(AciEvalContext evalCtx)
  {
    EnumEvalResult res;
    evalCtx.setEvalReason(EnumEvalReason.NO_REASON);
    LinkedList<Aci> denys = evalCtx.getDenyList();
    LinkedList<Aci> allows = evalCtx.getAllowList();
    // If allows list is empty and not doing geteffectiverights return
    // false.
    if (allows.isEmpty()
        && !(evalCtx.isGetEffectiveRightsEval()
            && !evalCtx.hasRights(ACI_SELF) && evalCtx
            .isTargAttrFilterMatchAciEmpty()))
    {
      evalCtx.setEvalReason(EnumEvalReason.NO_ALLOW_ACIS);
      evalCtx.setDecidingAci(null);
      return false;
    }
    evalCtx.setDenyEval(true);
    for (Aci denyAci : denys)
    {
      res = Aci.evaluate(evalCtx, denyAci);
      // Failure could be returned if a system limit is hit or
      // search fails
      if (res.equals(EnumEvalResult.FAIL))
      {
        evalCtx.setEvalReason(EnumEvalReason.EVALUATED_DENY_ACI);
        evalCtx.setDecidingAci(denyAci);
        return false;
      }
      else if (res.equals(EnumEvalResult.TRUE))
      {
        if (evalCtx.isGetEffectiveRightsEval()
            && !evalCtx.hasRights(ACI_SELF)
            && !evalCtx.isTargAttrFilterMatchAciEmpty())
        {
          // Iterate to next only if deny ACI contains a targattrfilters
          // keyword.
          if (AciEffectiveRights.setTargAttrAci(evalCtx, denyAci, true))
          {
            continue;
          }
          evalCtx.setEvalReason(EnumEvalReason.EVALUATED_DENY_ACI);
          evalCtx.setDecidingAci(denyAci);
          return false;
        }
        else
        {
          evalCtx.setEvalReason(EnumEvalReason.EVALUATED_DENY_ACI);
          evalCtx.setDecidingAci(denyAci);
          return false;
        }
      }
    }
    // Now check the allows -- flip the deny flag to false first.
    evalCtx.setDenyEval(false);
    for (Aci allowAci : allows)
    {
      res = Aci.evaluate(evalCtx, allowAci);
      if (res.equals(EnumEvalResult.TRUE))
      {
        if (evalCtx.isGetEffectiveRightsEval()
            && !evalCtx.hasRights(ACI_SELF)
            && !evalCtx.isTargAttrFilterMatchAciEmpty())
        {
          // Iterate to next only if deny ACI contains a targattrfilters
          // keyword.
          if (AciEffectiveRights.setTargAttrAci(evalCtx, allowAci,
              false))
          {
            continue;
          }
          evalCtx.setEvalReason(EnumEvalReason.EVALUATED_ALLOW_ACI);
          evalCtx.setDecidingAci(allowAci);
          return true;
        }
        else
        {
          evalCtx.setEvalReason(EnumEvalReason.EVALUATED_ALLOW_ACI);
          evalCtx.setDecidingAci(allowAci);
          return true;
        }
      }
    }
    // Nothing matched fall through.
    evalCtx.setEvalReason(EnumEvalReason.NO_MATCHED_ALLOWS_ACIS);
    evalCtx.setDecidingAci(null);
    return false;
  }
  /**
   * Test the attribute types of the search filter for access. This
   * method supports the search right.
   *
   * @param container
   *          The container used in the access evaluation.
   * @param filter
   *          The filter to check access on.
   * @return True if all attribute types in the filter have access.
   * @throws DirectoryException
   *           If there is a problem matching the entry using the
   *           provided filter.
   */
  private boolean testFilter(AciLDAPOperationContainer container,
      SearchFilter filter) throws DirectoryException
  {
    boolean ret = true;
    // If the resource entry has a dn equal to "cn=debugsearch" and it
    // contains the special attribute type "debugsearchindex", then the
    // resource entry is a pseudo entry created for debug purposes.
    // Return true if that is the case.
    if (debugSearchIndexDN.equals(container.getResourceDN())
        && container.getResourceEntry().hasAttribute(debugSearchIndex))
    {
      return true;
    }
    switch (filter.getFilterType())
    {
    case AND:
    case OR:
    {
      for (SearchFilter f : filter.getFilterComponents())
      {
        if (!testFilter(container, f))
        {
          return false;
        }
      }
      break;
    }
    case NOT:
    {
      ret = false;
      SearchFilter f = filter.getNotComponent();
      if (f.matchesEntry(container.getResourceEntry()))
      {
        ret = true;
      }
      if (ret)
      {
        ret = testFilter(container, f);
      }
      ret = !ret;
      break;
    }
    default:
    {
      AttributeType attrType = filter.getAttributeType();
      container.setCurrentAttributeType(attrType);
      ret = accessAllowed(container);
    }
    }
    return ret;
  }
  /**
   * Evaluate an entry to be added to see if it has any "aci" attribute
   * type. If it does, examines each "aci" attribute type value for
   * syntax errors. All of the "aci" attribute type values must pass
   * syntax check for the add operation to proceed. Any entry with an
   * "aci" attribute type must have "modify-acl" privileges.
   *
   * @param entry
   *          The entry to be examined.
   * @param operation
   *          The operation to to check privileges on.
   * @param clientDN
   *          The authorization DN.
   * @return True if the entry has no ACI attributes or if all of the
   *         "aci" attributes values pass ACI syntax checking.
   */
  private boolean verifySyntax(Entry entry, Operation operation,
      DN clientDN)
  {
    if (entry.hasOperationalAttribute(aciType))
    {
      /*
       * Check that the operation has "modify-acl" privileges since the
       * entry to be added has an "aci" attribute type.
       */
      if (!operation.getClientConnection().hasPrivilege(
          Privilege.MODIFY_ACL, operation))
      {
        Message message =
            INFO_ACI_ADD_FAILED_PRIVILEGE.get(String.valueOf(entry
                .getDN()), String.valueOf(clientDN));
        logError(message);
        return false;
      }
      List<Attribute> attributeList =
          entry.getOperationalAttribute(aciType, null);
      for (Attribute attribute : attributeList)
      {
        for (AttributeValue value : attribute)
        {
          try
          {
            DN dn = entry.getDN();
            Aci.decode(value.getValue(), dn);
          }
          catch (AciException ex)
          {
            Message message =
                WARN_ACI_ADD_FAILED_DECODE.get(String.valueOf(entry
                    .getDN()), ex.getMessage());
            logError(message);
            return false;
          }
        }
      }
    }
    return true;
  }
}