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

dugan
02.02.2007 e7701dfe8076e2fe9aaf97611e298afa26009cbc
Add ACI support for Get Effective Rights control. Issue #87.
4 files added
21 files modified
3023 ■■■■■ changed files
opendj-sdk/opends/resource/schema/00-core.ldif 10 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/api/AccessControlHandler.java 25 ●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/Aci.java 32 ●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciBody.java 10 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java 538 ●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciEffectiveRights.java 735 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciEvalContext.java 158 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java 125 ●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciTargetMatchContext.java 22 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciTargets.java 5 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/EnumEvalReason.java 74 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/TargAttrFilters.java 10 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/TargetAttr.java 92 ●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/controls/GetEffectiveRights.java 245 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/core/DefaultAccessControlProvider.java 10 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/core/SearchOperation.java 44 ●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/messages/AciMessages.java 41 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java 14 ●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/messages/ProtocolMessages.java 44 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/messages/ToolMessages.java 38 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/tools/LDAPSearch.java 50 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/tools/LDAPToolUtils.java 5 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/tools/ToolConstants.java 23 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java 6 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/GetEffectiveRightsTestCase.java 667 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/resource/schema/00-core.ldif
@@ -370,6 +370,16 @@
  DESC 'Sun-defined access control information attribute type'
  SYNTAX 1.3.6.1.4.1.26027.1.3.4 USAGE directoryOperation
  X-ORIGIN 'Sun Java System Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.42.2.27.9.1.39 NAME 'aclRights'
  DESC 'Sun-defined access control effective rights attribute type'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation
  X-ORIGIN 'Sun Java System Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.42.2.27.9.1.40 NAME 'aclRightsInfo'
  DESC 'Sun-defined access control effective rights information attribute type'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation
  X-ORIGIN 'Sun Java System Directory Server' )
attributeTypes: ( 2.16.840.1.113730.3.1.542 NAME 'nsUniqueId'
  DESC 'Sun-defined unique identifier' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
  SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation
opendj-sdk/opends/src/server/org/opends/server/api/AccessControlHandler.java
@@ -29,11 +29,7 @@
import org.opends.server.core.*;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
import org.opends.server.types.Entry;
import org.opends.server.types.Operation;
import org.opends.server.types.*;
/**
@@ -251,5 +247,24 @@
   */
  public abstract boolean isProxiedAuthAllowed(Operation operation,
                                        Entry newAuthorizationEntry);
  /**
   * Indicates whether a geteffectiverights control is allowed
   * based on the current operation and the control contents.
   *
   * @param operation
   *        The operation with which the geteffectiverights
   *        control is associated. This is always a
   *       SearchOperation.
   * @param control
   *        The control class containing the decoded
   *        geteffectiverights control contents.
   * @return  <CODE>true</CODE> if the operation should be allowed
   *          by the access control configuration, or
   *          <CODE>false</CODE> if not.
   */
  public abstract
  boolean isGetEffectiveRightsAllowed(Operation operation,
                                      Control control);
}
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/Aci.java
@@ -151,13 +151,13 @@
    /**
     * ACI_ADD is used to set the container rights for a LDAP add operation.
     */
    public static final int ACI_ADD = 0x0001;
    public static final int ACI_ADD = 0x0020;
    /**
     * ACI_DELETE is used to set the container rights for a LDAP
     * delete operation.
     */
    public static final int ACI_DELETE = 0x0002;
    public static final int ACI_DELETE = 0x0010;
    /**
     * ACI_READ is used to set the container rights for a LDAP
@@ -175,12 +175,12 @@
     * ACI_COMPARE is used to set the container rights for a LDAP
     * compare operation.
     */
    public static final int ACI_COMPARE = 0x0010;
    public static final int ACI_COMPARE = 0x0001;
    /**
     * ACI_SEARCH is used to set the container rights a LDAP search operation.
     */
    public static final int ACI_SEARCH = 0x0020;
    public static final int ACI_SEARCH = 0x0002;
    /**
     * ACI_SELF is used for the SELFWRITE right.
@@ -221,6 +221,11 @@
    public static final int ACI_WRITE_DELETE = 0x400;
    /**
     * ACI_SKIP_PROXY_CHECK is used to bypass the proxy access check.
     */
    public static final int ACI_SKIP_PROXY_CHECK = 0x4000;
    /**
     * TARGATTRFILTER_ADD is used to specify that a
     * targattrfilters ADD operation was seen in the ACI. For example,
     * given an ACI with:
@@ -271,8 +276,6 @@
     * @return  Returns a decoded ACI representing the string argument.
     * @throws AciException If the parsing of the ACI string fails.
     */
    //MPD remove ConfigException after fixing David's problem
    public static Aci decode (ByteString byteString, DN dn)
    throws AciException {
        String input=byteString.stringValue();
@@ -338,6 +341,15 @@
     */
    public static boolean
    isApplicable(Aci aci, AciTargetMatchContext matchCtx) {
        int ctxRights=matchCtx.getRights();
       //First check if the ACI and context have similar rights.
        if(!aci.hasRights(ctxRights)) {
           //TODO This check might be able to be removed further testing
           //     is needed.
           if(!(aci.hasRights(ACI_SEARCH| ACI_READ) &&
                 matchCtx.hasRights(ACI_SEARCH | ACI_READ)))
              return false;
        }
        return AciTargets.isTargetApplicable(aci, matchCtx) &&
                AciTargets.isTargetFilterApplicable(aci, matchCtx) &&
                AciTargets.isTargAttrFiltersApplicable(aci, matchCtx) &&
@@ -384,4 +396,12 @@
    public static EnumEvalResult evaluate(AciEvalContext evalCtx, Aci aci) {
        return aci.evaluate(evalCtx);
    }
  /**
   * Returns the name string of this ACI.
   * @return The name string.
   */
    public String getName() {
      return this.body.getName();
    }
}
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciBody.java
@@ -47,7 +47,7 @@
    private static final int VERSION = 1;
    /*
     * Regular expression group position for the namr string.
     * Regular expression group position for the name string.
     */
    private static final int NAME = 2;
@@ -310,4 +310,12 @@
        }
        return res;
    }
  /**
   * Returns the name string.
   * @return The name string.
   */
  public String getName() {
      return this.name;
    }
}
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java
@@ -32,11 +32,18 @@
import org.opends.server.api.Group;
import org.opends.server.api.ConnectionSecurityProvider;
import org.opends.server.core.AddOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.extensions.TLSConnectionSecurityProvider;
import org.opends.server.types.Operation;
import java.net.InetAddress;
import java.util.LinkedList;
import java.util.List;
import java.util.HashMap;
import static org.opends.server.authorization.dseecompat.Aci.*;
import static org.opends.server.authorization.dseecompat.AciHandler.*;
import org.opends.server.controls.GetEffectiveRights;
import static org.opends.server.util.ServerConstants.OID_GET_EFFECTIVE_RIGHTS;
/**
 *  The AciContainer class contains all of the needed information to perform
@@ -94,6 +101,7 @@
     * The entry being evaluated (resource entry).
     */
    private Entry resourceEntry;
    private Entry saveResourceEntry;
    /*
     * The client connection information.
@@ -145,12 +153,86 @@
     */
    private boolean seenEntry=false;
    /**
    /*
     *  True if geteffectiverights evaluation is in progress.
     */
    private boolean isGetEffectiveRightsEval=false;
     /*
     *  True if the operation has a geteffectiverights control.
     */
    private boolean hasGetEffectiveRightsControl=false;
    /*
     * The geteffectiverights authzID in DN format.
     */
    private DN authzid=null;
    /*
     * True if the authZid should be used as the client DN, only used in
     * geteffectiverights evaluation.
     */
    private boolean useAuthzid=false;
    /*
     * The list of specific attributes to get rights for, in addition to
     * any attributes requested in the search.
     */
    private List<AttributeType> specificAttrs=null;
    /*
     * The entry with all of its attributes available. Used in
     * geteffectiverights read entry level evaluation.
     */
    private Entry fullEntry=null;
    /*
     * Table of ACIs that have targattrfilter keywords that matched. Used
     * in geteffectiverights attributeLevel write evaluation.
     */
    private HashMap<Aci,Aci> targAttrFilterAcis=new HashMap<Aci, Aci>();
    /*
     * The name of a ACI that decided an evaluation and contained a
     * targattrfilter keyword. Used in geteffectiverights attributeLevel
     * write evaluation.
     */
    private String targAttrFiltersAciName=null;
    /*
     * Value that is used to store the allow/deny result of a deciding ACI
     * containing a targattrfilter keyword.  Used in geteffectiverights
     * attributeLevel write evaluation.
     */
    private int targAttrMatch=0;
    /*
     * The ACI that decided the last evaluation. Used in geteffectiverights
     * loginfo processing.
     */
    private Aci decidingAci=null;
    /*
     * The reason the last evaluation decision was made. Used both
     * in geteffectiverights loginfo processing and attributeLevel write
     * evaluation.
     */
    private EnumEvalReason evalReason=null;
    /*
     * A summary string holding the last evaluation information in textual
     * format. Used in geteffectiverights loginfo processing.
     */
    private String summaryString=null;
  /**
     * This constructor is used by all currently supported LDAP operations.
     *
     * @param operation The Operation object being evaluated and target
     * matching.
     *
     * @param rights The rights array to use in evaluation and target matching.
     *
     * @param entry The current entry being evaluated and target matched.
     */
    protected AciContainer(Operation operation, int rights, Entry entry) {
@@ -167,16 +249,34 @@
      if(origAuthorizationEntry != null)
         this.proxiedAuthorization=true;
      this.authorizationEntry=operation.getAuthorizationEntry();
      //Only need to process the geteffectiverights control once, -- for a
      //SearchOperation with read right. It is saved in the operation
      //attachment after that.
      if(operation instanceof SearchOperation && (rights == ACI_READ)) {
        GetEffectiveRights getEffectiveRightsControl =
              (GetEffectiveRights)
                      operation.getAttachment(OID_GET_EFFECTIVE_RIGHTS);
        if(getEffectiveRightsControl != null) {
          hasGetEffectiveRightsControl=true;
          if(getEffectiveRightsControl.getAuthzDN() == null)
            this.authzid=getClientDN();
          else
            this.authzid=getEffectiveRightsControl.getAuthzDN();
          this.specificAttrs=getEffectiveRightsControl.getAttributes();
          fullEntry=(Entry)operation.getAttachment(ALL_ATTRS_RESOURCE_ENTRY);
        }
      }
      //Reference the current authorization entry, so it can be put back
      //if an access proxy check was performed.
      this.saveAuthorizationEntry=this.authorizationEntry;
      this.saveResourceEntry=this.resourceEntry;
      this.rights = rights;
    }
  /**
   * Returns true if an entry has already been processed by an access proxy
   * check.
   *
   * @return True if an entry has already been processed by an access proxy
   * check.
   */
