/**
  * 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.
  * @throws DirectoryException If a modified ACI could not be decoded.
  */
 private boolean verifySyntax(Entry entry, Operation operation, DN clientDN)
     throws DirectoryException {
   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());
           throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
         }
       }
     }
   }
   return true;
 }
 /**
  * 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);
 }
  /**
   * 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;
  }
 /**
  * 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.
   evalCtx.setDenyEval(true);
   if (allows.isEmpty()
       && !(evalCtx.isGetEffectiveRightsEval()
           && !evalCtx.hasRights(ACI_SELF)
           && evalCtx.isTargAttrFilterMatchAciEmpty())) {
     evalCtx.setEvalReason(EnumEvalReason.NO_ALLOW_ACIS);
     evalCtx.setDecidingAci(null);
     return false;
   }
   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;
 }