From 2d0aba220afc66dcb50fcd2639df306a25f639ea Mon Sep 17 00:00:00 2001
From: dugan <dugan@localhost>
Date: Wed, 02 May 2007 02:02:04 +0000
Subject: [PATCH] Add ACI support for Get Effective Rights control. Issue #87.

---
 opends/src/server/org/opends/server/core/DefaultAccessControlProvider.java                                           |   10 
 opends/src/server/org/opends/server/authorization/dseecompat/TargAttrFilters.java                                    |   10 
 opends/src/server/org/opends/server/authorization/dseecompat/EnumEvalReason.java                                     |   74 +
 opends/src/server/org/opends/server/controls/GetEffectiveRights.java                                                 |  245 ++++
 opends/src/server/org/opends/server/core/SearchOperation.java                                                        |   44 
 opends/src/server/org/opends/server/authorization/dseecompat/TargetAttr.java                                         |   92 +
 opends/src/server/org/opends/server/messages/AciMessages.java                                                        |   41 
 opends/src/server/org/opends/server/messages/CoreMessages.java                                                       |   14 
 opends/src/server/org/opends/server/util/ServerConstants.java                                                        |    6 
 opends/src/server/org/opends/server/authorization/dseecompat/AciTargets.java                                         |    5 
 opends/src/server/org/opends/server/authorization/dseecompat/AciEvalContext.java                                     |  158 +++
 opends/src/server/org/opends/server/authorization/dseecompat/AciBody.java                                            |   10 
 opends/src/server/org/opends/server/authorization/dseecompat/AciEffectiveRights.java                                 |  735 ++++++++++++++
 opends/src/server/org/opends/server/authorization/dseecompat/Aci.java                                                |   32 
 opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/GetEffectiveRightsTestCase.java |  667 +++++++++++++
 opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java                                       |  538 ++++++++--
 opends/src/server/org/opends/server/tools/ToolConstants.java                                                         |   23 
 opends/src/server/org/opends/server/api/AccessControlHandler.java                                                    |   25 
 opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java                                         |  125 ++
 opends/src/server/org/opends/server/tools/LDAPToolUtils.java                                                         |    5 
 opends/src/server/org/opends/server/tools/LDAPSearch.java                                                            |   50 
 opends/src/server/org/opends/server/authorization/dseecompat/AciTargetMatchContext.java                              |   22 
 opends/src/server/org/opends/server/messages/ToolMessages.java                                                       |   38 
 opends/src/server/org/opends/server/messages/ProtocolMessages.java                                                   |   44 
 opends/resource/schema/00-core.ldif                                                                                  |   10 
 25 files changed, 2,825 insertions(+), 198 deletions(-)

diff --git a/opends/resource/schema/00-core.ldif b/opends/resource/schema/00-core.ldif
index cbe3be8..52d3c4d 100644
--- a/opends/resource/schema/00-core.ldif
+++ b/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
diff --git a/opends/src/server/org/opends/server/api/AccessControlHandler.java b/opends/src/server/org/opends/server/api/AccessControlHandler.java
index 71579e5..0639d14 100644
--- a/opends/src/server/org/opends/server/api/AccessControlHandler.java
+++ b/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);
 }
 
diff --git a/opends/src/server/org/opends/server/authorization/dseecompat/Aci.java b/opends/src/server/org/opends/server/authorization/dseecompat/Aci.java
index 2d6869a..b56d87b 100644
--- a/opends/src/server/org/opends/server/authorization/dseecompat/Aci.java
+++ b/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();
+    }
 }
diff --git a/opends/src/server/org/opends/server/authorization/dseecompat/AciBody.java b/opends/src/server/org/opends/server/authorization/dseecompat/AciBody.java
index db2b2b1..2300504 100644
--- a/opends/src/server/org/opends/server/authorization/dseecompat/AciBody.java
+++ b/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;
+    }
 }
diff --git a/opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java b/opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java
index 2602242..495212b 100644
--- a/opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java
+++ b/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;
+  }
 }
diff --git a/opends/src/server/org/opends/server/authorization/dseecompat/AciEffectiveRights.java b/opends/src/server/org/opends/server/authorization/dseecompat/AciEffectiveRights.java
new file mode 100644
index 0000000..2ef9ad7
--- /dev/null
+++ b/opends/src/server/org/opends/server/authorization/dseecompat/AciEffectiveRights.java
@@ -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;
+  }
+}
diff --git a/opends/src/server/org/opends/server/authorization/dseecompat/AciEvalContext.java b/opends/src/server/org/opends/server/authorization/dseecompat/AciEvalContext.java
index 3d94604..401e79e 100644
--- a/opends/src/server/org/opends/server/authorization/dseecompat/AciEvalContext.java
+++ b/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();
 }