@@ -187,6 +287,7 @@
  /**
   * Set to true if an entry has already been processsed by an access proxy
   * check.
   *
   * @param val The value to set the seenEntry boolean to.
   */
    public void setSeenEntry(boolean val) {
@@ -194,14 +295,200 @@
    }
  /**
   * Returns true if proxied authorization is being used.
   * @return  True if proxied authorization is being used.
   * {@inheritDoc}
   */
    public boolean isProxiedAuthorization() {
         return this.proxiedAuthorization;
    }
  /**
   * {@inheritDoc}
   */
    public boolean isGetEffectiveRightsEval() {
        return this.isGetEffectiveRightsEval;
    }
  /**
   * The container is going to be used in a geteffectiverights evaluation, set
   * the flag isGetEffectiveRightsEval to true.
   */
  public void setGetEffectiveRightsEval() {
       this.isGetEffectiveRightsEval=true;
    }
  /**
   * Return true if the container is being used in a geteffectiverights
   * evaluation.
   *
   * @return True if the container is being used in a geteffectiverights
   * evaluation.
   */
    public boolean hasGetEffectiveRightsControl() {
      return this.hasGetEffectiveRightsControl;
    }
  /**
   * Use the DN from the geteffectiverights control's authzId as the
   * client DN, rather than the authorization entry's DN.
   *
   * @param v The valued to set the useAuthzid to.
   */
    public void useAuthzid(boolean v) {
       this.useAuthzid=v;
    }
  /**
   * Return the list of additional attributes specified in the
   * geteffectiveritghts control.
   *
   * @return The list of attributes to return rights information about in the
   * entry.
   */
    public List<AttributeType> getSpecificAttributes() {
       return this.specificAttrs;
    }
  /**
   * During the geteffectiverights entrylevel read evaluation, an entry with all
   * of the attributes used in the AciHandler's maysend method evaluation is
   * needed to perform the evaluation over again. This entry was saved
   * in the operation's attachment mechanism when the container was created
   * during the SearchOperation read evaluation.
   *
   * This method is used to replace the current resource entry with that saved
   * entry to perform the entrylevel read evaluation described above and to
   * switch back to the current resource entry when needed.
   *
   * @param val Specifies if the saved entry should be used or not. True if it
   * should be used, false if the original resource entry should be used.
   *
   */
    public void useFullResourceEntry(boolean val) {
      if(val)
        resourceEntry=fullEntry;
      else
        resourceEntry=saveResourceEntry;
    }
   /**
    * {@inheritDoc}
    */
    public void addTargAttrFiltersMatchAci(Aci aci) {
      this.targAttrFilterAcis.put(aci, aci);
    }
   /**
    * {@inheritDoc}
    */
    public boolean hasTargAttrFiltersMatchAci(Aci aci) {
      return this.targAttrFilterAcis.containsKey(aci);
    }
   /**
    * {@inheritDoc}
    */
    public boolean isTargAttrFilterMatchAciEmpty() {
       return this.targAttrFilterAcis.isEmpty();
    }
  /**
   * Reset the values used by the geteffectiverights evaluation to
   * original values. The geteffectiverights evaluation uses the same container
   * repeatedly for different rights evaluations (read, write, proxy,...) and
   * this method resets variables that are specific to a single evaluation.
   */
    public void resetEffectiveRightsParams() {
      this.targAttrFilterAcis.clear();
      this.decidingAci=null;
      this.evalReason=null;
      this.targAttrFiltersMatch=false;
      this.summaryString=null;
      this.targAttrMatch=0;
    }
   /**
    * {@inheritDoc}
    */
    public void setTargAttrFiltersAciName(String name) {
      this.targAttrFiltersAciName=name;
    }
   /**
    * {@inheritDoc}
    */
    public String getTargAttrFiltersAciName() {
      return this.targAttrFiltersAciName;
    }
   /**
    * {@inheritDoc}
    */
    public void setTargAttrFiltersMatchOp(int flag) {
      this.targAttrMatch |= flag;
    }
   /**
    * {@inheritDoc}
    */
    public boolean hasTargAttrFiltersMatchOp(int flag) {
       return (this.targAttrMatch & flag) != 0;
    }
   /**
    * {@inheritDoc}
    */
    public void setDecidingAci(Aci aci) {
      this.decidingAci=aci;
    }
   /**
    * {@inheritDoc}
    */
    public String getDecidingAciName() {
      if(this.decidingAci != null)
         return this.decidingAci.getName();
      else return null;
    }
   /**
    * {@inheritDoc}
    */
    public void setEvalReason(EnumEvalReason reason) {
      this.evalReason=reason;
    }
   /**
    * {@inheritDoc}
    */
    public EnumEvalReason getEvalReason() {
      return this.evalReason;
    }
   /**
    * {@inheritDoc}
    */
    public void setEvalSummary(String summary) {
      this.summaryString=summary;
    }
   /**
    * {@inheritDoc}
    */
     public String getEvalSummary() {
      return this.summaryString;
    }
  /**
   * Returns true if the geteffectiverights control's authZid DN is equal to the
   * authoritzation entry's DN.
   *
   * @return True if the authZid is equal to the authorization entry's DN.
   */
    public boolean isAuthzidAuthorizationDN() {
     return this.authzid.equals(this.authorizationEntry.getDN());
    }
  /**
   * If the specified value is true, then the original authorization entry,
   * which is the  entry before the switch performed by the proxied
   * authorization control processing should be set to the current
@@ -216,228 +503,194 @@
        authorizationEntry=saveAuthorizationEntry;
    }
    /**
     * The list of deny ACIs. These are all of the applicable
     * ACIs that have a deny permission. Note that an ACI can
     * be on both allow and deny list if it has multiple
     * permission-bind rule pairs.
     *
     * @param denys The list of deny ACIs.
     */
   /**
    * {@inheritDoc}
    */
    public void setDenyList(LinkedList<Aci> denys) {
        denyList=denys;
    }
    /**
     * The list of allow ACIs. These are all of the applicable
     * ACIs that have an allow permission.
     *
     * @param allows  The list of allow ACIs.
     */
   /**
    * {@inheritDoc}
    */
    public void setAllowList(LinkedList<Aci> allows) {
        allowList=allows;
    }
    /**
     * Return the current attribute type being evaluated.
     * @return  Attribute type being evaluated.
     */
   /**
    * {@inheritDoc}
    */
    public AttributeType getCurrentAttributeType() {
        return attributeType;
    }
    /**
     * Return the current attribute type value being evaluated.
     * @return Attribute type value being evaluated.
     */
   /**
    * {@inheritDoc}
    */
    public AttributeValue getCurrentAttributeValue() {
        return attributeValue;
    }
    /**
     * Set the attribute type to be evaluated.
     * @param type The attribute type to evaluate.
     */
   /**
    * {@inheritDoc}
    */
    public void setCurrentAttributeType(AttributeType type) {
        attributeType=type;
    }
    /**
     * Set the attribute type value to be evaluated.
     * @param value The attribute type value to evaluate.
     */
   /**
    * {@inheritDoc}
    */
    public void setCurrentAttributeValue(AttributeValue value) {
        attributeValue=value;
    }
    /**
     * Check is this the first attribute being evaluated in an entry.
     * @return  True if it is the first attribute.
     */
   /**
    * {@inheritDoc}
    */
    public boolean isFirstAttribute() {
        return isFirst;
    }
    /**
     * Set if this is the first attribute in the entry.
     * @param val True if this is the first attribute being evaluated in the
     * entry.
     */
   /**
    * {@inheritDoc}
    */
    public void setIsFirstAttribute(boolean val) {
        isFirst=val;
    }
    /**
     * Check if an entry test rule was seen during target evaluation.
     * @return True if an entry test rule was seen.
     */
   /**
    * {@inheritDoc}
    */
    public boolean hasEntryTestRule() {
        return isEntryTestRule;
    }
    /**
     * Used to set if an entry test rule was seen during target evaluation.
     * @param val Set to true if an entry test rule was seen.
     */
    public void setEntryTestRule(boolean val) {
   /**
    * {@inheritDoc}
    */
   public void setEntryTestRule(boolean val) {
        isEntryTestRule=val;
    }
    /**
     * Get the entry being evaluated (known as the resource entry).
     * @return  The entry being evaluated.
     */
   /**
    * {@inheritDoc}
    */
    public Entry getResourceEntry() {
        return resourceEntry;
    }
    /**
     * Get the entry that corresponds to the client DN.
     * @return The client entry.
     */
   /**
    * {@inheritDoc}
    */
    public Entry getClientEntry() {
      return this.authorizationEntry;
    }
    /**
     * Get the deny list of ACIs.
     * @return The deny ACI list.
     */
   /**
    * {@inheritDoc}
    */
    public LinkedList<Aci> getDenyList() {
        return denyList;
     }
    /**
     * Get the allow list of ACIs.
     * @return The allow ACI list.
     */
   /**
    * {@inheritDoc}
    */
    public LinkedList<Aci> getAllowList() {
       return allowList;
    }
    /**
     * Check is this is a deny ACI evaluation.
     * @return  True if the evaluation is using an ACI from
     * deny list.
     */
   /**
    * {@inheritDoc}
    */
    public boolean isDenyEval() {
        return isDenyEval;
    }
    /**
     * Check is this operation bound anonymously.
     * @return  True if the authentication is anonymous.
     */
   /**
    * {@inheritDoc}
    */
    public boolean isAnonymousUser() {
        return !clientConnection.getAuthenticationInfo().isAuthenticated();
    }
    /**
     * Set the deny evaluation flag.
     * @param val True if this evaluation is a deny ACI.
     */
   /**
    * {@inheritDoc}
    */
    public void setDenyEval(boolean val) {
        isDenyEval = val;
    }
    /**
     * Returns the client authorization DN known as the client DN.
     * @return  The client's authorization DN.
     */
   /**
    * {@inheritDoc}
    */
    public DN getClientDN() {
      return this.authorizationEntry.getDN();
      if(this.useAuthzid)
        return this.authzid;
      else
       return this.authorizationEntry.getDN();
    }
    /**
     * Get the DN of the entry being evaluated.
     * @return The DN of the entry.
     */
   /**
    * {@inheritDoc}
    */
    public DN getResourceDN() {
        return resourceEntry.getDN();
    }
    /**
     * Checks if the container's rights has the specified rights.
     * @param  rights The rights to check for.
     * @return True if the container's rights has the specified rights.
     */
   /**
    * {@inheritDoc}
    */
    public boolean hasRights(int rights) {
       return (this.rights & rights) != 0;
    }
    /**
     * Return the rights set for this container's LDAP operation.
     * @return  The rights set for the container's LDAP operation.
     */
   /**
    * {@inheritDoc}
    */
    public int getRights() {
        return this.rights;
    }
    /**
     * Sets the rights for this container to the specified rights.
     * @param rights The rights to set the container's rights to.
     */
   /**
    * {@inheritDoc}
    */
    public void setRights(int rights) {
         this.rights=rights;
    }
    /**
     * Gets the hostname of the remote client.
     * @return  Cannonical hostname of remote client.
     */
   /**
    * {@inheritDoc}
    */
    public String getHostName() {
        return clientConnection.getRemoteAddress().getCanonicalHostName();
    }
    /**
     * Gets the remote client's address information.
     * @return  Remote client's address.
     */
   /**
    * {@inheritDoc}
    */
    public InetAddress getRemoteAddress() {
        return clientConnection.getRemoteAddress();
    }
    /**
     * Return true if the current operation is a LDAP add operation.
     * @return True if this is an add operation.
     */
   /**
    * {@inheritDoc}
    */
    public boolean isAddOperation() {
        return isAddOp;
    }
    /**
     * Set to true  if the ACI had a targattrfilter rule that matched.
     * @param v  The value to use.
     */
   /**
    * {@inheritDoc}
    */
    public void setTargAttrFiltersMatch(boolean v) {
        this.targAttrFiltersMatch=v;
    }
    /**
     * Return the value of the targAttrFiltersMatch variable. This is set to
     * true if the ACI had a targattrfilter rule that matched.
     * @return  True if the ACI had a targattrfilter rule that matched.
     */
   /**
    * {@inheritDoc}
    */
    public boolean getTargAttrFiltersMatch() {
        return targAttrFiltersMatch;
    }
@@ -498,12 +751,9 @@
      return matched;
    }
    /**
     * Convenience method that checks if the the clientDN is a member of the
     * specified group.
     * @param group The group to check membership in.
     * @return True if the clientDN is a member of the specified group.
     */
  /**
   * {@inheritDoc}
   */
    public boolean isMemberOf(Group group) {
        boolean ret;
        try {
@@ -513,4 +763,32 @@
        }
        return  ret;
    }
  /**
   * {@inheritDoc}
   */
    public String rightToString() {
      if(hasRights(ACI_SEARCH))
        return "search";
      else if(hasRights(ACI_COMPARE))
        return "compare";
      else if(hasRights(ACI_READ))
        return "read";
      else if(hasRights(ACI_DELETE))
        return "delete";
      else if(hasRights(ACI_ADD))
        return "add";
      else if(hasRights(ACI_WRITE))
        return "write";
      else if(hasRights(ACI_PROXY))
        return "proxy";
      else if(hasRights(ACI_IMPORT))
        return "import";
      else if(hasRights(ACI_EXPORT))
        return "export";
      else if(hasRights(ACI_WRITE) &&
              hasRights(ACI_SELF))
        return "selfwrite";
      return null;
  }
}
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciEffectiveRights.java
New file
@@ -0,0 +1,735 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import org.opends.server.types.*;
import org.opends.server.core.DirectoryServer;
import static org.opends.server.authorization.dseecompat.Aci.*;
import org.opends.server.protocols.asn1.ASN1OctetString;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.LinkedList;
/**
 * This class implements the dseecompat geteffectiverights evaluation.
 */
