/** {@inheritDoc} */
  @Override
  public void filterEntry(
      Operation operation, SearchResultEntry unfilteredEntry, SearchResultEntry filteredEntry) {
    AciLDAPOperationContainer operationContainer =
        new AciLDAPOperationContainer(operation, (ACI_READ), unfilteredEntry);

    // 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);

    boolean skipCheck = skipAccessCheck(operation);
    if (!skipCheck) {
      filterEntry(operationContainer, filteredEntry);
    }

    if (operationContainer.hasGetEffectiveRightsControl()) {
      AciEffectiveRights.addRightsToEntry(
          this,
          ((SearchOperation) operation).getAttributes(),
          operationContainer,
          filteredEntry,
          skipCheck);
    }
  }
  /** {@inheritDoc} */
  @Override
  public boolean isAllowed(LocalBackendAddOperation operation) throws DirectoryException {
    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;
  }
 /**
  * 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.
  * @param filteredEntry The partially filtered search result entry being returned to the client.
  */
 private void filterEntry(AciLDAPOperationContainer container, Entry filteredEntry) {
   List<AttributeType> typeList = getAllAttrs(filteredEntry);
   for (AttributeType attrType : typeList) {
     if (container.hasAllUserAttributes() && !attrType.isOperational()) {
       continue;
     }
     if (container.hasAllOpAttributes() && attrType.isOperational()) {
       continue;
     }
     container.setCurrentAttributeType(attrType);
     if (!accessAllowed(container)) {
       filteredEntry.removeAttribute(attrType);
     }
   }
 }
  /** {@inheritDoc} */
  @Override
  public boolean maySend(DN dn, Operation operation, SearchResultReference reference) {
    boolean ret;
    if (!(ret = skipAccessCheck(operation))) {
      Entry e = new Entry(dn, null, null, null);
      AttributeBuilder builder = 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) {
        builder.add(AttributeValues.create(refAttrType, URLString));
      }

      e.addAttribute(builder.toAttribute(), null);
      SearchResultEntry se = new SearchResultEntry(e);
      AciLDAPOperationContainer operationContainer =
          new AciLDAPOperationContainer(operation, (ACI_READ), se);
      operationContainer.setCurrentAttributeType(refAttrType);
      ret = accessAllowed(operationContainer);
    }
    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.
  */
 @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);
 }
  /** {@inheritDoc} */
  @Override
  public boolean maySend(Operation operation, SearchResultEntry entry) {
    if (skipAccessCheck(operation)) {
      return true;
    }

    AciLDAPOperationContainer operationContainer =
        new AciLDAPOperationContainer(operation, (ACI_SEARCH), entry);

    // Pre/post read controls are associated with other types of operation.
    if (operation instanceof SearchOperation) {
      try {
        if (!testFilter(operationContainer, ((SearchOperation) operation).getFilter())) {
          return false;
        }
      } catch (DirectoryException ex) {
        return false;
      }
    }

    operationContainer.clearEvalAttributes(ACI_NULL);
    operationContainer.setRights(ACI_READ);

    if (!accessAllowedEntry(operationContainer)) {
      return false;
    }

    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);
    }

    return true;
  }
  /**
   * Checks access on a modifyDN operation.
   *
   * @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;
        }
      }
      // Perform the RDN access checks.
      if (ret) {
        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.
        boolean rdnEquals = oldRDN.equals(newRDN);
        if (!rdnEquals) {
          operationContainer.setSeenEntry(true);
        }
        ret = accessAllowed(operationContainer);
      }
    }
    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:
       {
         SearchFilter f = filter.getNotComponent();
         ret = testFilter(container, f);
         break;
       }
     default:
       {
         AttributeType attrType = filter.getAttributeType();
         container.setCurrentAttributeType(attrType);
         ret = accessAllowed(container);
       }
   }
   return ret;
 }
 /**
  * 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;
 }
  /**
   * 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.
   * @throws DirectoryException If a modified ACI could not be decoded.
   */
  private boolean aciCheckMods(
      AciLDAPOperationContainer container,
      LocalBackendModifyOperation operation,
      boolean skipAccessCheck)
      throws DirectoryException {
    Entry resourceEntry = container.getResourceEntry();
    DN dn = resourceEntry.getDN();
    List<Modification> modifications = operation.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());
              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
            }
          }
        }
      }
    }
    return true;
  }