diff --git a/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java b/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java
index 2d15fbb..86a5462 100644
--- a/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java
+++ b/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.
 
    /**
diff --git a/opends/src/server/org/opends/server/authorization/dseecompat/AciTargetMatchContext.java b/opends/src/server/org/opends/server/authorization/dseecompat/AciTargetMatchContext.java
index 7fdb608..3ccb74b 100644
--- a/opends/src/server/org/opends/server/authorization/dseecompat/AciTargetMatchContext.java
+++ b/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();
 }
 
 
diff --git a/opends/src/server/org/opends/server/authorization/dseecompat/AciTargets.java b/opends/src/server/org/opends/server/authorization/dseecompat/AciTargets.java
index 0fd4984..8fd9d7b 100644
--- a/opends/src/server/org/opends/server/authorization/dseecompat/AciTargets.java
+++ b/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;
diff --git a/opends/src/server/org/opends/server/authorization/dseecompat/EnumEvalReason.java b/opends/src/server/org/opends/server/authorization/dseecompat/EnumEvalReason.java
new file mode 100644
index 0000000..3b9e359
--- /dev/null
+++ b/opends/src/server/org/opends/server/authorization/dseecompat/EnumEvalReason.java
@@ -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) {}
+}
diff --git a/opends/src/server/org/opends/server/authorization/dseecompat/TargAttrFilters.java b/opends/src/server/org/opends/server/authorization/dseecompat/TargAttrFilters.java
index 256c883..fd001d7 100644
--- a/opends/src/server/org/opends/server/authorization/dseecompat/TargAttrFilters.java
+++ b/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;
         }
diff --git a/opends/src/server/org/opends/server/authorization/dseecompat/TargetAttr.java b/opends/src/server/org/opends/server/authorization/dseecompat/TargetAttr.java
index 57bfe49..9b2afff 100644
--- a/opends/src/server/org/opends/server/authorization/dseecompat/TargetAttr.java
+++ b/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;
     }
 }
diff --git a/opends/src/server/org/opends/server/controls/GetEffectiveRights.java b/opends/src/server/org/opends/server/controls/GetEffectiveRights.java
new file mode 100644
index 0000000..24f8777
--- /dev/null
+++ b/opends/src/server/org/opends/server/controls/GetEffectiveRights.java
@@ -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);
+  }
+}
diff --git a/opends/src/server/org/opends/server/core/DefaultAccessControlProvider.java b/opends/src/server/org/opends/server/core/DefaultAccessControlProvider.java
index 9dc931d..6800688 100644
--- a/opends/src/server/org/opends/server/core/DefaultAccessControlProvider.java
+++ b/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;
+    }
   }
 }
diff --git a/opends/src/server/org/opends/server/core/SearchOperation.java b/opends/src/server/org/opends/server/core/SearchOperation.java
index 47d6c20..bca3da7 100644
--- a/opends/src/server/org/opends/server/core/SearchOperation.java
+++ b/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.
diff --git a/opends/src/server/org/opends/server/messages/AciMessages.java b/opends/src/server/org/opends/server/messages/AciMessages.java
index d140858..9935405 100644
--- a/opends/src/server/org/opends/server/messages/AciMessages.java
+++ b/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");
     }
 }
diff --git a/opends/src/server/org/opends/server/messages/CoreMessages.java b/opends/src/server/org/opends/server/messages/CoreMessages.java
index d27a8e9..1f3f357 100644
--- a/opends/src/server/org/opends/server/messages/CoreMessages.java
+++ b/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");
diff --git a/opends/src/server/org/opends/server/messages/ProtocolMessages.java b/opends/src/server/org/opends/server/messages/ProtocolMessages.java
index 156a19c..af4cef1 100644
--- a/opends/src/server/org/opends/server/messages/ProtocolMessages.java
+++ b/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"); }
+
+
 }
 
diff --git a/opends/src/server/org/opends/server/messages/ToolMessages.java b/opends/src/server/org/opends/server/messages/ToolMessages.java
index 9bfe596..e43ace7 100644
--- a/opends/src/server/org/opends/server/messages/ToolMessages.java
+++ b/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");
   }
 }
 
diff --git a/opends/src/server/org/opends/server/tools/LDAPSearch.java b/opends/src/server/org/opends/server/tools/LDAPSearch.java
index b799511..2c604de 100644
--- a/opends/src/server/org/opends/server/tools/LDAPSearch.java
+++ b/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());
diff --git a/opends/src/server/org/opends/server/tools/LDAPToolUtils.java b/opends/src/server/org/opends/server/tools/LDAPToolUtils.java
index ca40b69..3a54aa0 100644
--- a/opends/src/server/org/opends/server/tools/LDAPToolUtils.java
+++ b/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)
     {
diff --git a/opends/src/server/org/opends/server/tools/ToolConstants.java b/opends/src/server/org/opends/server/tools/ToolConstants.java
index 613f0b3..92ba45a 100644
--- a/opends/src/server/org/opends/server/tools/ToolConstants.java
+++ b/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";
 }
 
diff --git a/opends/src/server/org/opends/server/util/ServerConstants.java b/opends/src/server/org/opends/server/util/ServerConstants.java
index 79cc242..b8b39f9 100644
--- a/opends/src/server/org/opends/server/util/ServerConstants.java
+++ b/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.
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/GetEffectiveRightsTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/GetEffectiveRightsTestCase.java
new file mode 100644
index 0000000..4ae744c
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/GetEffectiveRightsTestCase.java
@@ -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();
+  }
+}

--
Gitblit v1.10.0