public class AciEffectiveRights {
  //Value used when a aclRights attribute was seen in the search operation
  //attribute set.
  private static final int ACL_RIGHTS = 0x001;
  //Value used when a aclRightsInfo attribute was seen in the search operation
  //attribute set.
  private static final int ACL_RIGHTS_INFO = 0x002;
  //Value used when an ACI has a targattrfilters keyword match and the result
  //of the access check was a deny.
  private static final int ACL_TARGATTR_DENY_MATCH = 0x004;
  //Value used when an ACI has a targattrfilters keyword match and the result
  //of the access check was an allow.
  private static final int ACL_TARGATTR_ALLOW_MATCH = 0x008;
  //String used to build attribute type name when an aclRights result needs to
  //be added to the return entry.
  private static final String aclRightsAttrStr = "aclRights";
  //String used to build attribute type name when an AclRightsInfo result needs
  //to be added to the return entry.
  private static final String aclRightsInfoAttrStr = "aclRightsInfo";
  //String used to build attribute type name when an entryLevel rights
  //attribute type name needs to be added to the return entry.
  private static final String entryLevelStr = "entryLevel";
  //String used to build attribute type name when an attributeLevel rights
  //attribute type name needs to be added to the return entry.
  private static final String attributeLevelStr = "attributeLevel";
  //The string that is used as the attribute type name when an
  //aclRights entryLevel evaluation needs to be added to the return entry.
  private static final String aclRightsEntryLevelStr=
                                     aclRightsAttrStr + ";" + entryLevelStr;
  //The string that is used as the  attribute type name when an
  //aclRights attribute level evaluation needs to be added to the return entry.
  //This string has the attribute type name used in the evaluation appended to
  //it to form the final attribute type name.
  private static final String aclRightsAttributeLevelStr=
        aclRightsAttrStr +  ";" + attributeLevelStr;
  //The string used to build attribute type name when an attribute level
  //aclRightsInfo attribute needs to be added to the return entry. This string
  //has the attribute type name used in the evaluation appended to it to form
  //the final attribute type name.
  private static final String aclRightsInfoAttrLogsStr =
                      aclRightsInfoAttrStr + ";logs;attributeLevel";
  //The string used to build attribute type name when an entryLevel
  //aclRightsInfo attribute needs to be added to the return entry.
  private static final String aclRightsInfoEntryLogsStr =
                      aclRightsInfoAttrStr + ";logs;entryLevel";
  //Attribute type used in access evaluation to see if the geteffectiverights
  //related to the "aclRights" attribute can be performed.
  private static AttributeType aclRights = null;
  //Attribute type used in access evaluation to see if the geteffectiverights
  //related to the "aclRightsInfo" attribute can be performed.
  private static AttributeType aclRightsInfo = null;
  //String used to fill in the summary status field when access was allowed.
  private static String ALLOWED="access allowed";
  //String used to fill in the summary status field when access was not allowed.
  private static String NOT_ALLOWED="access not allowed";
  //Evaluated as anonymous user. Used to fill in summary field.
  private static String anonymous="anonymous";
  //Format used to build the summary string.
  private static String summaryFormatStr =
        "acl_summary(%s): %s(%s) on entry/attr(%s, %s) to (%s)" +
        " (not proxied) ( reason: %s %s)";
  //Strings below represent access denied or allowed evaluation reasons.
  //Used to fill in the summary status field.
  //Access evaluated an allow ACI.
  private static String EVALUATED_ALLOW="evaluated allow";
  //Access evaluated a deny ACI.
  private static String EVALUATED_DENY="evaluated deny";
  //Access evaluated deny because there were no allow ACIs.
  private static String NO_ALLOWS="no acis matched the resource";
  //Access evaluated deny because no allow or deny ACIs evaluated.
  private static String NO_ALLOWS_MATCHED="no acis matched the subject";
  //Access evaluated allow because the clientDN has bypass-acl privileges.
  private static String SKIP_ACI="user has bypass-acl privileges";
  //TODO add support for the modify-acl privilige?
  /**
   * Attempts to add the geteffectiverights asked for in the search to the entry
   * being returned. The two geteffectiverights attributes that can be requested
   * are: aclRights and aclRightsInfo. The aclRightsInfo attribute will return
   * a summary string describing in human readable form, a summary of each
   * requested evaluation result. Here is a sample aclRightsInfo summary:
   *
   * acl_summary(main): access_not_allowed(proxy) on
   * entry/attr(uid=proxieduser,ou=acis,dc=example,dc=com, NULL) to
   * (uid=superuser,ou=acis,dc=example,dc=com) (not proxied)
   * (reason: no acis matched the resource )
   *
   * The aclRights attribute will return a simple
   * string with the following format:
   *
   *        add:0,delete:0,read:1,write:?,proxy:0
   *
   * A 0 represents access denied, 1 access allowed and ? that evaluation
   * depends on a value of an attribute (targattrfilter keyword present in ACI).
   *
   * There are two levels of rights information:
   *
   *  1. entryLevel - entry level rights information
   *  2. attributeLevel - attribute level rights information
   *
   * The attribute type names are built up using subtypes:
   *
   *    aclRights;entryLevel - aclRights entry level presentation
   *    aclRightsInfo;log;entryLevel;{right} - aclRightsInfo entry level
   *        presentation for each type of right (proxy, read, write, add,
   *        delete).
   *    aclRights;attributeLevel;{attributeType name} - aclRights attribute
   *        level presentation for each attribute type requested.
   *    aclRights;attributeLevel;logs;{right};{attributeType name}
   *        - aclRightsInfo  attribute level presentation for each attribute
   *          type requested.
   *
   * @param handler  The ACI handler to use in the evaluation.
   * @param searchAttributes  The attributes requested in the search.
   * @param container  The LDAP operation container to use in the evaluations.
   * @param e The entry to add the rights attributes to.
   * @param skipCheck  True if ACI evaluation was skipped because bypass-acl
   *                   privilege was found.
   * @return  A SearchResultEntry with geteffectiverights information possibly
   *          added to it.
   */
  public static SearchResultEntry
  addRightsToEntry(AciHandler handler, LinkedHashSet<String> searchAttributes,
            AciLDAPOperationContainer container,SearchResultEntry e,
            boolean skipCheck) {
    List<AttributeType> nonRightsAttrs = new LinkedList<AttributeType>();
    int attrMask=ACI_NULL;
    if(aclRights == null)
      aclRights =
              DirectoryServer.getAttributeType(aclRightsAttrStr.toLowerCase());
    if(aclRightsInfo == null)
      aclRightsInfo =
           DirectoryServer.getAttributeType(aclRightsInfoAttrStr.toLowerCase());
    //Check if the attributes aclRights and aclRightsInfo were requested and
    //add attributes less those two attributes to a new list of attribute types.
    for(String a : searchAttributes) {
      if(a.equalsIgnoreCase(aclRightsAttrStr))
        attrMask |= ACL_RIGHTS;
      else if(a.equalsIgnoreCase(aclRightsInfoAttrStr))
        attrMask |= ACL_RIGHTS_INFO;
      else {
        AttributeType attrType;
        if((attrType = DirectoryServer.getAttributeType(a)) == null)
          attrType = DirectoryServer.getDefaultAttributeType(a);
        nonRightsAttrs.add(attrType);
      }
    }
    //If the special geteffectiverights attributes were not found or
    //the user does not have both bypass-acl privs and is not allowed to
    //perform rights evalation -- return the entry unchanged.
    if(attrMask == ACI_NULL ||
      (!skipCheck && !rightsAccessAllowed(container,handler,attrMask)))
       return e;
    //From here on out, geteffectiverights evaluation is being performed and the
    //container will be manipulated. First set the flag that geteffectiverights
    //evaluation's underway and to use the authZid for authorizationDN (they
    //might be the same).
    container.setGetEffectiveRightsEval();
    container.useAuthzid(true);
    //If no attributes were requested return only entryLevel rights, else
    //return attributeLevel rights and entryLevel rights. Always try and
    //return the specific attribute rights if they exist.
    if(nonRightsAttrs.isEmpty()) {
      e=addAttributeLevelRights(container,handler,attrMask,e,
              container.getSpecificAttributes(), skipCheck);
      e=addEntryLevelRights(container,handler,attrMask,e, skipCheck);
    } else {
      e=addAttributeLevelRights(container,handler,attrMask,e,
              nonRightsAttrs,skipCheck);
      e=addAttributeLevelRights(container,handler,attrMask,e,
              container.getSpecificAttributes(), skipCheck);
      e=addEntryLevelRights(container,handler,attrMask,e,skipCheck);
    }
    return e;
  }
  /**
   * Perform the attributeLevel rights evaluation on a list of specified
   * attribute types. Each attribute has an access check done for the following
   * rights: search, read, compare, add, delete, proxy, selfwrite_add,
   * selfwrite_delete and write.
   *
   * The special rights, selfwrite_add and selfwrite_delete, use the authZid as
   * the attribute value to evaluate against the attribute type being
   * evaluated. The selfwrite_add performs the access check using the
   * ACI_WRITE_ADD right and selfwrite_delete uses ACI_WRITE_ADD right.
   *
   * The write right is made complicated by the targattrfilters keyword, which
   * might depend on an unknown value of an attribute type. For this case a
   * dummy attribute value is used to try and determine if a "?" needs to be
   * placed in the rights string.
   *
   * The special flag ACI_SKIP_PROXY_CHECK is always set, so that proxy
   * evaluation is bypassed in the Aci Handler's accessAllowed method.
   *
   * @param container The LDAP operation container to use in the evaluations.
   * @param handler  The Aci Handler to use in the access evaluations.
   * @param mask  Mask specifing what rights attribute processing to perform
   *              (aclRights or aclRightsInfo or both).
   * @param retEntry  The entry to return.
   * @param attrList The list of attribute types to iterate over.
   * @param skipCheck True if ACI evaluation was skipped because bypass-acl
   *                  privilege was found.
   * @return  A SearchResultEntry with geteffectiverights attribute level
   *          information added to it.
   */
  private static
  SearchResultEntry addAttributeLevelRights(AciLDAPOperationContainer container,
                                        AciHandler handler, int mask,
                                        SearchResultEntry retEntry,
                                        List<AttributeType> attrList,
                                        boolean skipCheck) {
    //The attribute list might be null.
    if(attrList == null)
      return retEntry;
    for(AttributeType a : attrList) {
      StringBuilder evalInfo=new StringBuilder();
      container.setCurrentAttributeType(a);
      container.setCurrentAttributeValue(null);
      //Perform search check and append results.
      container.setRights(ACI_SEARCH | ACI_SKIP_PROXY_CHECK);
      evalInfo.append(rightsString(container, handler, skipCheck, "search"));
      addAttrLevelRightsInfo(container, mask, a, retEntry, "search");
      evalInfo.append(',');
      //Perform read check and append results.
      container.setRights(ACI_READ | ACI_SKIP_PROXY_CHECK);
      evalInfo.append(rightsString(container, handler, skipCheck, "read"));
      addAttrLevelRightsInfo(container, mask, a, retEntry, "read");
      evalInfo.append(',');
      //Perform compare and append results.
      container.setRights(ACI_COMPARE | ACI_SKIP_PROXY_CHECK);
      evalInfo.append(rightsString(container, handler, skipCheck, "compare"));
      addAttrLevelRightsInfo(container, mask, a, retEntry, "compare");
      evalInfo.append(',');
      //Write right is more complicated. Create a dummy value and set that as
      //the attribute's value. Call the special writeRightsString method, rather
      //than rightsString.
      AttributeValue val=new AttributeValue(a, "dum###Val");
      container.setCurrentAttributeValue(val);
      evalInfo.append(attributeLevelWriteRights(container, handler, skipCheck));
      addAttrLevelRightsInfo(container, mask, a, retEntry, "write");
      evalInfo.append(',');
      //Perform both selfwrite_add and selfwrite_delete and append results.
      ByteString clientDNStr=
              new ASN1OctetString(container.getClientDN().toString());
      AttributeValue val1=new AttributeValue(a, clientDNStr);
      container.setCurrentAttributeValue(val1);
      container.setRights(ACI_WRITE_ADD | ACI_SKIP_PROXY_CHECK);
      evalInfo.append(rightsString(container, handler, skipCheck,
                      "selfwrite_add"));
      addAttrLevelRightsInfo(container, mask, a, retEntry, "selfwrite_add");
      evalInfo.append(',');
      container.setRights(ACI_WRITE_DELETE | ACI_SKIP_PROXY_CHECK);
      evalInfo.append(rightsString(container, handler, skipCheck,
                       "selfwrite_delete"));
      addAttrLevelRightsInfo(container, mask, a, retEntry, "selfwrite_delete");
      evalInfo.append(',');
      container.setCurrentAttributeValue(null);
                container.setRights(ACI_PROXY | ACI_SKIP_PROXY_CHECK);
      evalInfo.append(rightsString(container, handler, skipCheck, "proxy"));
      addAttrLevelRightsInfo(container, mask, a, retEntry, "proxy");
      //It is possible that only the aclRightsInfo attribute type was requested.
      //Only add the aclRights information if the aclRights attribute type was
      //seen.
      if(hasAttrMask(mask, ACL_RIGHTS))  {
        String typeStr=aclRightsAttributeLevelStr + ";" +
                a.getNormalizedPrimaryName();
        AttributeType attributeType=
                DirectoryServer.getDefaultAttributeType(typeStr);
        LinkedHashSet<AttributeValue> vals =
                new LinkedHashSet<AttributeValue>();
        vals.add(new AttributeValue(attributeType, evalInfo.toString()));
        Attribute attr =
                new Attribute(attributeType, typeStr, vals);
        //It is possible that the user might have specified the same attributes
        //in both the search and the specific attribute part of the control.
        //Only try to add the attribute type if it already hasn't been added.
        if(!retEntry.hasAttribute(attributeType))
         retEntry.addAttribute(attr,null);
      }
    }
    container.setCurrentAttributeValue(null);
    container.setCurrentAttributeType(null);
    return retEntry;
  }
  /**
   * Perform the attributeLevel write rights evaluation. The issue here is that
   * an ACI could contain a targattrfilters keyword that matches the attribute
   * being evaluated.
   *
   * There is no way of knowing if the filter part of the targattrfilter would
   * be successful or not. So if the ACI that allowed access, has an
   * targattrfilter keyword, a "?" is used as the result of the write (depends
   * on attribute value).
   *
   * If the allow ACI doesn't contain a targattrfilters keyword than a
   * "1" is added. If the ACI denies then a "0" is added. If the skipCheck flag
   * is true, then a 1 is used for the write access, since the client DN has
   * bypass privs.
   *
   * @param container The LDAP operation container to use in the evaluations.
   * @param handler The Aci Handler to use in the access evaluations.
   * @param skipCheck True if ACI evaluation was skipped because bypass-acl
   *                  privilege was found.
   * @return A string representing the rights information.
   */
  private static
  String attributeLevelWriteRights(AciLDAPOperationContainer container,
                                   AciHandler handler,  boolean skipCheck){
    boolean addRet=false, delRet=false;
    StringBuilder resString=new  StringBuilder();
    //If the user has bypass-acl privs and the authzid is equal to the
    //authorization dn, create a right string with a '1' and a valid
    //summary. If the user has bypass-acl privs and is querying for
    //another authzid or they don't have privs  -- fall through.
    if(skipCheck && container.isAuthzidAuthorizationDN()) {
      resString.append("write").append(":1");
      container.setEvalReason(EnumEvalReason.SKIP_ACI);
      container.setDecidingAci(null);
      createSummary(container, true, "main");
    } else {
     //Reset everything.
      container.resetEffectiveRightsParams();
      //Reset name.
      container.setTargAttrFiltersAciName(null);
      container.setRights(ACI_WRITE_ADD | ACI_SKIP_PROXY_CHECK);
      if(handler.accessAllowed(container)) {
        if(container.getTargAttrFiltersAciName() == null)
          addRet=true;
      }
      container.setRights(ACI_WRITE_DELETE | ACI_SKIP_PROXY_CHECK);
      if(handler.accessAllowed(container)) {
        if(container.getTargAttrFiltersAciName() == null)
          delRet=true;
      }
      //If both booleans are true, then access was allowed by ACIs that did
      //not contain targattrfilters.
      if(addRet && delRet)
        resString.append("write").append(":1");
      else {
        //If there is an ACI name then an ACI with a targattrfilters allowed,
        //access. A '?' is needed because that evaluation really depends on an
        //unknown attribute value, not the dummy value. If there is no ACI
        //then one of the above access checks failed and a '0' is needed.
        if(container.getTargAttrFiltersAciName() != null) {
          resString.append("write").append(":?");
        } else {
          resString.append("write").append(":0");
        }
      }
    }
    return resString.toString();
  }
  /**
   * Perform entryLevel rights evaluation. The rights string is added to the
   * entry if the aclRights attribute was seen in the search's requested
   * attribute set.
   *
   * @param container The LDAP operation container to use in the evaluations.
   * @param handler The Aci Handler to use in the access evaluations.
   * @param mask Mask specifing what rights attribute processing to perform
   *              (aclRights or aclRightsInfo or both).
   * @param retEntry The entry to return.
   * @param skipCheck True if ACI evaluation was skipped because bypass-acl
   *                  privilege was found.
   * @return A SearchResultEntry with geteffectiverights entryLevel rights
   *          information added to it.
   */
  private static SearchResultEntry
  addEntryLevelRights(AciLDAPOperationContainer container,
                                           AciHandler handler,
                                           int mask, SearchResultEntry retEntry,
                                           boolean skipCheck) {
    //Perform access evaluations for rights: add, delete, read, write, proxy.
    StringBuilder evalInfo=new StringBuilder();
    container.setCurrentAttributeType(null);
    container.setRights(ACI_ADD | ACI_SKIP_PROXY_CHECK);
    evalInfo.append(rightsString(container, handler, skipCheck, "add"));
    addEntryLevelRightsInfo(container, mask, retEntry, "add");
    evalInfo.append(',');
    container.setCurrentAttributeType(null);
    container.setRights(ACI_DELETE | ACI_SKIP_PROXY_CHECK);
    evalInfo.append(rightsString(container, handler, skipCheck, "delete"));
    addEntryLevelRightsInfo(container, mask, retEntry, "delete");
    evalInfo.append(',');
    container.setCurrentAttributeType(null);
    //The read right needs the entry with the full set of attributes. This was
    //saved in the Aci Handlers maysend method.
    container.useFullResourceEntry(true);
    container.setRights(ACI_READ | ACI_SKIP_PROXY_CHECK);
    evalInfo.append(rightsString(container, handler, skipCheck, "read"));
    addEntryLevelRightsInfo(container, mask, retEntry, "read");
    evalInfo.append(',');
    //Switch back to the entry from the Aci Handler's filterentry method.
    container.useFullResourceEntry(false);
    container.setCurrentAttributeType(null);
    container.setRights(ACI_WRITE| ACI_SKIP_PROXY_CHECK);
    evalInfo.append(rightsString(container, handler, skipCheck, "write"));
    addEntryLevelRightsInfo(container, mask, retEntry, "write");
    evalInfo.append(',');
    container.setCurrentAttributeType(null);
    container.setRights(ACI_PROXY| ACI_SKIP_PROXY_CHECK);
    evalInfo.append(rightsString(container, handler, skipCheck, "proxy"));
    addEntryLevelRightsInfo(container, mask, retEntry, "proxy");
    if(hasAttrMask(mask, ACL_RIGHTS)) {
      AttributeType attributeType=
              DirectoryServer.getDefaultAttributeType(aclRightsEntryLevelStr);
      LinkedHashSet<AttributeValue> vals = new LinkedHashSet<AttributeValue>();
      vals.add(new AttributeValue(attributeType, evalInfo.toString()));
      Attribute attr =
              new Attribute(attributeType, aclRightsEntryLevelStr, vals);
      retEntry.addAttribute(attr,null);
    }
    return retEntry;
  }
  /**
   * Create the rights for aclRights attributeLevel or entryLevel rights
   * evaluation. The only right needing special treatment is the read right
   * with no current attribute type set in the container. For that case the
   * accessAllowedEntry method is used instead of the accessAllowed method.
   *
   * @param container The LDAP operation container to use in the evaluations.
   * @param handler The Aci Handler to use in the access evaluations.
   * @param skipCheck True if ACI evaluation was skipped because bypass-acl
   *                  privilege was found.
   * @param rightStr String used representation of the right we are evaluating.
   * @return  A string representing the aclRights for the current right and
   * attribute type/value combinations.
   */
  private static
  String rightsString(AciLDAPOperationContainer container,
                                            AciHandler handler,
                                            boolean skipCheck, String rightStr){
    StringBuilder resString=new  StringBuilder();
    container.resetEffectiveRightsParams();
    //If the user has bypass-acl privs and the authzid is equal to the
    //authorization dn, create a right string with a '1' and a valid
    //summary. If the user has bypass-acl privs and is querying for
    //another authzid or they don't have privs  -- fall through.
    if(skipCheck && container.isAuthzidAuthorizationDN()) {
      resString.append(rightStr).append(":1");
      container.setEvalReason(EnumEvalReason.SKIP_ACI);
      container.setDecidingAci(null);
      createSummary(container, true, "main");
    } else {
      boolean ret;
      //Check if read right check, if so do accessAllowedEntry.
      if(container.hasRights(ACI_READ) &&
         container.getCurrentAttributeType() == null)
        ret=handler.accessAllowedEntry(container);
      else
        ret=handler.accessAllowed(container);
      if(ret)
        resString.append(rightStr).append(":1");
      else
        resString.append(rightStr).append(":0");
    }
    return resString.toString();
  }
  /**
   * Check that access is allowed on the aclRights and/or aclRightsInfo
   * attribute types.
   *
   * @param container The LDAP operation container to use in the evaluations.
   * @param handler   The Aci Handler to use in the access evaluations.
   * @param mask Mask specifing what rights attribute processing to perform
   *              (aclRights or aclRightsInfo or both).
   * @return True if access to the geteffectiverights attribute types are
   *         allowed.
   */
  private static
  boolean rightsAccessAllowed(AciLDAPOperationContainer container,
                              AciHandler handler, int mask) {
    boolean retRight=true, retInfo=true;
    if(hasAttrMask(mask, ACL_RIGHTS)) {
        container.setCurrentAttributeType(aclRights);
        container.setRights(ACI_READ | ACI_SKIP_PROXY_CHECK);
        retRight=handler.accessAllowed(container);
    }
    if(hasAttrMask(mask, ACL_RIGHTS_INFO)) {
        container.setCurrentAttributeType(aclRightsInfo);
        container.setRights(ACI_READ | ACI_SKIP_PROXY_CHECK);
        retInfo=handler.accessAllowed(container);
    }
    return !(!retRight || !retInfo);
  }
  /**
   * Add aclRightsInfo attributeLevel information to the entry. This is the
   * summary string built from the last access check.
   *
   * @param container  The LDAP operation container to use in the evaluations.
   * @param mask  Mask specifing what rights attribute processing to perform
   *              (aclRights or aclRightsInfo or both).
   * @param aType The attribute type to use in building the attribute type name.
   * @param retEntry The entry to add the rights information to.
   * @param rightStr The string representation of the rights evaluated.
   */
  private static
  void addAttrLevelRightsInfo(AciLDAPOperationContainer container, int mask,
                     AttributeType aType, SearchResultEntry retEntry,
                     String rightStr) {
    //Check if the aclRightsInfo attribute was requested.
    if(hasAttrMask(mask,ACL_RIGHTS_INFO)) {
      //Build the attribute type.
      String typeStr=
              aclRightsInfoAttrLogsStr + ";" + rightStr + ";" +
              aType.getPrimaryName();
         AttributeType attributeType=
                DirectoryServer.getDefaultAttributeType(typeStr);
      LinkedHashSet<AttributeValue> vals = new LinkedHashSet<AttributeValue>();
      vals.add(new AttributeValue(attributeType, container.getEvalSummary()));
      Attribute attr =
                     new Attribute(attributeType, typeStr, vals);
      //The attribute type might have already been added, probably not but it
      //is possible.
      if(!retEntry.hasAttribute(attributeType))
          retEntry.addAttribute(attr,null);
    }
  }
  /**
   * Add aclRightsInfo entryLevel rights to the entry to be returned. This is
   * the summary string built from the last access check.
   *
   * @param container   The LDAP operation container to use in the evaluations.
   * @param mask Mask specifing what rights attribute processing to perform
   *              (aclRights or aclRightsInfo or both).
   * @param retEntry  The entry to add the rights information to.
   * @param rightStr The string representation of the rights evaluated.
   */
  private static
   void addEntryLevelRightsInfo(AciLDAPOperationContainer container, int mask,
                       SearchResultEntry retEntry,
                      String rightStr) {
     //Check if the aclRightsInfo attribute was requested.
     if(hasAttrMask(mask,ACL_RIGHTS_INFO)) {
       String typeStr=
               aclRightsInfoEntryLogsStr + ";" + rightStr;
          AttributeType attributeType=
                 DirectoryServer.getDefaultAttributeType(typeStr);
       LinkedHashSet<AttributeValue> vals = new LinkedHashSet<AttributeValue>();
       vals.add(new AttributeValue(attributeType, container.getEvalSummary()));
       Attribute attr =
                      new Attribute(attributeType, typeStr, vals);
       retEntry.addAttribute(attr,null);
     }
   }
  /**
   * Check if the provided mask has a specific rights attr value.
   *
   * @param mask The mask with the attribute flags.
   * @param rightsAttr The rights attr value to check for.
   * @return True if the mask contains the rights attr value.
   */
  private static boolean hasAttrMask(int mask, int rightsAttr) {
        return (mask & rightsAttr) != 0;
  }
  /**
   * Create the summary string used in the aclRightsInfo log string.
   *
   * @param evalCtx The evaluation context to gather information from.
   * @param evalRet The value returned from the access evaluation.
   * @param srcStr String that can be used to specify where the summary call's
   *               origin is.
   */
 public static
  void createSummary(AciEvalContext evalCtx, boolean evalRet, String srcStr) {
    String accessStatus=NOT_ALLOWED;
    if(evalRet)
      accessStatus=ALLOWED;
    String accessReason="";
    StringBuilder decideAci=new StringBuilder("");
    //Try and determine what reason string to use.
    if(evalCtx.getEvalReason() == EnumEvalReason.EVALUATED_ALLOW_ACI) {
      accessReason=EVALUATED_ALLOW;
      decideAci.append(", deciding_aci: ").append(evalCtx.getDecidingAciName());
    } else if(evalCtx.getEvalReason() == EnumEvalReason.EVALUATED_DENY_ACI) {
      accessReason=EVALUATED_DENY;
      decideAci.append(", deciding_aci: ").append(evalCtx.getDecidingAciName());
    }  else if(evalCtx.getEvalReason() == EnumEvalReason.NO_ALLOW_ACIS)
      accessReason=NO_ALLOWS;
    else if(evalCtx.getEvalReason() == EnumEvalReason.NO_MATCHED_ALLOWS_ACIS)
      accessReason=NO_ALLOWS_MATCHED;
    else if(evalCtx.getEvalReason() == EnumEvalReason.SKIP_ACI)
      accessReason=SKIP_ACI;
    //Only manipulate the evaluation context's targattrfilters ACI name
    //if not a selfwrite evaluation and the context's targattrfilter match
    //hashtable is not empty.
    if(!evalCtx.isTargAttrFilterMatchAciEmpty() &&
            !evalCtx.hasRights(ACI_SELF)) {
      //If the allow list was empty then access is '0'.
      if(evalCtx.getAllowList().isEmpty()) {
        evalCtx.setTargAttrFiltersAciName(null);
      } else if(evalRet) {
        //The evaluation returned true, clear the evaluation context's
        //targattrfilters ACI name only if a deny targattrfilters ACI
        //was not seen. It could remove the allow.
        if(!evalCtx.hasTargAttrFiltersMatchOp(ACL_TARGATTR_DENY_MATCH))
          evalCtx.setTargAttrFiltersAciName(null);
      } else {
        //The evaluation returned false. If the reason was an
        //explicit deny evaluation by a non-targattrfilters ACI, clear
        //the evaluation context's targattrfilters ACI name since targattrfilter
        //evaluation is pretty much ignored during geteffectiverights eval.
        //Else, it was a non-explicit deny, if there is not a targattrfilters
        //ACI that might have granted access the deny stands, else there is
        //a targattrfilters ACI that might grant access.
        if(evalCtx.getEvalReason() == EnumEvalReason.EVALUATED_DENY_ACI)
          evalCtx.setTargAttrFiltersAciName(null);
        else if(!evalCtx.hasTargAttrFiltersMatchOp(ACL_TARGATTR_ALLOW_MATCH))
          evalCtx.setTargAttrFiltersAciName(null);
      }
    }
    //Actually build the string.
    String user=anonymous;
    if(!evalCtx.getClientDN().isNullDN())
      user=evalCtx.getClientDN().toString();
    String right=evalCtx.rightToString();
    AttributeType aType=evalCtx.getCurrentAttributeType();
    String attrStr="NULL";
    if(aType != null)
      attrStr=aType.getPrimaryName();
    if(evalCtx.getTargAttrFiltersAciName() != null)
      decideAci.append(", access depends on attr value");
    String summaryStr = String.format(summaryFormatStr, srcStr, accessStatus,
                         right,evalCtx.getResourceDN().toString(),attrStr, user,
                            accessReason, decideAci.toString());
    evalCtx.setEvalSummary(summaryStr);
  }
  /**
   * If the specified ACI is in the targattrfilters hashtable contained in the
   * evaluation context, set the  evaluation context's targattrfilters match
   * variable to either ACL_TARGATTR_DENY_MATCH or ACL_TARGATTR_ALLOW_MATCH
   * depending on the value of the variable denyAci.
   *
   * @param evalCtx The evaluation context to evaluate and save information to.
   * @param aci The ACI to match.
   * @param denyAci True if the evaluation was a allow, false if the
   *                evaluation was an deny or the ACI is not in the table.
   * @return  True if the ACI was found in the hashtable.
   */
  public static
  boolean  setTargAttrAci(AciEvalContext evalCtx, Aci aci, boolean denyAci) {
    boolean ret=false;
    if(evalCtx.hasTargAttrFiltersMatchAci(aci)) {
       if(denyAci)
        evalCtx.setTargAttrFiltersMatchOp(ACL_TARGATTR_DENY_MATCH);
      else
        evalCtx.setTargAttrFiltersMatchOp(ACL_TARGATTR_ALLOW_MATCH);
      ret=true;
    }
    return ret;
  }
}
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciEvalContext.java
@@ -29,6 +29,7 @@
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.AttributeType;
import org.opends.server.api.Group;
import java.net.InetAddress;
@@ -146,4 +147,161 @@
     * member of the specified group.
     */
    public boolean isMemberOf(Group group);
  /**
   * Returns true if the hashtable of ACIs that matched the targattrfilters
   * keyword evaluation is empty.  Used by geteffectiverights evaluation to
   * determine the access value to put in the "write" rights evaluation field.
   *
   * @return True if there were not any ACIs that matched targattrfilters
   *         keyword evaluation.
   */
    public boolean isTargAttrFilterMatchAciEmpty();
  /**
   * The context maintains a hashtable of ACIs that matched the targattrfilters
   * keyword evaluation.  The hasTargAttrFiltersMatchAci method returns true if
   * the specified ACI is contained in that hashtable. Used by
   * geteffectiverights evaluation to determine the access value to put in the
   * "write" rights evaluation field.
   *
   * @param aci The ACI that to evaluate if it contains a match during
   *            targattrfilters keyword evaluation.
   *
   * @return True if a specified ACI matched targattrfilters evaluation.
   */
    public boolean hasTargAttrFiltersMatchAci(Aci aci);
  /**
   * Return true if an ACI that evaluated to deny or allow has an
   * targattrfilters keyword. Used by geteffectiverights
   * evaluation to determine the access value to put in the "write" rights
   * evaluation field.
   *
   * @param flag  The integer value specifying either a deny or allow, but not
   * both.
   *
   * @return   True if the ACI that evaluated to
   */
    public boolean hasTargAttrFiltersMatchOp(int flag);
  /**
   * Returns true if the evaluation context is being used in a
   * geteffectiverights evaluation.
   *
   * @return  True if the evaluation context is being used in a
   * geteffectiverights evaluation.
   */
    public boolean isGetEffectiveRightsEval();
  /**
   * Set the name of the ACI that last matched a targattrfilters rule. Used
   * in geteffectiverights targattrfilters "write" rights evaluation.
   *
   * @param name The ACI name string matching the targattrfilters rule.
   */
    public void setTargAttrFiltersAciName(String name);
  /**
   * Set a flag that specifies that a ACI that evaluated to either deny or
   * allow contains a targattrfilters keyword. Used by geteffectiverights
   * evaluation to determine the access value to put in the "write" rights
   * evaluation field.
   *
   * @param flag Either the integer value representing an allow or a deny,
   *             but not both.
   */
    public void setTargAttrFiltersMatchOp(int flag);
  /**
   * Set the reason the last access evaluation was evaluated the way it
   * was. Used by geteffectiverights evaluation to eventually build the
   * summary string.
   *
   * @param reason  The enumeration representing the reason of the last access
   * evaluation.
   */
    public void setEvalReason(EnumEvalReason reason);
  /**
   * Return the reason the last access evaluation was evaluated the way it
   * was. Used by geteffectiverights evaluation to build the summary string.
   *
   * @return The enumeration representing the reason of the last access
   * evaluation.
   */
    public EnumEvalReason getEvalReason();
  /**
   * Set the ACI that decided that last access evaluation. Used by
   * geteffectiverights evaluation to the build summary string.
   *
   * @param aci The ACI that decided the last access evaluation.
   */
    public void setDecidingAci(Aci aci);
  /**
   * Check if an evaluation context contains a set of access rights.
   *
   * @param rights The rights mask to check.
   *
   * @return True if the evaluation context contains a access right set.
   */
    public boolean hasRights(int rights);
  /**
   * Return the name of the ACI that decided the last access evaluation. Used
   * by geteffectiverights evaluation to build the summmary string.
   *
   * @return The name of the ACI that decided the last access evaluation.
   */
    public String getDecidingAciName();
  /**
   * Return true if a evaluation context is being used in proxied authorization
   * evaluation.
   *
   * @return  True if evaluation context is being used in proxied authorization
   * evaluation.
   */
    public boolean isProxiedAuthorization();
    /**
     * Get the current attribute type being evaluated.
     *
     * @return  The attribute type currently being evaluated.
     */
    public AttributeType getCurrentAttributeType();
  /**
   * Set the value of the summary string to the specified string.
   * Used in geteffectiverights evaluation to build summary string.
   *
   * @param summary The string to set the summary string to
   */
    public void setEvalSummary(String summary);
  /**
   * Return the access evaluation summary string. Used by the geteffectiverights
   * evaluation when a aclRightsInfo attribute was specified in a search.
   *
   * @return   The string describing the access evaluation.
   */
    public String getEvalSummary();
  /**
   * Return a string representation of the current right being evaluated.
   * Used in geteffectiverights evaluation to build summary string.
   *
   * @return  String representation of the current right being evaluated.
   */
    public String rightToString();
    /**
   * Return the name of the ACI that last matched a targattrfilters rule. Used
   * in geteffectiverights evaluation.
   *
   * @return   The name of the ACI that last matched a targattrfilters rule.
   */
    public String getTargAttrFiltersAciName();
}
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java
@@ -43,6 +43,7 @@
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.protocols.internal.InternalClientConnection;
import static org.opends.server.schema.SchemaConstants.*;
import static org.opends.server.util.ServerConstants.OID_GET_EFFECTIVE_RIGHTS;
import java.util.*;
import java.util.concurrent.locks.Lock;
@@ -75,6 +76,14 @@
     */
    public static String ORIG_AUTH_ENTRY="origAuthorizationEntry";
   /**
     * 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 String ALL_ATTRS_RESOURCE_ENTRY = "allAttrsResourceEntry";
    /**
     * This constructor instantiates the ACI handler class that performs the
     * main processing for the dseecompat ACI package. It does the following
@@ -352,28 +361,71 @@
     */
    private boolean testApplicableLists(AciEvalContext evalCtx) {
        EnumEvalResult res=EnumEvalResult.FALSE;
        //First check deny lists
        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) ||
              (res.equals(EnumEvalResult.TRUE)))) {
               return false;
           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);
        LinkedList<Aci>allows=evalCtx.getAllowList();
        for(Aci allowAci : allows) {
           res=Aci.evaluate(evalCtx, allowAci);
           if(res.equals(EnumEvalResult.TRUE)) {
               break;
           }
        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;
            }
          }
        }
        return res.getBoolVal();
        //Nothing matched fall through.
        evalCtx.setEvalReason(EnumEvalReason.NO_MATCHED_ALLOWS_ACIS);
        evalCtx.setDecidingAci(null);
        return false;
    }
    /**
@@ -397,6 +449,8 @@
                   allows.add(aci);
                }
            }
           if(targetMatchCtx.getTargAttrFiltersMatch())
              targetMatchCtx.setTargAttrFiltersMatch(false);
        }
        targetMatchCtx.setAllowList(allows);
        targetMatchCtx.setDenyList(denys);
@@ -425,7 +479,7 @@
     *
     * @return True if access is allowed.
     */
    private boolean accessAllowed(AciContainer container)
     boolean accessAllowed(AciContainer container)
    {
        DN dn = container.getResourceEntry().getDN();
        //For ACI_WRITE_ADD and ACI_WRITE_DELETE set the ACI_WRITE
@@ -439,9 +493,9 @@
        if((container.getCurrentAttributeValue() != null) &&
           (container.hasRights(ACI_WRITE)) &&
           (isAttributeDN(container.getCurrentAttributeType())))  {
          String DNString=null;
          try {
            String DNString =
                        container.getCurrentAttributeValue().getStringValue();
           DNString  =  container.getCurrentAttributeValue().getStringValue();
            DN tmpDN = DN.decode(DNString);
            //Have a valid DN, compare to clientDN to see if the ACI_SELF
            //right should be set.
@@ -449,7 +503,11 @@
              container.setRights(container.getRights() | ACI_SELF);
            }
          } catch (DirectoryException ex) {
             return false;
             //Log a message and keep going.
             int  msgID  = MSGID_ACI_NOT_VALID_DN;
             String message = getMessage(msgID, DNString);
             logError(ErrorLogCategory.ACCESS_CONTROL,
                     ErrorLogSeverity.INFORMATIONAL, message, msgID);
          }
        }
@@ -458,8 +516,9 @@
        //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.hasRights(ACI_PROXY) &&
             container.isProxiedAuthorization()) {
          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);
@@ -488,9 +547,13 @@
         */
        createApplicableList(candidates,container);
        /*
         * Lastly, evaluate the applicable list.
         * Evaluate the applicable list.
         */
        return(testApplicableLists(container));
        boolean ret=testApplicableLists(container);
        //Build summary string if doing geteffectiverights eval.
        if(container.isGetEffectiveRightsEval())
          AciEffectiveRights.createSummary(container, ret, "main");
        return ret;
    }
  /**
@@ -578,7 +641,7 @@
     *
     * @return True if access is allowed.
     */
    private boolean accessAllowedEntry(AciLDAPOperationContainer container) {
     boolean accessAllowedEntry(AciLDAPOperationContainer container) {
        boolean ret=false;
        //set flag that specifies this is the first attribute evaluated
        //in the entry
@@ -816,6 +879,8 @@
              ret=accessAllowedEntry(operationContainer);
          }
      }
      if(ret && operation.getAttachment(OID_GET_EFFECTIVE_RIGHTS) != null)
         operation.setAttachment(ALL_ATTRS_RESOURCE_ENTRY, entry );
      return ret;
  }
@@ -844,10 +909,17 @@
      //method, set the seen flag to true to bypass any proxy check.
      operationContainer.setSeenEntry(true);
      SearchResultEntry returnEntry;
      if(!skipAccessCheck(operation)) {
      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;
  }
@@ -1009,6 +1081,19 @@
    return true;
  }
  /**
   * Called when a geteffectiverights request control was decoded. Currently
   * used to save the control in the specified operation's attachment list.
   * Eventually will be used to check access to the actual control.
   * @param operation The operation to save the attachment to.
   * @param c  The request control to save.
   * @return  True if the control is allowed access.
   */
  public boolean isGetEffectiveRightsAllowed(Operation operation, Control c) {
    operation.setAttachment(OID_GET_EFFECTIVE_RIGHTS, c);
    return true;
  }
  //Not planned to be implemented methods.
   /**
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciTargetMatchContext.java
@@ -143,6 +143,28 @@
     * @return  True if the ACI had a targattrfilter rule that matched.
     */
    public boolean getTargAttrFiltersMatch();
    /**
     * Add the specified ACI to a list of ACIs that have a targattrfilters rule
     * that matched. This is used by geteffectiverights to determine the rights
     * of an attribute that possibly might evaluate to true.
     * @param aci The ACI to save.
     */
    public void addTargAttrFiltersMatchAci(Aci aci);
    /**
     * Save the name of the last ACI that matched a targattrfilters rule. This
     * is used by geteffectiverights evaluation.
     * @param name The ACI's name to save.
     */
    void setTargAttrFiltersAciName(String name);
    /**
     * Returns true of a match context is performing a geteffectiverights
     * evaluation.
     * @return  True if a match context is evaluating geteffectiverights.
     */
    boolean isGetEffectiveRightsEval();
}
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciTargets.java
@@ -385,7 +385,7 @@
                     targAttrFilters.hasMask(TARGATTRFILTERS_ADD)) ||
                    (matchCtx.hasRights(ACI_WRITE_DELETE) &&
                    targAttrFilters.hasMask(TARGATTRFILTERS_DELETE)))
                ret=targAttrFilters.isApplicableMod(matchCtx);
                ret=targAttrFilters.isApplicableMod(matchCtx, aci);
        }
        return ret;
    }
@@ -423,7 +423,8 @@
                else
                    ret = false;
            }
            if((isFirstAttr) && (aci.getTargets().getTargetAttr() == null))
            if((isFirstAttr) && (aci.getTargets().getTargetAttr() == null)
                && aci.getTargets().getTargAttrFilters() == null)
                targetMatchCtx.setEntryTestRule(true);
        }
        return ret;
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/EnumEvalReason.java
New file
@@ -0,0 +1,74 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
/**
 * This class provides an enumeration of the reasons why an ACI evaluation
 * returned a result from the AciHandler's testApplicableLists call. This class
 * is used by a geteffectiverights aclRightsInfo attribute search to build
 * a summary string.
 */
public enum EnumEvalReason {
  /**
   * There are aren't any allow ACIs.
   */
  NO_ALLOW_ACIS(0),
  /**
   * An deny ACI either evaluated to FAIL or to TRUE.
   */
  EVALUATED_DENY_ACI(1),
  /**
   * An allow  evaluated to true.
   */
  EVALUATED_ALLOW_ACI(2),
  /**
   * None of the allow and deny ACIs evaluated to true.
   */
  NO_MATCHED_ALLOWS_ACIS(3),
  /**
   * No specific reason could be determined.
   */
  NO_REASON(4),
  /**
   * The authorization DN has bypass-acl privileges.
   */
  SKIP_ACI(5);
  /**
   * Create a new enumeration type for the specified result value.
   * @param v The value of the result.
   */
  EnumEvalReason(int v) {}
}
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/TargAttrFilters.java
@@ -246,10 +246,12 @@
     * operation.
     * @param matchCtx The target match context containing the information
     * needed to match.
     * @param aci  The ACI currently being evaluted for a target match.
     * @return True if this TargAttrFitlers object is applicable to this
     * target match context.
     */
    public boolean isApplicableMod(AciTargetMatchContext matchCtx) {
    public boolean isApplicableMod(AciTargetMatchContext matchCtx,
                                   Aci aci) {
        //Get the targFitlerList corresponding to this context's rights.
        TargAttrFilterList attrFilterList=getTargAttrFilterList(matchCtx);
        //If the list is empty return true and go on to the targattr check
@@ -270,6 +272,12 @@
            attrMatched=matchFilterAttributeValue(attrType, value, filter);
            //This flag causes any targattr checks to be bypassed in AciTargets.
            matchCtx.setTargAttrFiltersMatch(true);
            //Doing a geteffectiverights eval, save the ACI and the name
            //in the context.
            if(matchCtx.isGetEffectiveRightsEval()) {
              matchCtx.setTargAttrFiltersAciName(aci.getName());
              matchCtx.addTargAttrFiltersMatchAci(aci);
            }
            if(op.equals(EnumTargetOperator.NOT_EQUALITY))
                attrMatched = !attrMatched;
        }
opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/TargetAttr.java
@@ -54,6 +54,11 @@
     */
    private HashSet<AttributeType> attributes = new HashSet<AttributeType>();
  /**
   * HashSet of the operational attribute types parsed by the constructor.
   */
  private HashSet<AttributeType> opAttributes = new HashSet<AttributeType>();
    /*
     * Regular expression that matches one or more ATTR_NAME's separated by
     * the "||" token.
@@ -93,6 +98,22 @@
                        //Add each element of array to attributes HashSet
                        //after converting it to AttributeType.
                        arrayToAttributeTypes(attributeArray);
                       //Must be either all operational attrs or all user attrs,
                       //but not both.
                        if(!opAttributes.isEmpty() && !attributes.isEmpty()) {
                            int msgID =
                             MSGID_ACI_TARGETATTR_INVALID_OP_USER_ATTR;
                            String message = getMessage(msgID, attrString);
                            throw new AciException(msgID, message);
                        }
                        //Inequality not allowed with operational attrs.
                        if(!opAttributes.isEmpty() &&
                            operator.equals(EnumTargetOperator.NOT_EQUALITY)) {
                            int msgID =
                               MSGID_ACI_TARGATTR_INVALID_OP_ATTR_INEQUALITY;
                            String message = getMessage(msgID, attrString);
                            throw new AciException(msgID, message);
                        }
                    } else {
                      int msgID =
                         MSGID_ACI_SYNTAX_INVALID_TARGETATTRKEYWORD_EXPRESSION;
@@ -106,7 +127,8 @@
    /**
     * Converts each element of an array of attribute type strings
     * to attribute types and adds them to the attributes HashSet.
     * to attribute types and adds them to either the attributes HashSet or
     * the operational attributes HashSet if they are operational.
     * @param attributeArray The array of attribute type strings.
     */
    private void arrayToAttributeTypes(String[] attributeArray) {
@@ -117,6 +139,9 @@
                    DirectoryServer.getAttributeType(attribute)) == null)
                attributeType =
                        DirectoryServer.getDefaultAttributeType(attribute);
          if(attributeType.isOperational())
           opAttributes.add(attributeType);
          else
            attributes.add(attributeType);
        }
    }
@@ -147,6 +172,15 @@
        return attributes;
    }
  /**
   * Return array holding  operational attribute types to be evaluated
   * in the expression.
   * @return  Array holding attribute types.
   */
  public HashSet<AttributeType> getOpAttributes() {
        return opAttributes;
    }
    /**
     * Decodes an targetattr expression string into a targetattr class suitable
     * for evaluation.
@@ -167,12 +201,24 @@
     *
     *       targetattrs="*"
     *
     * is  seen when an ACI is parsed. If the isAllAttributes boolean is
     * true, the second check is skipped and the TargetAttr's operator is
     * checked to see if the method should return false (NOT_EQUALITY)
     * instead of true.
     * is  seen when an ACI is parsed.  This boolean only applies to
     * non-operational attribute types. If the attribute type being evaluated
     * and the isAllAttributes is true, then the evaluation will return false
     * because operational attributes must be explicity defined.
     *
     * If the isAllAttributes boolean is false, then the TargeAttr's
     * If the isAllAttributes boolean is true (and the attribute is
     * non-operational), the second check is skipped and the TargetAttr's
     * operator is checked to see if the method should return false
     * (NOT_EQUALITY) instead of true.
     *
     * If the isAllAttributes boolean is false, then the attribute type is
     * checked to see if it is operational. If it is, then the operational
     * HashSet is searched to see if it contains the operational attribute
     * type. If it is found then true is returned, else false is returned
     * if it isn't found. The NOT_EQUALITY operator is invalid for operational
     * attribute types and is not checked.
     *
     * If the attribute is not operational,  then the TargeAttr's user
     * attribute type HashSet is searched to see if it contains the
     * specified attribute type. That result could be negated depending
     * on if the TargetAttr's operator is NOT_EQUALITY.
@@ -183,25 +229,33 @@
     * TargetAttr's operator value applied to the test result.
     */
    public static boolean isApplicable(AttributeType a,
                          TargetAttr targetAttr) {
                                       TargetAttr targetAttr) {
      boolean ret;
      if(targetAttr.isAllAttributes()) {
          //If it is an operational attribute, then access is denied for all
          //attributes wild-card. Operational attributes must be
          // explicitly defined.
          if(a.isOperational()) {
              ret=false;
          } else
              ret =
              !targetAttr.getOperator().equals(EnumTargetOperator.NOT_EQUALITY);
      }  else {
        //If it is an operational attribute, then access is denied for all
        //attributes wild-card. Operational attributes must be
        // explicitly defined and cannot be negated.
        if(a.isOperational()) {
          ret=false;
        } else
          ret =
             !targetAttr.getOperator().equals(EnumTargetOperator.NOT_EQUALITY);
      }  else {
        ret=false;
          HashSet<AttributeType> attributes=targetAttr.getAttributes();
          if(attributes.contains(a))
          HashSet<AttributeType> opAttributes=targetAttr.getOpAttributes();
           //Check if the attribute is operational, if so check the
           //operation HashSet.
           if(a.isOperational()) {
             if(opAttributes.contains(a))
               ret=true;
         } else {
            if(attributes.contains(a))
              ret=true;
          if(targetAttr.getOperator().equals(EnumTargetOperator.NOT_EQUALITY))
            if(targetAttr.getOperator().equals(EnumTargetOperator.NOT_EQUALITY))
              ret = !ret;
      }
          }
       }
      return ret;
    }
}
opendj-sdk/opends/src/server/org/opends/server/controls/GetEffectiveRights.java
New file
@@ -0,0 +1,245 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.controls;
import org.opends.server.types.*;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.asn1.ASN1Exception;
import org.opends.server.protocols.ldap.LDAPResultCode;
import static org.opends.server.util.ServerConstants.OID_GET_EFFECTIVE_RIGHTS;
import static org.opends.server.util.Validator.ensureNotNull;
import static org.opends.server.util.StaticUtils.toLowerCase;
import org.opends.server.core.DirectoryServer;
import static org.opends.server.messages.ProtocolMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
/**
 * This class partially implements the geteffectiverights control as defined
 * in draft-ietf-ldapext-acl-model-08.txt. The main differences are:
 *
 *  - The response control is not supported. Instead the dseecompat
 *    geteffectiverights control implementation creates attributes containing
 *    right information strings and adds those attributes to the
 *    entry being returned. The attribute type names are dynamically created;
 *    see the dseecompat's AciGetEffectiveRights class for details.
 *
 *  - The dseecompat implementation allows additional attribute types
 *    in the request control for which rights information can be returned.
 *    These are known as the specified attribute types.
 *
 * The dseecompat request control value is the following:
 *
 * <BR>
 * <PRE>
 *  GetRightsControl ::= SEQUENCE {
 *    authzId    authzId
 *    attributes  SEQUENCE OF AttributeType
 *  }
 *
 *   -- Only the "dn:DN form is supported.
 *
 * </PRE>
 *
 **/
public class GetEffectiveRights extends Control {
  //The DN representing the authzId. May be null.
  private DN authzDN=null;
  //The list of additional attribute types to return rights for. May be null.
  private List<AttributeType> attrs=null;
  /**
   *  Create a new geteffectiverights control with null authzDN and null
   *  attribute list.
   */
  public GetEffectiveRights() {
    super(OID_GET_EFFECTIVE_RIGHTS, true, null);
  }
  /**
   * Create a new geteffectiverights control with the specified raw octet
   * string, an authzDN and an attribute list.
   *
   * @param val  The octet string repsentation of the control value.
   *
   * @param authzDN  The authzDN.
   *
   * @param attrs  The list of additional attributes to be returned.
   */
  public GetEffectiveRights(ASN1OctetString val, DN authzDN,
                            List<AttributeType> attrs) {
    super(OID_GET_EFFECTIVE_RIGHTS, true, val);
    this.authzDN=authzDN;
    this.attrs=attrs;
  }
  /**
   * Return the authzDN parsed from the control.
   *
   * @return The DN representing the authzId.
   */
  public DN getAuthzDN () {
    return authzDN;
  }
  /**
   * Return the requested additional attributes parsed from the control. Known
   * as the specified attributes.
   *
   * @return  The list containing any additional attributes to return rights
   *          about.
   */
  public List<AttributeType> getAttributes() {
    return attrs;
  }
  /**
   * Decodes the provided ASN1 element.  Assume that it is a ASN1 sequence
   * of attributetypes.
   *
   * @param attributeElement   The ASN1 element to be decoded.
   *
   * @return  A list of attribute types to process rights of.
   *
   * @throws ASN1Exception If the attribute element cannot be decoded as a
   *                       sequence.
   */
  private static
  List<AttributeType> decodeAttributeSequence(ASN1Element attributeElement)
          throws ASN1Exception {
    List<AttributeType>  attributeList = new LinkedList<AttributeType>();
    //Decode the sequence element and put the individual elements in array.
    ArrayList<ASN1Element> attrElems =
            attributeElement.decodeAsSequence().elements();
    int numAttrElements = attrElems.size();
    for(int i=0; i < numAttrElements; i++) {
      //Decode as an octet string.
      ASN1OctetString tmp=attrElems.get(i).decodeAsOctetString();
      //Get an attribute type for it and add to the list.
      AttributeType attributeType;
      if((attributeType =
              DirectoryServer.getAttributeType(tmp.toString())) == null)
        attributeType =
                DirectoryServer.getDefaultAttributeType(tmp.toString());
      attributeList.add(attributeType);
    }
    return attributeList;
  }
  /**
   * Decodes the request control's value octet string into a GetEffectiveRights
   * class. It assumes that it is an ASN1 sequence as described in class
   * description.
   *
   * @param val The octet string repsentation of the control value.
   *
   * @return  A decoded GetEffectiveRights class representing the request
   *          control.
   *
   * @throws LDAPException   If the request control's value contains errors
   *                         causing a valid GetEffectiveRights class to not
   *                         be created.
   */
  private static
  GetEffectiveRights decodeValueSequence(ASN1OctetString val )
          throws LDAPException {
    DN authzDN;
    List<AttributeType> attrs=null;
    String authzIDString="";
    try {
      ASN1Element sequence = ASN1Element.decode(val.value());
      ArrayList<ASN1Element> elements =
              sequence.decodeAsSequence().elements();
      ASN1OctetString authzID = elements.get(0).decodeAsOctetString();
      //There is an sequence containing an attribute list, try to decode it.
      if(elements.size() == 2)
        attrs=decodeAttributeSequence(elements.get(1));
      authzIDString = authzID.stringValue();
      String lowerAuthzIDString = toLowerCase(authzIDString);
      //Make sure authzId starts with "dn:" and is a valid DN.
      if (lowerAuthzIDString.startsWith("dn:"))
         authzDN = DN.decode(authzIDString.substring(3));
      else {
         int  msgID = MSGID_GETEFFECTIVERIGHTS_INVALID_AUTHZID;
         String message = getMessage(msgID, authzID);
         throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID, message);
      }
    } catch (ASN1Exception e) {
         if (debugEnabled()) {
            debugCaught(DebugLogLevel.ERROR, e);
         }
         int msgID = MSGID_GETEFFECTIVERIGHTS_DECODE_ERROR;
         String message = getMessage(msgID, e.getMessage());
         throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID, message);
    } catch (DirectoryException de) {
        if (debugEnabled()) {
          debugCaught(DebugLogLevel.ERROR, de);
        }
        int msgID  = MSGID_CANNOT_DECODE_GETEFFECTIVERIGHTS_AUTHZID_DN;
        String message =
        getMessage(msgID, authzIDString.substring(3), de.getErrorMessage());
        throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID, message);
    }
    return new GetEffectiveRights(val, authzDN, attrs);
  }
  /**
   * Decodes the request control's value into a GetEffectiveRights class.
   *
   * @param control  The control class representing the request control.
   *
   * @return  A decoded GetEffectiveRights class representing the request
   *          control.
   *
   * @throws LDAPException   If the request control's value contains errors
   *                         causing a valid GetEffectiveRights class to not
   *                         be created.
   */
  public static
  GetEffectiveRights decodeControl(Control control) throws LDAPException {
    ensureNotNull(control);
    ASN1OctetString value=control.getValue();
    //If the value is null create a GetEffectiveRights class with null
    //authzDN and attribute list, else try to decode the value.
    if(value == null)
      return new GetEffectiveRights();
    else
      return GetEffectiveRights.decodeValueSequence(value);
  }
}
opendj-sdk/opends/src/server/org/opends/server/core/DefaultAccessControlProvider.java
@@ -34,7 +34,7 @@
import org.opends.server.types.Operation;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
import org.opends.server.types.Entry;
import org.opends.server.types.*;
/**
 * This class implements a default access control provider for the
@@ -209,5 +209,13 @@
    public boolean isProxiedAuthAllowed(Operation operation, Entry entry) {
     return true;
    }
     /**
     * {@inheritDoc}
     */
    @Override
    public boolean isGetEffectiveRightsAllowed(Operation operation, Control c) {
        return true;
    }
  }
}
opendj-sdk/opends/src/server/org/opends/server/core/SearchOperation.java
@@ -42,12 +42,7 @@
import org.opends.server.api.plugin.PreParsePluginResult;
import org.opends.server.api.plugin.SearchEntryPluginResult;
import org.opends.server.api.plugin.SearchReferencePluginResult;
import org.opends.server.controls.AccountUsableResponseControl;
import org.opends.server.controls.LDAPAssertionRequestControl;
import org.opends.server.controls.MatchedValuesControl;
import org.opends.server.controls.PersistentSearchControl;
import org.opends.server.controls.ProxiedAuthV1Control;
import org.opends.server.controls.ProxiedAuthV2Control;
import org.opends.server.controls.*;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.types.Attribute;
@@ -1922,6 +1917,43 @@
          else if (oid.equals(OID_VIRTUAL_ATTRS_ONLY))
          {
            virtualAttributesOnly = true;
          } else if(oid.equals(OID_GET_EFFECTIVE_RIGHTS)) {
            GetEffectiveRights effectiveRightsControl;
            if (c instanceof GetEffectiveRights)
            {
              effectiveRightsControl = (GetEffectiveRights) c;
            }
            else
            {
              try
              {
                effectiveRightsControl = GetEffectiveRights.decodeControl(c);
              }
              catch (LDAPException le)
              {
                if (debugEnabled())
                {
                  debugCaught(DebugLogLevel.ERROR, le);
                }
                setResultCode(ResultCode.valueOf(le.getResultCode()));
                appendErrorMessage(le.getMessage());
                break searchProcessing;
              }
            }
              if (!AccessControlConfigManager.getInstance()
                   .getAccessControlHandler().
                    isGetEffectiveRightsAllowed(this, effectiveRightsControl)) {
                 setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
                 int msgID =
                        MSGID_SEARCH_EFFECTIVERIGHTS_INSUFFICIENT_ACCESS_RIGHTS;
                 appendErrorMessage(getMessage(msgID, String.valueOf(baseDN)));
                 skipPostOperation = true;
                 break searchProcessing;
               }
          }
          // NYI -- Add support for additional controls.
opendj-sdk/opends/src/server/org/opends/server/messages/AciMessages.java
@@ -731,6 +731,31 @@
   public static final int MSGID_ACI_HANDLER_CANNOT_LOCK_NEW_SUPERIOR_USER =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 72;
  /**
   * The message ID for the message that will be used if a attribute type with
   * a DN syntax failed to DN decode in the selfwrite access checking. This
   * takes one argument, which is the invalid DN string.
   */
  public static final int MSGID_ACI_NOT_VALID_DN =
       CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 73;
    /**
   * The message ID for the message that will be used if a targetattr
   * keyword expression contains both operational and user attribute
   * types. This takes one argument, which is the targetattr expression string.
   */
  public static final int MSGID_ACI_TARGETATTR_INVALID_OP_USER_ATTR =
       CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 74;
      /**
   * The message ID for the message that will be used if a targetattr
   * keyword expression performs both an inequality operation using
   * operational attribute types. This takes one argument, which is the
   * targetattr expression string.
   */
  public static final int MSGID_ACI_TARGATTR_INVALID_OP_ATTR_INEQUALITY =
       CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 75;
    /**
     * Associates a set of generic messages with the message IDs defined in
     * this class.
@@ -1148,5 +1173,21 @@
      registerMessage(MSGID_ACI_HANDLER_CANNOT_LOCK_NEW_SUPERIOR_USER,
          "Unable to obtain a lock on the ModifyDN new superior entry %s");
      registerMessage(MSGID_ACI_NOT_VALID_DN,
          "Selfwrite check skipped because an attribute \"%s\" with a " +
          "distinguished name syntax was not a valid DN");
      registerMessage(MSGID_ACI_TARGETATTR_INVALID_OP_USER_ATTR,
              "The provided Access Control Instruction (ACI) " +
              "targetattr expression value \"%s\" is invalid because" +
              " the expression contains both operational attribute types" +
              " and user attribute types");
      registerMessage(MSGID_ACI_TARGATTR_INVALID_OP_ATTR_INEQUALITY,
              "The provided Access Control Instruction (ACI) " +
              "targetattr expression value \"%s\" is invalid because" +
              " the expression performs an inequality operation using " +
              "operational attribute types");
    }
}
opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -6120,6 +6120,16 @@
       CATEGORY_MASK_CORE | SEVERITY_MASK_INFORMATIONAL | 610;
  /**
   * The message ID for the message that will be used if a search
   * operation cannot be processed due to insufficient access rights caused
   * by an access control check on the geteffectiverights control. This message
   * takes a single argument, the name of the search operation's base entry.
   */
  public static final
  int MSGID_SEARCH_EFFECTIVERIGHTS_INSUFFICIENT_ACCESS_RIGHTS =
    CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 611;
  /**
   * Associates a set of generic messages with the message IDs defined
@@ -8175,8 +8185,8 @@
        "The entry %s cannot be modified due to insufficient access rights");
    registerMessage(MSGID_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS,
        "The entry %s cannot be searched due to insufficient access rights");
    registerMessage(MSGID_SEARCH_EFFECTIVERIGHTS_INSUFFICIENT_ACCESS_RIGHTS,
        "The entry %s cannot be searched due to insufficient access rights");
    registerMessage(MSGID_BIND_OPERATION_INSECURE_SIMPLE_BIND,
                    "Rejecting a simple bind request for user %s because the " +
                    "password policy requires secure authentication");
opendj-sdk/opends/src/server/org/opends/server/messages/ProtocolMessages.java
@@ -4578,6 +4578,36 @@
  /**
  * The message ID for the message that will be used if an error occurs parsing
  * the geteffectiverights authzid because it does not start with the required
  * string "dn:". This takes one argument, which is the authzid string.
  */
 public static final int MSGID_GETEFFECTIVERIGHTS_INVALID_AUTHZID =
      CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 424;
  /**
   * The message ID for the message that will be used if an error occurs while
   * attempting to decode the value of an geteffectiverights request control.
   * This takes a single argument, which is a message explaining the problem
   * that occurred.
   */
  public static final int MSGID_GETEFFECTIVERIGHTS_DECODE_ERROR =
       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_INFORMATIONAL | 425;
  /**
   * The message ID for the message that will be used if an error occurs while
   * trying to decode the geteffectiverights authzid DN string.  This takes two
   * arguments, which are the authzid string and a message explaining the
   * problem that was encountered.
   */
  public static final int MSGID_CANNOT_DECODE_GETEFFECTIVERIGHTS_AUTHZID_DN =
       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 426;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -6540,6 +6570,18 @@
    registerMessage(MSGID_VLVRES_CONTROL_CANNOT_DECODE_VALUE,
                    "Unable to process the provided VLV response control " +
                    "because an error occurred while attempting to decode " +
                    "the control value:  %s");  }
                    "the control value:  %s");
   registerMessage(MSGID_GETEFFECTIVERIGHTS_INVALID_AUTHZID,
                    "The authorization ID \"%s\" contained in the " +
                     "geteffectiverights control is invalid because it does" +
                     " not start with \"dn:\" to indicate a user DN");
    registerMessage(MSGID_GETEFFECTIVERIGHTS_DECODE_ERROR,
            "Cannot decode the provided geteffectiverights " +
             "request control:  %s");
    registerMessage(MSGID_CANNOT_DECODE_GETEFFECTIVERIGHTS_AUTHZID_DN,
                    "Unable to decode authzid DN string \"%s\" as a valid " +
                    "distinguished name:  %s"); }
}
opendj-sdk/opends/src/server/org/opends/server/messages/ToolMessages.java
@@ -7067,6 +7067,31 @@
  public static final int MSGID_LDIFIMPORT_CANNOT_READ_FILE =
       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 887;
/**
  * The message ID for the message that will be used as the description of the
  * geteffectiverights control authzid argument.  This does not take any
  * arguments.
  */
 public static final int MSGID_DESCRIPTION_EFFECTIVERIGHTS_USER =
      CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 888;
 /**
  * The message ID for the message that will be used as the description of the
  * geteffectiverights control specific attribute list argument.  This does
  * not take any arguments.
  */
 public static final int MSGID_DESCRIPTION_EFFECTIVERIGHTS_ATTR =
      CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 889;
 /**
  * The message ID for the message that will be used as the description of the
  * geteffectiverights authzid when it does not start with the required string
  * "dn:". This takes a single argument, which is the authzid string.
  */
 public static final int MSGID_EFFECTIVERIGHTS_INVALID_AUTHZID =
      CATEGORY_MASK_TOOLS | SEVERITY_MASK_MILD_ERROR | 890;
  /**
@@ -9294,6 +9319,19 @@
    registerMessage(MSGID_LDIFIMPORT_CANNOT_READ_FILE,
                    "The specified LDIF file %s cannot be read");
   registerMessage(MSGID_DESCRIPTION_EFFECTIVERIGHTS_USER,
                    "Use geteffectiverights control with the provided " +
                    "authzid");
    registerMessage(MSGID_DESCRIPTION_EFFECTIVERIGHTS_ATTR,
                    "Specifies geteffectiverights control specific " +
                    "attribute list");
   registerMessage(MSGID_EFFECTIVERIGHTS_INVALID_AUTHZID,
                    "The authorization ID \"%s\" contained in the " +
                     "geteffectiverights control is invalid because it does" +
                     " not start with \"dn:\" to indicate a user DN");
  }
}
opendj-sdk/opends/src/server/org/opends/server/tools/LDAPSearch.java
@@ -71,10 +71,7 @@
import org.opends.server.protocols.ldap.SearchResultDoneProtocolOp;
import org.opends.server.protocols.ldap.SearchResultEntryProtocolOp;
import org.opends.server.protocols.ldap.SearchResultReferenceProtocolOp;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.LDAPException;
import org.opends.server.types.NullOutputStream;
import org.opends.server.types.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.messages.MessageHandler.*;
@@ -671,6 +668,8 @@
    StringArgument    trustStorePath           = null;
    StringArgument    trustStorePassword       = null;
    StringArgument    vlvDescriptor            = null;
    StringArgument    effectiveRightsUser      = null;
    StringArgument    effectiveRightsAttrs     = null;
    // Create the command-line argument parser for use with this program.
@@ -872,6 +871,21 @@
                    "{controloid[:criticality[:value|::b64value|:<fileurl]]}",
                    null, null, MSGID_DESCRIPTION_CONTROLS);
      argParser.addArgument(controlStr);
      effectiveRightsUser =
              new StringArgument("effectiveRightsUser",
                      OPTION_SHORT_EFFECTIVERIGHTSUSER,
                      OPTION_LONG_EFFECTIVERIGHTSUSER, false, false, true,
                      "{authzid}", null, null,
                      MSGID_DESCRIPTION_EFFECTIVERIGHTS_USER );
      argParser.addArgument(effectiveRightsUser);
      effectiveRightsAttrs =
              new StringArgument("effectiveRightsAttrs",
                      OPTION_SHORT_EFFECTIVERIGHTSATTR,
                      OPTION_LONG_EFFECTIVERIGHTSATTR, false, true, true,
                      "{attribute}", null, null,
                      MSGID_DESCRIPTION_EFFECTIVERIGHTS_ATTR );
      argParser.addArgument(effectiveRightsAttrs);
      version = new IntegerArgument("version", 'V', "version", false, false,
                                    true, "{version}", 3, null,
@@ -1157,6 +1171,34 @@
      }
    }
    if(effectiveRightsUser.isPresent()) {
      String authzID=effectiveRightsUser.getValue();
      if (!authzID.startsWith("dn:")) {
        int  msgID   = MSGID_EFFECTIVERIGHTS_INVALID_AUTHZID;
        String message = getMessage(msgID, authzID);
        err.println(wrapText(message, MAX_LINE_WIDTH));
        err.println(argParser.getUsage());
        return 1;
      }
      ASN1OctetString v=null;
      ASN1OctetString effectiveRightsUserVal =
              new ASN1OctetString(authzID);
      ASN1Sequence sequence=null;
      ArrayList<ASN1Element> attrElements =
              new ArrayList<ASN1Element>();
      for(String a : effectiveRightsAttrs.getValues())
        attrElements.add(new ASN1OctetString(a));
      ASN1Sequence attrSeq=new ASN1Sequence(attrElements);
      ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(2);
      elements.add(effectiveRightsUserVal);
      elements.add(attrSeq);
      sequence= new ASN1Sequence(elements);
      LDAPControl effectiveRightsControl =
              new LDAPControl(OID_GET_EFFECTIVE_RIGHTS, false,
                      new ASN1OctetString(sequence.encode()));
      searchOptions.getControls().add(effectiveRightsControl);
    }
    if (proxyAuthzID.isPresent())
    {
      ASN1OctetString proxyValue = new ASN1OctetString(proxyAuthzID.getValue());
opendj-sdk/opends/src/server/org/opends/server/tools/LDAPToolUtils.java
@@ -121,6 +121,11 @@
    {
      controlOID = OID_VIRTUAL_ATTRS_ONLY;
    }
    else if(lowerOID.equals("effectiverights") ||
              lowerOID.equals("geteffectiverights"))
    {
      controlOID = OID_GET_EFFECTIVE_RIGHTS;
    }
    if (idx < 0)
    {
opendj-sdk/opends/src/server/org/opends/server/tools/ToolConstants.java
@@ -457,5 +457,28 @@
   * displayed in usage information.
   */
  public static final String OPTION_VALUE_SASLOPTION = "{name=value}";
  /**
   * The value for the short option geteffectiverights control authzid.
   */
   public static final char OPTION_SHORT_EFFECTIVERIGHTSUSER = 'g';
  /**
   * The value for the long option geteffectiverights  control authzid.
   */
   public static final String OPTION_LONG_EFFECTIVERIGHTSUSER =
          "getEffectiveRightsAuthzid";
  /**
   * The value for the short option geteffectiveights control attributes.
   */
   public static final char OPTION_SHORT_EFFECTIVERIGHTSATTR = 'e';
  /**
   * The value for the long option geteffectiverights control specific
   * attribute list.
   */
   public static final String OPTION_LONG_EFFECTIVERIGHTSATTR =
          "getEffectiveRightsAttribute";
}
opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
@@ -1786,6 +1786,12 @@
  public static final String OID_PROXIED_AUTH_V2 = "2.16.840.1.113730.3.4.18";
   /**
   * The OID for the get effective rights control.
   */
  public static final String OID_GET_EFFECTIVE_RIGHTS =
                                                    "1.3.6.1.4.1.42.2.27.9.5.2";
  /**
   * The OID for the real attributes only control.
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/GetEffectiveRightsTestCase.java
New file
@@ -0,0 +1,667 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.testng.annotations.BeforeMethod;
import static org.opends.server.config.ConfigConstants.*;
import org.testng.Assert;
import static org.testng.Assert.assertEquals;
import org.opends.server.TestCaseUtils;
import org.opends.server.types.AttributeType;
import org.opends.server.types.Attribute;
import org.opends.server.tools.LDAPSearch;
import org.opends.server.tools.LDAPModify;
import static org.opends.server.util.ServerConstants.OID_GET_EFFECTIVE_RIGHTS;
import static org.opends.server.util.ServerConstants.EOL;
import java.io.*;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
public class GetEffectiveRightsTestCase {
  private static ByteArrayOutputStream oStream = new ByteArrayOutputStream();
  private static final String DIR_MGR_DN = "cn=Directory Manager";
  private static final String PWD = "password";
  private static final String filter = "(objectclass=*)";
  private static final String base="uid=user.3,ou=People,o=test";
  private static final String user1="uid=user.1,ou=People,o=test";
  private static final String superUser="uid=superuser,ou=admins,o=test";
  private static final String[] attrList={"pager", "fax"};
  private static final String[] memberAttrList={"member"};
  private static final String entryLevel = "aclRights;entryLevel";
  private static final String attributeLevel = "aclRights;attributeLevel;";
  //Various results for entryLevel searches.
  private static final
  String bypassRights = "add:1,delete:1,read:1,write:1,proxy:1";
  private static final
  String rRights = "add:0,delete:0,read:1,write:0,proxy:0";
  private static final
  String arRights = "add:1,delete:0,read:1,write:0,proxy:0";
  private static final
  String adrRights = "add:1,delete:1,read:1,write:0,proxy:0";
  private static final
  String adrwRights = "add:1,delete:1,read:1,write:1,proxy:0";
  private static final
  String allRights = "add:1,delete:1,read:1,write:1,proxy:1";
  private static final
  String ACCESS_HANDLER_DN = "cn=Access Control Handler,cn=config";
  //Results for attributeLevel searches
  private static final String srwMailAttrRights =
          "search:1,read:1,compare:0,write:1," +
          "selfwrite_add:1,selfwrite_delete:1,proxy:0";
  private static final String srDescrptionAttrRights =
          "search:1,read:1,compare:0,write:0," +
          "selfwrite_add:0,selfwrite_delete:0,proxy:0";
  private static final String srxFaxAttrRights =
          "search:1,read:1,compare:0,write:?," +
          "selfwrite_add:0,selfwrite_delete:0,proxy:0";
  private static final String srPagerAttrRights =
          "search:1,read:1,compare:0,write:0," +
          "selfwrite_add:0,selfwrite_delete:0,proxy:0";
 private static final String selfWriteAttrRights =
          "search:0,read:0,compare:0,write:0," +
          "selfwrite_add:1,selfwrite_delete:1,proxy:0";
  //ACI needed to search/read aciRights attribute.
  private static final
  String aclRightsAci = "(targetattr=\"aclRights\")" +
          "(version 3.0;acl \"aclRights access\";" +
          "allow (search, read) " +
          "userdn=\"ldap:///uid=superuser,ou=admins,o=test\";)";
  //General ACI superuser to search/read.
  private static final
  String readSearchAci = "(targetattr=\"*\")" +
          "(version 3.0;acl \"read/search access\";" +
          "allow (search, read) " +
          "userdn=\"ldap:///uid=superuser,ou=admins,o=test\";)";
  //General ACI for anonymous test.
  private static final
  String readSearchAnonAci = "(targetattr=\"*\")" +
          "(version 3.0;acl \"anonymous read/search access\";" +
          "allow (search, read) " +
          "userdn=\"ldap:///anyone\";)";
  //Test ACIs.
  private static final
  String addAci = "(version 3.0;acl \"add access\";" +
          "allow (add) " +
          "userdn=\"ldap:///uid=superuser,ou=admins,o=test\";)";
  private static final
  String delAci = "(version 3.0;acl \"delete access\";" +
          "allow (delete) " +
          "userdn=\"ldap:///uid=superuser,ou=admins,o=test\";)";
  private static final
  String writeAci = "(version 3.0;acl \"write access\";" +
          "allow (write) " +
          "userdn=\"ldap:///uid=superuser,ou=admins,o=test\";)";
  private static final
  String writeMailAci =  "(targetattr=\"mail\")" +
          "(version 3.0;acl \"write mail access\";" +
          "allow (write) " +
          "userdn=\"ldap:///uid=superuser,ou=admins,o=test\";)";
  private static final
  String proxyAci = "(version 3.0;acl \"proxy access\";" +
          "allow  (proxy) " +
          "userdn=\"ldap:///uid=superuser,ou=admins,o=test\";)";
  private static final
  String faxTargAttrAci =
          "(targattrfilters=\"add=fax:(fax=*), del=fax:(fax=*)\")" +
          "(version 3.0;acl \"allow write fax\";" +
          "allow (write)" +
          "userdn=\"ldap:///uid=superuser,ou=admins,o=test\";)";
  private static final
  String pagerTargAttrAci =
          "(targattrfilters=\"add=pager:(pager=*), del=pager:(pager=*)\")" +
          "(version 3.0;acl \"deny write pager\";" +
          "deny (write)" +
          "userdn=\"ldap:///uid=superuser,ou=admins,o=test\";)";
  private static final
  String selfWriteAci = "(targetattr=\"member\")" +
          "(version 3.0; acl \"selfwrite\"; allow(selfwrite)" +  "" +
          "userdn=\"ldap:///uid=user.1,ou=People,o=test\";)";
  @BeforeClass
  public void setupClass() throws Exception {
    TestCaseUtils.startServer();
    deleteAttrFromEntry(ACCESS_HANDLER_DN, ATTR_AUTHZ_GLOBAL_ACI);
    addEntries();
  }
    @BeforeMethod
    public void removeAcis() throws Exception {
        deleteAttrFromEntry("ou=People,o=test", "aci");
  }
  /**
   * Test entry level using the -g param and anonymous dn as the authzid.
   * @throws Exception If the search result is empty or a right string
   * doesn't match the expected value.
   */
  @Test()
  public void testAnonEntryLevelParams() throws Exception {
    String aciLdif=makeAddAciLdif("aci", "ou=People,o=test", readSearchAnonAci);
    modEntries(aciLdif, DIR_MGR_DN, PWD);
    String userResults =
            LDAPSearchParams(DIR_MGR_DN, PWD, null, "dn:", null,
                    base, filter, "aclRights");
    Assert.assertFalse(userResults.equals(""));
    HashMap<String, String> attrMap=getAttrMap(userResults);
    checkEntryLevel(attrMap, rRights);
  }
  /**
   * Test entry level using the -g param and superuser dn as the authzid.
   * @throws Exception If the search result is empty or a right string
   * doesn't match the expected value.
   */
  @Test()
  public void testSuEntryLevelParams() throws Exception {
    String aciLdif=makeAddAciLdif("aci", "ou=People,o=test", aclRightsAci);
    modEntries(aciLdif, DIR_MGR_DN, PWD);
    aciLdif=makeAddAciLdif("aci", "ou=People,o=test", readSearchAci);
    modEntries(aciLdif, DIR_MGR_DN, PWD);
    String userResults =
            LDAPSearchParams(superUser, PWD, null, "dn: " + superUser, null,
                    base, filter, "aclRights");
    Assert.assertFalse(userResults.equals(""));
    HashMap<String, String> attrMap=getAttrMap(userResults);
    checkEntryLevel(attrMap, rRights);
    aciLdif=makeAddAciLdif("aci", "ou=People,o=test", addAci);
    modEntries(aciLdif, DIR_MGR_DN, PWD);
    userResults =
            LDAPSearchParams(superUser, PWD, null, "dn: " + superUser, null,
                    base, filter, "aclRights");
    Assert.assertFalse(userResults.equals(""));
    attrMap=getAttrMap(userResults);
    checkEntryLevel(attrMap, arRights);
    aciLdif=makeAddAciLdif("aci", "ou=People,o=test", delAci);
    modEntries(aciLdif, DIR_MGR_DN, PWD);
    userResults =
            LDAPSearchParams(superUser, PWD, null, "dn: " + superUser, null,
                    base, filter, "aclRights");
    Assert.assertFalse(userResults.equals(""));
    attrMap=getAttrMap(userResults);
    checkEntryLevel(attrMap, adrRights);
    aciLdif=makeAddAciLdif("aci", "ou=People,o=test", writeAci);
    modEntries(aciLdif, DIR_MGR_DN, PWD);
    userResults =
            LDAPSearchParams(superUser, PWD, null, "dn: " + superUser, null,
                    base, filter, "aclRights");
    Assert.assertFalse(userResults.equals(""));
    attrMap=getAttrMap(userResults);
    checkEntryLevel(attrMap, adrwRights);
    aciLdif=makeAddAciLdif("aci", "ou=People,o=test", proxyAci);
    modEntries(aciLdif, DIR_MGR_DN, PWD);
    userResults =
            LDAPSearchParams(superUser, PWD, null, "dn: " + superUser, null,
                    base, filter, "aclRights");
    Assert.assertFalse(userResults.equals(""));
    attrMap=getAttrMap(userResults);
    checkEntryLevel(attrMap, allRights);
  }
   /**
   * Test entry level using the control OID only (no authzid specified).
   * Should use the bound user (superuser) as the authzid.
   * @throws Exception If the search result is empty or a right string
   * doesn't match the expected value.
   */
  @Test()
   public void testSuEntryLevelCtrl() throws Exception {
     String aciLdif=makeAddAciLdif("aci", "ou=People,o=test", aclRightsAci);
     modEntries(aciLdif, DIR_MGR_DN, PWD);
     aciLdif=makeAddAciLdif("aci", "ou=People,o=test", readSearchAci);
     modEntries(aciLdif, DIR_MGR_DN, PWD);
     String userResults =
            LDAPSearchCtrl(superUser, PWD, null, OID_GET_EFFECTIVE_RIGHTS,
                    base, filter, "aclRights");
     Assert.assertFalse(userResults.equals(""));
     HashMap<String, String> attrMap=getAttrMap(userResults);
     checkEntryLevel(attrMap, rRights);
     aciLdif=makeAddAciLdif("aci", "ou=People,o=test", addAci);
     modEntries(aciLdif, DIR_MGR_DN, PWD);
     userResults =
            LDAPSearchCtrl(superUser, PWD, null, OID_GET_EFFECTIVE_RIGHTS,
                    base, filter, "aclRights");
     Assert.assertFalse(userResults.equals(""));
     attrMap=getAttrMap(userResults);
     checkEntryLevel(attrMap, arRights);
     aciLdif=makeAddAciLdif("aci", "ou=People,o=test", delAci);
     modEntries(aciLdif, DIR_MGR_DN, PWD);
     userResults =
            LDAPSearchCtrl(superUser, PWD, null, OID_GET_EFFECTIVE_RIGHTS,
                    base, filter, "aclRights");
     Assert.assertFalse(userResults.equals(""));
     attrMap=getAttrMap(userResults);
     checkEntryLevel(attrMap, adrRights);
     aciLdif=makeAddAciLdif("aci", "ou=People,o=test", writeAci);
     modEntries(aciLdif, DIR_MGR_DN, PWD);
     userResults =
            LDAPSearchCtrl(superUser, PWD, null, OID_GET_EFFECTIVE_RIGHTS,
                    base, filter, "aclRights");
     Assert.assertFalse(userResults.equals(""));
     attrMap=getAttrMap(userResults);
     checkEntryLevel(attrMap, adrwRights);
     aciLdif=makeAddAciLdif("aci", "ou=People,o=test", proxyAci);
     modEntries(aciLdif, DIR_MGR_DN, PWD);
     userResults =
             LDAPSearchCtrl(superUser, PWD, null, OID_GET_EFFECTIVE_RIGHTS,
                     base, filter, "aclRights");
     Assert.assertFalse(userResults.equals(""));
     attrMap=getAttrMap(userResults);
     checkEntryLevel(attrMap, allRights);
   }
  /**
  * Test entry level using the control OID only -- bound as a bypass user.
  * Should use the bound user (DIR_MGR) as the authzid.
  * @throws Exception If the search result is empty or a right string
  * doesn't match the expected value.
  */
 @Test()
  public void testBypassEntryLevelCtrl() throws Exception {
    String userResults =
           LDAPSearchCtrl(DIR_MGR_DN, PWD, null, OID_GET_EFFECTIVE_RIGHTS,
                   base, filter, "aclRights");
    Assert.assertFalse(userResults.equals(""));
    HashMap<String, String> attrMap=getAttrMap(userResults);
    checkEntryLevel(attrMap, bypassRights);
  }
    /**
   * Test attribute level using the -g param and superuser dn as the authzid.
   * The attributes used are mail and description. Mail should show write
   * access allowed, description should show write access not allowed.
   * @throws Exception If the search result is empty or a right string
   * doesn't match the expected value.
   */
  @Test()
  public void testSuAttrLevelParams() throws Exception {
    String aciLdif=makeAddAciLdif("aci", "ou=People,o=test", aclRightsAci);
    modEntries(aciLdif, DIR_MGR_DN, PWD);
    aciLdif=makeAddAciLdif("aci", "ou=People,o=test", readSearchAci);
    modEntries(aciLdif, DIR_MGR_DN, PWD);
    aciLdif=makeAddAciLdif("aci", "ou=People,o=test", writeMailAci);
    modEntries(aciLdif, DIR_MGR_DN, PWD);
    String userResults =
            LDAPSearchParams(superUser, PWD, null, "dn: " + superUser, null,
                    base, filter, "aclRights mail description");
    Assert.assertFalse(userResults.equals(""));
    HashMap<String, String> attrMap=getAttrMap(userResults);
    checkAttributeLevel(attrMap, "mail", srwMailAttrRights);
    checkAttributeLevel(attrMap, "description", srDescrptionAttrRights);
  }
 /**
 * Test attribute level using the -g param and superuser dn as the authzid and
 * the -e option using pager and fax.
 * The attributes used are mail and description. Mail should show write
 * access allowed, description should show write access not allowed.
 *
 * @throws Exception If the search result is empty or a right string
 * doesn't match the expected value.
 */
@Test()
public void testSuAttrLevelParams2() throws Exception {
  String aciLdif=makeAddAciLdif("aci", "ou=People,o=test", aclRightsAci);
  modEntries(aciLdif, DIR_MGR_DN, PWD);
  aciLdif=makeAddAciLdif("aci", "ou=People,o=test", readSearchAci);
  modEntries(aciLdif, DIR_MGR_DN, PWD);
  aciLdif=makeAddAciLdif("aci", "ou=People,o=test", writeMailAci);
  modEntries(aciLdif, DIR_MGR_DN, PWD);
  aciLdif=makeAddAciLdif("aci", "ou=People,o=test", faxTargAttrAci);
  modEntries(aciLdif, DIR_MGR_DN, PWD);
  aciLdif=makeAddAciLdif("aci", "ou=People,o=test", pagerTargAttrAci);
  modEntries(aciLdif, DIR_MGR_DN, PWD);
  String userResults =
          LDAPSearchParams(superUser, PWD, null, "dn: " + superUser, attrList,
                  base, filter, "aclRights mail description");
  Assert.assertFalse(userResults.equals(""));
  HashMap<String, String> attrMap=getAttrMap(userResults);
  checkAttributeLevel(attrMap, "mail", srwMailAttrRights);
  checkAttributeLevel(attrMap, "description", srDescrptionAttrRights);
  checkAttributeLevel(attrMap, "fax", srxFaxAttrRights);
  checkAttributeLevel(attrMap, "pager", srPagerAttrRights);
}
 /**
 * Test selfwrite attribute level using the -g param and user.1 dn as the
 * authzid and the -e option member.
 * The attributes used are mail and description. Mail should show write
 * access allowed, description should show write access not allowed.
 *
 * @throws Exception If the search result is empty or a right string
 * doesn't match the expected value.
 */
@Test()
public void testSuAttrLevelParams3() throws Exception {
  String aciLdif=makeAddAciLdif("aci", "ou=People,o=test", aclRightsAci);
  modEntries(aciLdif, DIR_MGR_DN, PWD);
  aciLdif=makeAddAciLdif("aci", "ou=People,o=test", readSearchAci);
  modEntries(aciLdif, DIR_MGR_DN, PWD);
  aciLdif=makeAddAciLdif("aci", "ou=People,o=test", selfWriteAci);
  modEntries(aciLdif, DIR_MGR_DN, PWD);
  String userResults =
          LDAPSearchParams(superUser, PWD, null, "dn: " + user1, memberAttrList,
                  base, filter, "aclRights");
  Assert.assertFalse(userResults.equals(""));
  HashMap<String, String> attrMap=getAttrMap(userResults);
  checkAttributeLevel(attrMap, "member", selfWriteAttrRights);
}
 private void
 checkAttributeLevel(HashMap<String, String> attrMap, String attr,
                     String reqRightsStr) throws Exception {
   String attrType=attributeLevel + attr;
   String retRightsStr=attrMap.get(attrType);
   Assert.assertTrue(retRightsStr.equals(reqRightsStr));
 }
 private void
 checkEntryLevel(HashMap<String, String> attrMap, String reqRightsStr)
 throws Exception {
    String retRightsStr=attrMap.get(entryLevel);
    Assert.assertTrue(retRightsStr.equals(reqRightsStr));
 }
 private HashMap<String, String>
 getAttrMap(String resultString) throws Exception {
       StringReader r=new StringReader(resultString);
    BufferedReader br=new BufferedReader(r);
    HashMap<String, String> attrMap = new HashMap<String,String>();
    try {
      while(true) {
        String s = br.readLine();
        if(s == null)
          break;
        if(s.startsWith("dn:"))
          continue;
        String[] a=s.split(": ");
        if(a.length != 2)
          break;
        attrMap.put(a[0],a[1]);
      }
    } catch (IOException e) {
      Assert.assertEquals(0, 1,  e.getMessage());
    }
   return attrMap;
 }
  private void addEntries() throws Exception {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(
            "dn: ou=People,o=test",
            "objectClass: top",
            "objectClass: organizationalUnit",
            "ou: People",
            "",
            "dn: ou=admins,o=test",
            "objectClass: top",
            "objectClass: organizationalUnit",
            "ou: admins",
            "",
            "dn: cn=group,ou=People,o=test",
            "objectclass: top",
            "objectclass: groupOfNames",
            "cn: group",
            "member: uid=user.1,ou=People,o=test",
            "",
            "dn: uid=superuser,ou=admins,o=test",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalPerson",
            "objectClass: inetOrgPerson",
            "uid: superuser",
            "givenName: superuser",
            "sn: 1",
            "cn: User 1",
            "userPassword: password",
            "ds-privilege-name: proxied-auth",
            "",
            "dn: uid=proxyuser,ou=admins,o=test",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalPerson",
            "objectClass: inetOrgPerson",
            "uid: proxyuser",
            "givenName: proxyuser",
            "sn: 1",
            "cn: User 1",
            "userPassword: password",
            "",
            "dn: uid=user.1,ou=People,o=test",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalPerson",
            "objectClass: inetOrgPerson",
            "uid: user.1",
            "givenName: User",
            "sn: 1",
            "cn: User 1",
            "userPassword: password",
            "",
            "dn: uid=user.2,ou=People,o=test",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalPerson",
            "objectClass: inetOrgPerson",
            "uid: user.2",
            "givenName: User",
            "sn: 2",
            "cn: User 2",
            "userPassword: password",
            "",
            "dn: uid=user.3,ou=People,o=test",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalPerson",
            "objectClass: inetOrgPerson",
            "uid: user.3",
            "givenName: User",
            "sn: 3",
            "mail: user.3@test",
            "description: user.3 description",
            "cn: User 3",
            "userPassword: password");
  }
  private String LDAPSearchCtrl(String bindDn, String bindPassword,
                            String proxyDN, String controlStr,
                            String base, String filter, String attr)
          throws Exception {
    ArrayList<String> argList=new ArrayList<String>(20);
    argList.add("-h");
    argList.add("127.0.0.1");
    argList.add("-p");
    argList.add(String.valueOf(TestCaseUtils.getServerLdapPort()));
    argList.add("-D");
    argList.add(bindDn);
    argList.add("-w");
    argList.add(bindPassword);
    argList.add("-T");
    if(proxyDN != null) {
      argList.add("-Y");
      argList.add("dn:" + proxyDN);
    }
    if(controlStr != null) {
      argList.add("-J");
      argList.add(controlStr);
    }
    argList.add("-b");
    argList.add(base);
    argList.add("-s");
    argList.add("sub");
    argList.add(filter);
    String[] attrs=attr.split("\\s+");
    for(String a : attrs)
     argList.add(a);
    String[] args = new String[argList.size()];
    oStream.reset();
    int retVal =
            LDAPSearch.mainSearch(argList.toArray(args), false, oStream, oStream);
    Assert.assertEquals(0, retVal,  "Returned error: " + oStream.toString());
    return oStream.toString();
  }
  private String LDAPSearchParams(String bindDn, String bindPassword,
                            String proxyDN, String authzid, String[] attrList,
                            String base, String filter ,String attr)
          throws Exception {
    ArrayList<String> argList=new ArrayList<String>(20);
    argList.add("-h");
    argList.add("127.0.0.1");
    argList.add("-p");
    argList.add(String.valueOf(TestCaseUtils.getServerLdapPort()));
    argList.add("-D");
    argList.add(bindDn);
    argList.add("-w");
    argList.add(bindPassword);
    argList.add("-T");
    if(proxyDN != null) {
      argList.add("-Y");
      argList.add("dn:" + proxyDN);
    }
    if(authzid != null) {
      argList.add("-g");
      argList.add(authzid);
    }
    if(attrList != null) {
      for(String a : attrList) {
        argList.add("-e");
        argList.add(a);
      }
    }
    argList.add("-b");
    argList.add(base);
    argList.add("-s");
    argList.add("sub");
    argList.add(filter);
    String[] attrs=attr.split("\\s+");
    for(String a : attrs)
     argList.add(a);
    String[] args = new String[argList.size()];
    oStream.reset();
    int retVal =
         LDAPSearch.mainSearch(argList.toArray(args), false, oStream, oStream);
    Assert.assertEquals(0, retVal, "Returned error: " + oStream.toString());
    return oStream.toString();
  }
  private void modEntries(String ldif, String bindDn, String bindPassword)
          throws Exception {
    File tempFile = getTemporaryLdifFile();
    TestCaseUtils.writeFile(tempFile, ldif);
    ArrayList<String> argList=new ArrayList<String>(20);
    argList.add("-h");
    argList.add("127.0.0.1");
    argList.add("-p");
    argList.add(String.valueOf(TestCaseUtils.getServerLdapPort()));
    argList.add("-D");
    argList.add(bindDn);
    argList.add("-w");
    argList.add(bindPassword);
    argList.add("-f");
    argList.add(tempFile.getAbsolutePath());
    String[] args = new String[argList.size()];
    ldapModify(argList.toArray(args));
  }
  private void ldapModify(String[] args) {
    oStream.reset();
    LDAPModify.mainModify(args, false, oStream, oStream);
  }
  private void deleteAttrFromEntry(String dn, String attr)
  throws Exception {
    StringBuilder ldif = new StringBuilder();
    ldif.append(TestCaseUtils.makeLdif(
            "dn: "  + dn,
            "changetype: modify",
            "delete: " + attr));
    modEntries(ldif.toString(), DIR_MGR_DN, PWD);
  }
  private static ThreadLocal<Map<String,File>> tempLdifFile =
          new ThreadLocal<Map<String,File>>();
  private File getTemporaryLdifFile() throws IOException {
    Map<String,File> tempFilesForThisThread = tempLdifFile.get();
    if (tempFilesForThisThread == null) {
      tempFilesForThisThread = new HashMap<String,File>();
      tempLdifFile.set(tempFilesForThisThread);
    }
    File tempFile = tempFilesForThisThread.get("effectiverights-tests");
    if (tempFile == null) {
      tempFile = File.createTempFile("effectiverights-tests", ".ldif");
      tempFile.deleteOnExit();
      tempFilesForThisThread.put("effectiverights-tests", tempFile);
    }
    return tempFile;
  }
  private static String makeAddAciLdif(String attr, String dn, String... acis) {
    StringBuilder ldif = new StringBuilder();
    ldif.append("dn: ").append(dn).append(EOL);
    ldif.append("changetype: modify").append(EOL);
    ldif.append("add: ").append(attr).append(EOL);
    for(String aci : acis)
      ldif.append(attr).append(":").append(aci).append(EOL);
    ldif.append(EOL);
    return ldif.toString();
  }
}