You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by el...@apache.org on 2011/10/14 19:38:32 UTC

svn commit: r1183441 [1/4] - in /directory/apacheds/trunk/interceptors/authz: ./ .settings/ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/directory/ src/main/java/org/apache/directory/server/ src/ma...

Author: elecharny
Date: Fri Oct 14 17:38:30 2011
New Revision: 1183441

URL: http://svn.apache.org/viewvc?rev=1183441&view=rev
Log:
added the interceptors projects

Added:
    directory/apacheds/trunk/interceptors/authz/   (with props)
    directory/apacheds/trunk/interceptors/authz/.settings/
    directory/apacheds/trunk/interceptors/authz/.settings/org.eclipse.jdt.core.prefs
    directory/apacheds/trunk/interceptors/authz/pom.xml
    directory/apacheds/trunk/interceptors/authz/src/
    directory/apacheds/trunk/interceptors/authz/src/main/
    directory/apacheds/trunk/interceptors/authz/src/main/java/
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/AciAuthorizationInterceptor.java
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/DefaultAuthorizationInterceptor.java
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/GroupCache.java
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/TupleCache.java
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/ACDFEngine.java
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/ACITupleFilter.java
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/AciContext.java
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/HighestPrecedenceFilter.java
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MaxImmSubFilter.java
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MaxValueCountFilter.java
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MicroOperationFilter.java
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MostSpecificProtectedItemFilter.java
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MostSpecificUserClassFilter.java
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/OperationScope.java
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/RelatedProtectedItemFilter.java
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/RelatedUserClassFilter.java
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/RestrictedByFilter.java
    directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/package-info.java
    directory/apacheds/trunk/interceptors/authz/src/test/
    directory/apacheds/trunk/interceptors/authz/src/test/java/
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/DummyAttributeTypeRegistry.java
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/DummyOidRegistry.java
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/HighestPrecedenceFilterTest.java
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/MaxImmSubFilterTest.java
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/MaxValueCountFilterTest.java
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/MicroOperationFilterTest.java
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/MostSpecificProtectedItemFilterTest.java
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/MostSpecificUserClassFilterTest.java
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/OperationScopeTest.java
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/RelatedProtectedItemFilterTest.java
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/RelatedUserClassFilterTest.java
    directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/RestrictedByFilterTest.java

Propchange: directory/apacheds/trunk/interceptors/authz/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Fri Oct 14 17:38:30 2011
@@ -0,0 +1,11 @@
+target
+.project
+.classpath
+.settings
+eclipse-classes
+*.log
+*.iml
+*.ipr
+dependency-reduced-pom.xml
+META-INF
+

Added: directory/apacheds/trunk/interceptors/authz/.settings/org.eclipse.jdt.core.prefs
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/interceptors/authz/.settings/org.eclipse.jdt.core.prefs?rev=1183441&view=auto
==============================================================================
--- directory/apacheds/trunk/interceptors/authz/.settings/org.eclipse.jdt.core.prefs (added)
+++ directory/apacheds/trunk/interceptors/authz/.settings/org.eclipse.jdt.core.prefs Fri Oct 14 17:38:30 2011
@@ -0,0 +1,9 @@
+#Thu Oct 13 14:00:09 CEST 2011
+encoding//src/test/java=ISO-8859-1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+eclipse.preferences.version=1
+encoding//src/test/resources=ISO-8859-1
+org.eclipse.jdt.core.compiler.source=1.6
+encoding//src/main/java=ISO-8859-1
+encoding//src/main/resources=ISO-8859-1
+org.eclipse.jdt.core.compiler.compliance=1.6

Added: directory/apacheds/trunk/interceptors/authz/pom.xml
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/interceptors/authz/pom.xml?rev=1183441&view=auto
==============================================================================
--- directory/apacheds/trunk/interceptors/authz/pom.xml (added)
+++ directory/apacheds/trunk/interceptors/authz/pom.xml Fri Oct 14 17:38:30 2011
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+  
+  http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.directory.server</groupId>
+    <artifactId>apacheds-interceptors</artifactId>
+    <version>2.0.0-M4-SNAPSHOT</version>
+  </parent>
+  
+  <artifactId>apacheds-interceptors-authz</artifactId>
+  <name>ApacheDS Authorization Interceptor</name>
+  <packaging>jar</packaging>
+
+  <description>
+    Authorization interceptor
+  </description>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.directory.junit</groupId>
+      <artifactId>junit-addons</artifactId>
+      <scope>test</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>apacheds-i18n</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>apacheds-core-api</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>apacheds-core-api</artifactId>
+      <classifier>tests</classifier>
+      <scope>test</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>apacheds-core-shared</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>apacheds-interceptors-event</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>apacheds-interceptors-subtree</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>commons-collections</groupId>
+      <artifactId>commons-collections</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>commons-lang</groupId>
+      <artifactId>commons-lang</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.directory.shared</groupId>
+      <artifactId>shared-ldap-client-api</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.directory.shared</groupId>
+      <artifactId>shared-i18n</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.directory.shared</groupId>
+      <artifactId>shared-ldap-codec-standalone</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.directory.shared</groupId>
+      <artifactId>shared-ldap-codec-core</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.directory.shared</groupId>
+      <artifactId>shared-ldap-extras-aci</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.directory.shared</groupId>
+      <artifactId>shared-ldap-extras-trigger</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.directory.shared</groupId>
+      <artifactId>shared-ldap-extras-util</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.directory.shared</groupId>
+      <artifactId>shared-ldap-model</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.directory.shared</groupId>
+      <artifactId>shared-ldap-schema-data</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.directory.shared</groupId>
+      <artifactId>shared-util</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>bouncycastle</groupId>
+      <artifactId>bcprov-jdk15</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>net.sf.ehcache</groupId>
+      <artifactId>ehcache-core</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.directory.shared</groupId>
+      <artifactId>shared-ldap-extras-codec</artifactId>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration> 
+          <systemPropertyVariables>
+            <workingDirectory>${basedir}/target/server-work</workingDirectory>
+          </systemPropertyVariables>
+        </configuration>
+      </plugin>
+      
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-source-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>attach-sources</id>
+            <phase>verify</phase>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+        <excludes>
+          <exclude>**/*.gif</exclude>
+        </excludes>
+      </resource>
+    </resources>
+  </build>
+</project>
+

Added: directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/AciAuthorizationInterceptor.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/AciAuthorizationInterceptor.java?rev=1183441&view=auto
==============================================================================
--- directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/AciAuthorizationInterceptor.java (added)
+++ directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/AciAuthorizationInterceptor.java Fri Oct 14 17:38:30 2011
@@ -0,0 +1,1423 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.apache.directory.server.core.authz;
+
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.naming.directory.SearchControls;
+
+import org.apache.directory.server.constants.ServerDNConstants;
+import org.apache.directory.server.core.shared.DefaultCoreSession;
+import org.apache.directory.server.core.api.CoreSession;
+import org.apache.directory.server.core.api.DirectoryService;
+import org.apache.directory.server.core.api.LdapPrincipal;
+import org.apache.directory.server.core.api.entry.ClonedServerEntry;
+import org.apache.directory.server.core.api.entry.ServerEntryUtils;
+import org.apache.directory.server.core.api.filtering.EntryFilter;
+import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
+import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
+import org.apache.directory.server.core.api.interceptor.InterceptorChain;
+import org.apache.directory.server.core.api.interceptor.NextInterceptor;
+import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.CompareOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.EntryOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.ListOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.OperationContext;
+import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.SearchingOperationContext;
+import org.apache.directory.server.core.api.partition.ByPassConstants;
+import org.apache.directory.server.core.api.partition.PartitionNexus;
+import org.apache.directory.server.core.authz.support.ACDFEngine;
+import org.apache.directory.server.core.authz.support.AciContext;
+import org.apache.directory.server.core.subtree.SubentryInterceptor;
+import org.apache.directory.server.i18n.I18n;
+import org.apache.directory.shared.ldap.aci.ACIItem;
+import org.apache.directory.shared.ldap.aci.ACIItemParser;
+import org.apache.directory.shared.ldap.aci.ACITuple;
+import org.apache.directory.shared.ldap.aci.MicroOperation;
+import org.apache.directory.shared.ldap.model.constants.AuthenticationLevel;
+import org.apache.directory.shared.ldap.model.constants.Loggers;
+import org.apache.directory.shared.ldap.model.constants.SchemaConstants;
+import org.apache.directory.shared.ldap.model.entry.Attribute;
+import org.apache.directory.shared.ldap.model.entry.Entry;
+import org.apache.directory.shared.ldap.model.entry.Modification;
+import org.apache.directory.shared.ldap.model.entry.StringValue;
+import org.apache.directory.shared.ldap.model.entry.Value;
+import org.apache.directory.shared.ldap.model.exception.LdapException;
+import org.apache.directory.shared.ldap.model.exception.LdapNoPermissionException;
+import org.apache.directory.shared.ldap.model.exception.LdapOperationErrorException;
+import org.apache.directory.shared.ldap.model.exception.LdapOperationException;
+import org.apache.directory.shared.ldap.model.filter.EqualityNode;
+import org.apache.directory.shared.ldap.model.filter.ExprNode;
+import org.apache.directory.shared.ldap.model.filter.OrNode;
+import org.apache.directory.shared.ldap.model.message.AliasDerefMode;
+import org.apache.directory.shared.ldap.model.name.Dn;
+import org.apache.directory.shared.ldap.model.schema.AttributeType;
+import org.apache.directory.shared.ldap.model.schema.normalizers.ConcreteNameComponentNormalizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * An ACI based authorization service.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class AciAuthorizationInterceptor extends BaseInterceptor
+{
+    /** the logger for this class */
+    private static final Logger LOG = LoggerFactory.getLogger( AciAuthorizationInterceptor.class );
+
+    /** the dedicated logger for ACI */
+    private static final Logger ACI_LOG = LoggerFactory.getLogger( Loggers.ACI_LOG.getName() );
+
+    private static final Collection<MicroOperation> ADD_PERMS;
+    private static final Collection<MicroOperation> READ_PERMS;
+    private static final Collection<MicroOperation> COMPARE_PERMS;
+    private static final Collection<MicroOperation> SEARCH_ENTRY_PERMS;
+    private static final Collection<MicroOperation> SEARCH_ATTRVAL_PERMS;
+    private static final Collection<MicroOperation> REMOVE_PERMS;
+    private static final Collection<MicroOperation> BROWSE_PERMS;
+    private static final Collection<MicroOperation> LOOKUP_PERMS;
+    private static final Collection<MicroOperation> REPLACE_PERMS;
+    private static final Collection<MicroOperation> RENAME_PERMS;
+    private static final Collection<MicroOperation> EXPORT_PERMS;
+    private static final Collection<MicroOperation> IMPORT_PERMS;
+    private static final Collection<MicroOperation> MOVERENAME_PERMS;
+
+    static
+    {
+        Set<MicroOperation> set = new HashSet<MicroOperation>( 2 );
+        set.add( MicroOperation.BROWSE );
+        set.add( MicroOperation.RETURN_DN );
+        SEARCH_ENTRY_PERMS = Collections.unmodifiableCollection( set );
+
+        set = new HashSet<MicroOperation>( 2 );
+        set.add( MicroOperation.READ );
+        set.add( MicroOperation.BROWSE );
+        LOOKUP_PERMS = Collections.unmodifiableCollection( set );
+
+        set = new HashSet<MicroOperation>( 2 );
+        set.add( MicroOperation.ADD );
+        set.add( MicroOperation.REMOVE );
+        REPLACE_PERMS = Collections.unmodifiableCollection( set );
+
+        set = new HashSet<MicroOperation>( 2 );
+        set.add( MicroOperation.EXPORT );
+        set.add( MicroOperation.RENAME );
+        MOVERENAME_PERMS = Collections.unmodifiableCollection( set );
+
+        SEARCH_ATTRVAL_PERMS = Collections.singleton( MicroOperation.READ );
+        ADD_PERMS = Collections.singleton( MicroOperation.ADD );
+        READ_PERMS = Collections.singleton( MicroOperation.READ );
+        COMPARE_PERMS = Collections.singleton( MicroOperation.COMPARE );
+        REMOVE_PERMS = Collections.singleton( MicroOperation.REMOVE );
+        BROWSE_PERMS = Collections.singleton( MicroOperation.BROWSE );
+        RENAME_PERMS = Collections.singleton( MicroOperation.RENAME );
+        EXPORT_PERMS = Collections.singleton( MicroOperation.EXPORT );
+        IMPORT_PERMS = Collections.singleton( MicroOperation.IMPORT );
+    }
+
+    /** a tupleCache that responds to add, delete, and modify attempts */
+    private TupleCache tupleCache;
+
+    /** a groupCache that responds to add, delete, and modify attempts */
+    private GroupCache groupCache;
+
+    /** a normalizing ACIItem parser */
+    private ACIItemParser aciParser;
+
+    /** use and instance of the ACDF engine */
+    private ACDFEngine engine;
+
+    /** interceptor chain */
+    private InterceptorChain chain;
+
+    /** the system wide subschemaSubentryDn */
+    private String subschemaSubentryDn;
+
+    /** A reference to the nexus for direct backend operations */
+    private PartitionNexus nexus;
+
+    public static final SearchControls DEFAULT_SEARCH_CONTROLS = new SearchControls();
+
+
+    /**
+     * Load the Tuples into the cache
+     */
+    private void initTupleCache() throws LdapException
+    {
+        // Load all the prescriptiveACI : they are stored in AccessControlSubentry entries
+        Dn adminDn = new Dn( schemaManager, ServerDNConstants.ADMIN_SYSTEM_DN );
+
+        SearchControls controls = new SearchControls();
+        controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+        controls.setReturningAttributes( new String[]
+            { SchemaConstants.PRESCRIPTIVE_ACI_AT } );
+
+        ExprNode filter =
+                new EqualityNode<String>( OBJECT_CLASS_AT, new StringValue( SchemaConstants.ACCESS_CONTROL_SUBENTRY_OC ) );
+
+        CoreSession adminSession = new DefaultCoreSession( new LdapPrincipal( schemaManager, adminDn, AuthenticationLevel.STRONG ),
+            directoryService );
+
+        SearchOperationContext searchOperationContext = new SearchOperationContext( adminSession, Dn.ROOT_DSE, filter,
+            controls );
+
+        searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES );
+
+        EntryFilteringCursor results = nexus.search( searchOperationContext );
+
+        try
+        {
+            while ( results.next() )
+            {
+                Entry entry = results.get();
+
+                tupleCache.subentryAdded( entry.getDn(), entry );
+            }
+
+            results.close();
+        }
+        catch ( Exception e )
+        {
+            throw new LdapOperationException( e.getMessage(), e );
+        }
+    }
+
+
+    /**
+     * Load the Groups into the cache
+     */
+    private void initGroupCache() throws LdapException
+    {
+        // Load all the member/uniqueMember : they are stored in groupOfNames/groupOfUniqueName
+        Dn adminDn = new Dn( schemaManager, ServerDNConstants.ADMIN_SYSTEM_DN );
+
+        SearchControls controls = new SearchControls();
+        controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+        controls.setReturningAttributes( new String[]
+            { SchemaConstants.MEMBER_AT, SchemaConstants.UNIQUE_MEMBER_AT } );
+
+        ExprNode filter =
+            new OrNode(
+                new EqualityNode<String>( OBJECT_CLASS_AT, new StringValue( SchemaConstants.GROUP_OF_NAMES_OC ) ),
+                new EqualityNode<String>( OBJECT_CLASS_AT, new StringValue( SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC ) ) );
+
+        CoreSession adminSession = new DefaultCoreSession( new LdapPrincipal( schemaManager, adminDn, AuthenticationLevel.STRONG ),
+            directoryService );
+
+        SearchOperationContext searchOperationContext = new SearchOperationContext( adminSession, Dn.ROOT_DSE, filter,
+            controls );
+
+        searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES );
+
+        EntryFilteringCursor results = nexus.search( searchOperationContext );
+
+        try
+        {
+            while ( results.next() )
+            {
+                Entry entry = results.get();
+
+                groupCache.groupAdded( entry.getDn(), entry );
+            }
+
+            results.close();
+        }
+        catch ( Exception e )
+        {
+            throw new LdapOperationException( e.getMessage(), e );
+        }
+    }
+
+
+    /**
+     * Initializes this interceptor based service by getting a handle on the nexus, setting up
+     * the tuple and group membership caches, the ACIItem parser and the ACDF engine.
+     *
+     * @param directoryService the directory service core
+     * @throws Exception if there are problems during initialization
+     */
+    public void init( DirectoryService directoryService ) throws LdapException
+    {
+        LOG.debug( "Initializing the AciAuthorizationInterceptor" );
+
+        super.init( directoryService );
+
+        nexus = directoryService.getPartitionNexus();
+
+        Dn adminDn = directoryService.getDnFactory().create( ServerDNConstants.ADMIN_SYSTEM_DN );
+        CoreSession adminSession = new DefaultCoreSession( new LdapPrincipal( schemaManager, adminDn, AuthenticationLevel.STRONG ),
+            directoryService );
+        chain = directoryService.getInterceptorChain();
+
+        // Create the caches
+        tupleCache = new TupleCache( adminSession );
+        groupCache = new GroupCache( directoryService );
+
+        // Iitialize the ACI PARSER and ACDF engine
+        aciParser = new ACIItemParser( new ConcreteNameComponentNormalizer( schemaManager ), schemaManager );
+        engine = new ACDFEngine( schemaManager );
+
+        // stuff for dealing with subentries (garbage for now)
+        Value<?> subschemaSubentry = directoryService.getPartitionNexus().getRootDSE( null ).get(
+            SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get();
+        Dn subschemaSubentryDnName = directoryService.getDnFactory().create( subschemaSubentry.getString() );
+        subschemaSubentryDn = subschemaSubentryDnName.getNormName();
+
+        // Init the caches now
+        initTupleCache();
+        initGroupCache();
+    }
+
+
+    private void protectCriticalEntries( Dn dn ) throws LdapException
+    {
+        Dn principalDn = getPrincipal().getDn();
+
+        if ( dn.isEmpty() )
+        {
+            String msg = I18n.err( I18n.ERR_8 );
+            LOG.error( msg );
+            throw new LdapNoPermissionException( msg );
+        }
+
+        if ( isTheAdministrator( dn ) )
+        {
+            String msg = I18n.err( I18n.ERR_9, principalDn.getName(), dn.getName() );
+            LOG.error( msg );
+            throw new LdapNoPermissionException( msg );
+        }
+    }
+
+
+    /**
+     * Adds perscriptiveACI tuples to a collection of tuples by accessing the
+     * tupleCache.  The tuple cache is accessed for each A/C subentry
+     * associated with the protected entry.  Note that subentries are handled
+     * differently: their parent, the administrative entry is accessed to
+     * determine the perscriptiveACIs effecting the AP and hence the subentry
+     * which is considered to be in the same context.
+     *
+     * @param tuples the collection of tuples to add to
+     * @param dn the normalized distinguished name of the protected entry
+     * @param entry the target entry that access to is being controled
+     * @throws Exception if there are problems accessing attribute values
+     * @param proxy the partition nexus proxy object
+     */
+    private void addPerscriptiveAciTuples( OperationContext opContext, Collection<ACITuple> tuples, Dn dn, Entry entry )
+        throws LdapException
+    {
+        Entry originalEntry = null;
+
+        if ( entry instanceof ClonedServerEntry )
+        {
+            originalEntry = ((ClonedServerEntry)entry).getOriginalEntry();
+        }
+        else
+        {
+            originalEntry = entry;
+        }
+
+        Attribute oc = originalEntry.get( OBJECT_CLASS_AT );
+
+        /*
+         * If the protected entry is a subentry, then the entry being evaluated
+         * for perscriptiveACIs is in fact the administrative entry.  By
+         * substituting the administrative entry for the actual subentry the
+         * code below this "if" statement correctly evaluates the effects of
+         * perscriptiveACI on the subentry.  Basically subentries are considered
+         * to be in the same naming context as their access point so the subentries
+         * effecting their parent entry applies to them as well.
+         */
+        if ( oc.contains( SchemaConstants.SUBENTRY_OC ) )
+        {
+            Dn parentDn = dn.getParent();
+            originalEntry = opContext.lookup( parentDn, ByPassConstants.LOOKUP_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
+        }
+
+        Attribute subentries = originalEntry.get( ACCESS_CONTROL_SUBENTRIES_AT );
+
+        if ( subentries == null )
+        {
+            return;
+        }
+
+        for ( Value<?> value : subentries )
+        {
+            String subentryDn = value.getString();
+            tuples.addAll( tupleCache.getACITuples( subentryDn ) );
+        }
+    }
+
+
+    /**
+     * Adds the set of entryACI tuples to a collection of tuples.  The entryACI
+     * is parsed and tuples are generated on they fly then added to the collection.
+     *
+     * @param tuples the collection of tuples to add to
+     * @param entry the target entry that access to is being regulated
+     * @throws Exception if there are problems accessing attribute values
+     */
+    private void addEntryAciTuples( Collection<ACITuple> tuples, Entry entry ) throws LdapException
+    {
+        Attribute entryAci = entry.get( ENTRY_ACI_AT );
+
+        if ( entryAci == null )
+        {
+            return;
+        }
+
+        for ( Value<?> value : entryAci )
+        {
+            String aciString = value.getString();
+            ACIItem item;
+
+            try
+            {
+                item = aciParser.parse( aciString );
+            }
+            catch ( ParseException e )
+            {
+                String msg = I18n.err( I18n.ERR_10, aciString );
+                LOG.error( msg, e );
+                throw new LdapOperationErrorException( msg );
+            }
+
+            tuples.addAll( item.toTuples() );
+        }
+    }
+
+
+    /**
+     * Adds the set of subentryACI tuples to a collection of tuples.  The subentryACI
+     * is parsed and tuples are generated on the fly then added to the collection.
+     *
+     * @param tuples the collection of tuples to add to
+     * @param dn the normalized distinguished name of the protected entry
+     * @param entry the target entry that access to is being regulated
+     * @throws Exception if there are problems accessing attribute values
+     * @param proxy the partition nexus proxy object
+     */
+    private void addSubentryAciTuples( OperationContext opContext, Collection<ACITuple> tuples, Dn dn, Entry entry )
+        throws LdapException
+    {
+        // only perform this for subentries
+        if ( !entry.contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.SUBENTRY_OC ) )
+        {
+            return;
+        }
+
+        // get the parent or administrative entry for this subentry since it
+        // will contain the subentryACI attributes that effect subentries
+        Dn parentDn = dn.getParent();
+        Entry administrativeEntry = ( ( ClonedServerEntry ) opContext.lookup( parentDn, ByPassConstants.LOOKUP_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY ) )
+            .getOriginalEntry();
+
+        Attribute subentryAci = administrativeEntry.get( SUBENTRY_ACI_AT );
+
+        if ( subentryAci == null )
+        {
+            return;
+        }
+
+        for ( Value<?> value : subentryAci )
+        {
+            String aciString = value.getString();
+            ACIItem item;
+
+            try
+            {
+                item = aciParser.parse( aciString );
+            }
+            catch ( ParseException e )
+            {
+                String msg = I18n.err( I18n.ERR_11, aciString );
+                LOG.error( msg, e );
+                throw new LdapOperationErrorException( msg );
+            }
+
+            tuples.addAll( item.toTuples() );
+        }
+    }
+
+
+    /* -------------------------------------------------------------------------------
+     * Within every access controled interceptor method we must retrieve the ACITuple
+     * set for all the perscriptiveACIs that apply to the candidate, the target entry
+     * operated upon.  This ACITuple set is gotten from the TupleCache by looking up
+     * the subentries referenced by the accessControlSubentries operational attribute
+     * within the target entry.
+     *
+     * Then the entry is inspected for an entryACI.  This is not done for the add op
+     * since it could introduce a security breech.  So for non-add ops if present a
+     * set of ACITuples are generated for all the entryACIs within the entry.  This
+     * set is combined with the ACITuples cached for the perscriptiveACI affecting
+     * the target entry.  If the entry is a subentry the ACIs are also processed for
+     * the subentry to generate more ACITuples.  This subentry TupleACI set is joined
+     * with the entry and perscriptive ACI.
+     *
+     * The union of ACITuples are fed into the engine along with other parameters
+     * to decide whether a permission is granted or rejected for the specific
+     * operation.
+     * -------------------------------------------------------------------------------
+     */
+
+    public void add( NextInterceptor next, AddOperationContext addContext ) throws LdapException
+    {
+        // bypass authz code if it was disabled
+        if ( !addContext.getSession().getDirectoryService().isAccessControlEnabled() )
+        {
+            ACI_LOG.debug( "ACI interceptor disabled" );
+            next.add( addContext );
+            return;
+        }
+
+        ACI_LOG.debug( "Adding the entry {}", addContext.getEntry() );
+
+        // Access the principal requesting the operation, and bypass checks if it is the admin
+        LdapPrincipal principal = addContext.getSession().getEffectivePrincipal();
+        Dn principalDn = principal.getDn();
+
+        Entry serverEntry = addContext.getEntry();
+
+        Dn dn = addContext.getDn();
+
+        // bypass authz code but manage caches if operation is performed by the admin
+        if ( isPrincipalAnAdministrator( principalDn ) )
+        {
+            ACI_LOG.debug( "Addition done by the administartor : no check" );
+
+            next.add( addContext );
+            tupleCache.subentryAdded( dn, serverEntry );
+            groupCache.groupAdded( dn, serverEntry );
+            return;
+        }
+
+        // perform checks below here for all non-admin users
+        SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) chain.get( SubentryInterceptor.class
+            .getSimpleName() );
+        Entry subentry = subentryInterceptor.getSubentryAttributes( dn, serverEntry );
+
+        for ( Attribute attribute : serverEntry )
+        {
+            subentry.put( attribute );
+        }
+
+        // Assemble all the information required to make an access control decision
+        Set<Dn> userGroups = groupCache.getGroups( principalDn.getNormName() );
+        Collection<ACITuple> tuples = new HashSet<ACITuple>();
+
+        // Build the total collection of tuples to be considered for add rights
+        // NOTE: entryACI are NOT considered in adds (it would be a security breech)
+        addPerscriptiveAciTuples( addContext, tuples, dn, subentry );
+        addSubentryAciTuples( addContext, tuples, dn, subentry );
+
+        // check if entry scope permission is granted
+        AciContext entryAciCtx = new AciContext( schemaManager, addContext );
+        entryAciCtx.setUserGroupNames( userGroups );
+        entryAciCtx.setUserDn( principalDn );
+        entryAciCtx.setAuthenticationLevel( principal.getAuthenticationLevel() );
+        entryAciCtx.setEntryDn( dn );
+        entryAciCtx.setMicroOperations( ADD_PERMS );
+        entryAciCtx.setAciTuples( tuples );
+        entryAciCtx.setEntry( subentry );
+
+        engine.checkPermission( entryAciCtx );
+
+        // now we must check if attribute type and value scope permission is granted
+        for ( Attribute attribute : serverEntry )
+        {
+            for ( Value<?> value : attribute )
+            {
+                AciContext attrAciContext = new AciContext( schemaManager, addContext );
+                attrAciContext.setUserGroupNames( userGroups );
+                attrAciContext.setUserDn( principalDn );
+                attrAciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+                attrAciContext.setEntryDn( dn );
+                attrAciContext.setAttributeType( attribute.getAttributeType() );
+                attrAciContext.setAttrValue( value );
+                attrAciContext.setMicroOperations( ADD_PERMS );
+                attrAciContext.setAciTuples( tuples );
+                attrAciContext.setEntry( serverEntry );
+
+                engine.checkPermission( attrAciContext );
+            }
+        }
+
+        // if we've gotten this far then access has been granted
+        next.add( addContext );
+
+        // if the entry added is a subentry or a groupOf[Unique]Names we must
+        // update the ACITuple cache and the groups cache to keep them in sync
+        tupleCache.subentryAdded( dn, serverEntry );
+        groupCache.groupAdded( dn, serverEntry );
+    }
+
+
+    private boolean isTheAdministrator( Dn normalizedDn )
+    {
+        return normalizedDn.getNormName().equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
+    }
+
+
+    public void delete( NextInterceptor next, DeleteOperationContext deleteContext ) throws LdapException
+    {
+        CoreSession session = deleteContext.getSession();
+
+        // bypass authz code if we are disabled
+        if ( !session.getDirectoryService().isAccessControlEnabled() )
+        {
+            next.delete( deleteContext );
+            return;
+        }
+
+        Dn dn = deleteContext.getDn();
+        LdapPrincipal principal = session.getEffectivePrincipal();
+        Dn principalDn = principal.getDn();
+
+        Entry entry = deleteContext.getEntry();
+
+        protectCriticalEntries( dn );
+
+        // bypass authz code but manage caches if operation is performed by the admin
+        if ( isPrincipalAnAdministrator( principalDn ) )
+        {
+            next.delete( deleteContext );
+
+            tupleCache.subentryDeleted( dn, entry );
+            groupCache.groupDeleted( dn, entry );
+
+            return;
+        }
+
+        Set<Dn> userGroups = groupCache.getGroups( principalDn.getNormName() );
+        Collection<ACITuple> tuples = new HashSet<ACITuple>();
+        addPerscriptiveAciTuples( deleteContext, tuples, dn, entry );
+        addEntryAciTuples( tuples, entry );
+        addSubentryAciTuples( deleteContext, tuples, dn, entry );
+
+        AciContext aciContext = new AciContext( schemaManager, deleteContext );
+        aciContext.setUserGroupNames( userGroups );
+        aciContext.setUserDn( principalDn );
+        aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+        aciContext.setEntryDn( dn );
+        aciContext.setMicroOperations( REMOVE_PERMS );
+        aciContext.setAciTuples( tuples );
+        aciContext.setEntry( entry );
+
+        engine.checkPermission( aciContext );
+
+        next.delete( deleteContext );
+
+        tupleCache.subentryDeleted( dn, entry );
+        groupCache.groupDeleted( dn, entry );
+    }
+
+
+    // False positive, we want to keep the comment
+    @SuppressWarnings("PMD.CollapsibleIfStatements")
+    public void modify( NextInterceptor next, ModifyOperationContext modifyContext ) throws LdapException
+    {
+        Dn dn = modifyContext.getDn();
+
+        // Access the principal requesting the operation, and bypass checks if it is the admin
+        Entry entry = modifyContext.getEntry();
+
+        LdapPrincipal principal = modifyContext.getSession().getEffectivePrincipal();
+        Dn principalDn = principal.getDn();
+
+        // bypass authz code if we are disabled
+        if ( !modifyContext.getSession().getDirectoryService().isAccessControlEnabled() )
+        {
+            next.modify( modifyContext );
+            return;
+        }
+
+        List<Modification> mods = modifyContext.getModItems();
+
+        // bypass authz code but manage caches if operation is performed by the admin
+        if ( isPrincipalAnAdministrator( principalDn ) )
+        {
+            next.modify( modifyContext );
+
+            /**
+             * @TODO: A virtual entry can be created here for not hitting the backend again.
+             */
+            Entry modifiedEntry = modifyContext.lookup( dn, ByPassConstants.LOOKUP_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
+            tupleCache.subentryModified( dn, mods, modifiedEntry );
+            groupCache.groupModified( dn, mods, entry, schemaManager );
+            return;
+        }
+
+        Set<Dn> userGroups = groupCache.getGroups( principalDn.getName() );
+        Collection<ACITuple> tuples = new HashSet<ACITuple>();
+        addPerscriptiveAciTuples( modifyContext, tuples, dn, entry );
+        addEntryAciTuples( tuples, entry );
+        addSubentryAciTuples( modifyContext, tuples, dn, entry );
+
+        AciContext entryAciContext = new AciContext( schemaManager, modifyContext );
+        entryAciContext.setUserGroupNames( userGroups );
+        entryAciContext.setUserDn( principalDn );
+        entryAciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+        entryAciContext.setEntryDn( dn );
+        entryAciContext.setMicroOperations( Collections.singleton( MicroOperation.MODIFY ) );
+        entryAciContext.setAciTuples( tuples );
+        entryAciContext.setEntry( entry );
+
+        engine.checkPermission( entryAciContext );
+
+        Collection<MicroOperation> perms = null;
+        Entry entryView = entry.clone();
+
+        for ( Modification mod : mods )
+        {
+            Attribute attr = mod.getAttribute();
+
+            switch ( mod.getOperation() )
+            {
+                case ADD_ATTRIBUTE:
+                    perms = ADD_PERMS;
+
+                    // If the attribute is being created with an initial value ...
+                    if ( entry.get( attr.getId() ) == null )
+                    {
+                        AciContext attrAciContext = new AciContext( schemaManager, modifyContext );
+                        attrAciContext.setUserGroupNames( userGroups );
+                        attrAciContext.setUserDn( principalDn );
+                        attrAciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+                        attrAciContext.setEntryDn( dn );
+                        attrAciContext.setAttributeType( attr.getAttributeType() );
+                        attrAciContext.setMicroOperations( perms );
+                        attrAciContext.setAciTuples( tuples );
+                        attrAciContext.setEntry( entry );
+
+                        // ... we also need to check if adding the attribute is permitted
+                        engine.checkPermission( attrAciContext );
+                    }
+
+                    break;
+
+                case REMOVE_ATTRIBUTE:
+                    perms = REMOVE_PERMS;
+                    Attribute entryAttr = entry.get( attr.getId() );
+
+                    if ( entryAttr != null )
+                    {
+                        // If there is only one value remaining in the attribute ...
+                        if ( entryAttr.size() == 1 )
+                        {
+                            // ... we also need to check if removing the attribute at all is permitted
+                            AciContext aciContext = new AciContext( schemaManager, modifyContext );
+                            aciContext.setUserGroupNames( userGroups );
+                            aciContext.setUserDn( principalDn );
+                            aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+                            aciContext.setEntryDn( dn );
+                            aciContext.setAttributeType( attr.getAttributeType() );
+                            aciContext.setMicroOperations( perms );
+                            aciContext.setAciTuples( tuples );
+                            aciContext.setEntry( entry );
+
+                            engine.checkPermission( aciContext );
+                        }
+                    }
+
+                    break;
+
+                case REPLACE_ATTRIBUTE:
+                    perms = REPLACE_PERMS;
+                    break;
+            }
+
+            /**
+             * Update the entry view as the current modification is applied to the original entry.
+             * This is especially required for handling the MaxValueCount protected item. Number of
+             * values for an attribute after a modification should be known in advance in order to
+             * check permissions for MaxValueCount protected item. So during addition of the first
+             * value of an attribute it can be rejected if the permission denied due the the
+             * MaxValueCount protected item. This is not the perfect implementation as required by
+             * the specification because the system should reject the addition exactly on the right
+             * value of the attribute. However as we do not have that much granularity in our
+             * implementation (we consider an Attribute Addition itself a Micro Operation,
+             * not the individual Value Additions) we just handle this when the first value of an
+             * attribute is being checked for relevant permissions below.
+             */
+            entryView = ServerEntryUtils.getTargetEntry( mod, entryView, schemaManager );
+
+            for ( Value<?> value : attr )
+            {
+                AciContext aciContext = new AciContext( schemaManager, modifyContext );
+                aciContext.setUserGroupNames( userGroups );
+                aciContext.setUserDn( principalDn );
+                aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+                aciContext.setEntryDn( dn );
+                aciContext.setAttributeType( attr.getAttributeType() );
+                aciContext.setAttrValue( value );
+                aciContext.setMicroOperations( perms );
+                aciContext.setAciTuples( tuples );
+                aciContext.setEntry( entry );
+                aciContext.setEntryView( entryView );
+
+                engine.checkPermission( aciContext );
+            }
+        }
+
+        next.modify( modifyContext );
+        /**
+         * @TODO: A virtual entry can be created here for not hitting the backend again.
+         */
+        Entry modifiedEntry = modifyContext.lookup( dn, ByPassConstants.LOOKUP_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
+        tupleCache.subentryModified( dn, mods, modifiedEntry );
+        groupCache.groupModified( dn, mods, entry, schemaManager );
+    }
+
+
+    public boolean hasEntry( NextInterceptor next, EntryOperationContext hasEntryContext ) throws LdapException
+    {
+        Dn dn = hasEntryContext.getDn();
+
+        if ( !hasEntryContext.getSession().getDirectoryService().isAccessControlEnabled() )
+        {
+            return ( dn.isRootDSE() || next.hasEntry( hasEntryContext ) );
+        }
+
+        boolean answer = next.hasEntry( hasEntryContext );
+
+        // no checks on the RootDSE
+        if ( dn.isRootDSE() )
+        {
+            // No need to go down to the stack, if the dn is empty
+            // It's the rootDSE, and it exists !
+            return answer;
+        }
+
+        // TODO - eventually replace this with a check on session.isAnAdministrator()
+        LdapPrincipal principal = hasEntryContext.getSession().getEffectivePrincipal();
+        Dn principalDn = principal.getDn();
+
+        if ( isPrincipalAnAdministrator( principalDn ) )
+        {
+            return answer;
+        }
+
+        Entry entry = hasEntryContext.lookup( dn, ByPassConstants.HAS_ENTRY_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
+        Set<Dn> userGroups = groupCache.getGroups( principalDn.getNormName() );
+        Collection<ACITuple> tuples = new HashSet<ACITuple>();
+        addPerscriptiveAciTuples( hasEntryContext, tuples, dn, entry );
+        addEntryAciTuples( tuples, ( ( ClonedServerEntry ) entry ).getOriginalEntry() );
+        addSubentryAciTuples( hasEntryContext, tuples, dn, ( ( ClonedServerEntry ) entry ).getOriginalEntry() );
+
+        // check that we have browse access to the entry
+        AciContext aciContext = new AciContext( schemaManager, hasEntryContext );
+        aciContext.setUserGroupNames( userGroups );
+        aciContext.setUserDn( principalDn );
+        aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+        aciContext.setEntryDn( dn );
+        aciContext.setMicroOperations( BROWSE_PERMS );
+        aciContext.setAciTuples( tuples );
+        aciContext.setEntry( ((ClonedServerEntry)entry).getOriginalEntry() );
+
+        engine.checkPermission( aciContext );
+
+        return next.hasEntry( hasEntryContext );
+    }
+
+
+    /**
+     * Checks if the READ permissions exist to the entry and to each attribute type and
+     * value.
+     *
+     * @todo not sure if we should hide attribute types/values or throw an exception
+     * instead.  I think we're going to have to use a filter to restrict the return
+     * of attribute types and values instead of throwing an exception.  Lack of read
+     * perms to attributes and their values results in their removal when returning
+     * the entry.
+     *
+     * @param principal the user associated with the call
+     * @param dn the name of the entry being looked up
+     * @param entry the raw entry pulled from the nexus
+     * @throws Exception if undlying access to the DIT fails
+     */
+    private void checkLookupAccess( LookupOperationContext lookupContext, Entry entry ) throws LdapException
+    {
+        Dn dn = lookupContext.getDn();
+
+        // no permissions checks on the RootDSE
+        if ( dn.isRootDSE() )
+        {
+            return;
+        }
+
+        LdapPrincipal principal = lookupContext.getSession().getEffectivePrincipal();
+        Dn userName = principal.getDn();
+        Set<Dn> userGroups = groupCache.getGroups( userName.getNormName() );
+        Collection<ACITuple> tuples = new HashSet<ACITuple>();
+        addPerscriptiveAciTuples( lookupContext, tuples, dn, entry );
+        addEntryAciTuples( tuples, entry );
+        addSubentryAciTuples( lookupContext, tuples, dn, entry );
+
+        // check that we have read access to the entry
+        AciContext aciContext = new AciContext( schemaManager, lookupContext );
+        aciContext.setUserGroupNames( userGroups );
+        aciContext.setUserDn( userName );
+        aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+        aciContext.setEntryDn( dn );
+        aciContext.setMicroOperations( LOOKUP_PERMS );
+        aciContext.setAciTuples( tuples );
+        aciContext.setEntry( entry );
+
+        engine.checkPermission( aciContext );
+
+        // check that we have read access to every attribute type and value
+        for ( Attribute attribute : entry )
+        {
+
+            for ( Value<?> value : attribute )
+            {
+                AciContext valueAciContext = new AciContext( schemaManager, lookupContext );
+                valueAciContext.setUserGroupNames( userGroups );
+                valueAciContext.setUserDn( userName );
+                valueAciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+                valueAciContext.setEntryDn( dn );
+                valueAciContext.setAttributeType( attribute.getAttributeType() );
+                valueAciContext.setAttrValue( value );
+                valueAciContext.setMicroOperations( READ_PERMS );
+                valueAciContext.setAciTuples( tuples );
+                valueAciContext.setEntry( entry );
+
+                engine.checkPermission( valueAciContext );
+            }
+        }
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Entry lookup( NextInterceptor next, LookupOperationContext lookupContext ) throws LdapException
+    {
+        CoreSession session = lookupContext.getSession();
+        DirectoryService directoryService = session.getDirectoryService();
+
+        LdapPrincipal principal = session.getEffectivePrincipal();
+        Dn principalDn = principal.getDn();
+
+        if ( !principalDn.isSchemaAware() )
+        {
+            principalDn.apply( schemaManager );
+        }
+
+        // Bypass this interceptor if we disabled the AC subsystem or if the principal is the admin
+        if ( isPrincipalAnAdministrator( principalDn ) || !directoryService.isAccessControlEnabled() )
+        {
+            return next.lookup( lookupContext );
+        }
+
+        lookupContext.setByPassed( ByPassConstants.LOOKUP_BYPASS );
+        Entry entry = directoryService.getOperationManager().lookup( lookupContext );
+
+        checkLookupAccess( lookupContext, entry );
+
+        return next.lookup( lookupContext );
+    }
+
+
+    public void rename( NextInterceptor next, RenameOperationContext renameContext ) throws LdapException
+    {
+        Dn oldName = renameContext.getDn();
+        Entry originalEntry = null;
+
+        if ( renameContext.getEntry() != null )
+        {
+            originalEntry = ((ClonedServerEntry)renameContext.getEntry()).getOriginalEntry();
+        }
+
+        LdapPrincipal principal = renameContext.getSession().getEffectivePrincipal();
+        Dn principalDn = principal.getDn();
+        Dn newName = renameContext.getNewDn();
+
+        // bypass authz code if we are disabled
+        if ( !renameContext.getSession().getDirectoryService().isAccessControlEnabled() )
+        {
+            next.rename( renameContext );
+            return;
+        }
+
+        protectCriticalEntries( oldName );
+
+        // bypass authz code but manage caches if operation is performed by the admin
+        if ( isPrincipalAnAdministrator( principalDn ) )
+        {
+            next.rename( renameContext );
+            tupleCache.subentryRenamed( oldName, newName );
+
+            // TODO : this method returns a boolean : what should we do with the result ?
+            groupCache.groupRenamed( oldName, newName );
+
+            return;
+        }
+
+        Set<Dn> userGroups = groupCache.getGroups( principalDn.getNormName() );
+        Collection<ACITuple> tuples = new HashSet<ACITuple>();
+        addPerscriptiveAciTuples( renameContext, tuples, oldName, originalEntry );
+        addEntryAciTuples( tuples, originalEntry );
+        addSubentryAciTuples( renameContext, tuples, oldName, originalEntry );
+
+        AciContext aciContext = new AciContext( schemaManager, renameContext );
+        aciContext.setUserGroupNames( userGroups );
+        aciContext.setUserDn( principalDn );
+        aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+        aciContext.setEntryDn( oldName );
+        aciContext.setMicroOperations( RENAME_PERMS );
+        aciContext.setAciTuples( tuples );
+        aciContext.setEntry( originalEntry );
+
+        engine.checkPermission( aciContext );
+
+        next.rename( renameContext );
+        tupleCache.subentryRenamed( oldName, newName );
+        groupCache.groupRenamed( oldName, newName );
+    }
+
+
+    public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext moveAndRenameContext )
+        throws LdapException
+    {
+        Dn oldDn = moveAndRenameContext.getDn();
+
+        Entry entry = moveAndRenameContext.getOriginalEntry();
+
+        LdapPrincipal principal = moveAndRenameContext.getSession().getEffectivePrincipal();
+        Dn principalDn = principal.getDn();
+        Dn newDn = moveAndRenameContext.getNewDn();
+
+        // bypass authz code if we are disabled
+        if ( !moveAndRenameContext.getSession().getDirectoryService().isAccessControlEnabled() )
+        {
+            next.moveAndRename( moveAndRenameContext );
+            return;
+        }
+
+        protectCriticalEntries( oldDn );
+
+        // bypass authz code but manage caches if operation is performed by the admin
+        if ( isPrincipalAnAdministrator( principalDn ) )
+        {
+            next.moveAndRename( moveAndRenameContext );
+            tupleCache.subentryRenamed( oldDn, newDn );
+            groupCache.groupRenamed( oldDn, newDn );
+            return;
+        }
+
+        Set<Dn> userGroups = groupCache.getGroups( principalDn.getNormName() );
+        Collection<ACITuple> tuples = new HashSet<ACITuple>();
+        addPerscriptiveAciTuples( moveAndRenameContext, tuples, oldDn,entry );
+        addEntryAciTuples( tuples, entry );
+        addSubentryAciTuples( moveAndRenameContext, tuples, oldDn, entry );
+
+        AciContext aciContext = new AciContext( schemaManager, moveAndRenameContext );
+        aciContext.setUserGroupNames( userGroups );
+        aciContext.setUserDn( principalDn );
+        aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+        aciContext.setEntryDn( oldDn );
+        aciContext.setMicroOperations( MOVERENAME_PERMS );
+        aciContext.setAciTuples( tuples );
+        aciContext.setEntry( entry );
+
+        engine.checkPermission( aciContext );
+
+        // Get the entry again without operational attributes
+        // because access control subentry operational attributes
+        // will not be valid at the new location.
+        // This will certainly be fixed by the SubentryInterceptor,
+        // but after this service.
+
+        Entry importedEntry = moveAndRenameContext.lookup( oldDn,
+            ByPassConstants.LOOKUP_EXCLUDING_OPR_ATTRS_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
+
+        // As the target entry does not exist yet and so
+        // its subentry operational attributes are not there,
+        // we need to construct an entry to represent it
+        // at least with minimal requirements which are object class
+        // and access control subentry operational attributes.
+        SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) chain.get( SubentryInterceptor.class
+            .getSimpleName() );
+        Entry subentryAttrs = subentryInterceptor.getSubentryAttributes( newDn, importedEntry );
+
+        for ( Attribute attribute : importedEntry )
+        {
+            subentryAttrs.put( attribute );
+        }
+
+        Collection<ACITuple> destTuples = new HashSet<ACITuple>();
+        // Import permission is only valid for prescriptive ACIs
+        addPerscriptiveAciTuples( moveAndRenameContext, destTuples, newDn, subentryAttrs );
+
+        // Evaluate the target context to see whether it
+        // allows an entry named newName to be imported as a subordinate.
+        aciContext = new AciContext( schemaManager, moveAndRenameContext );
+        aciContext.setUserGroupNames( userGroups );
+        aciContext.setUserDn( principalDn );
+        aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+        aciContext.setEntryDn( newDn );
+        aciContext.setMicroOperations( IMPORT_PERMS );
+        aciContext.setAciTuples( destTuples );
+        aciContext.setEntry( subentryAttrs );
+
+        engine.checkPermission( aciContext );
+
+        next.moveAndRename( moveAndRenameContext );
+        tupleCache.subentryRenamed( oldDn, newDn );
+        groupCache.groupRenamed( oldDn, newDn );
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void move( NextInterceptor next, MoveOperationContext moveContext ) throws LdapException
+    {
+        Dn oriChildName = moveContext.getDn();
+
+        // Access the principal requesting the operation, and bypass checks if it is the admin
+        Entry entry = moveContext.getOriginalEntry();
+
+        Dn newDn = moveContext.getNewDn();
+
+        LdapPrincipal principal = moveContext.getSession().getEffectivePrincipal();
+        Dn principalDn = principal.getDn();
+
+        // bypass authz code if we are disabled
+        if ( !moveContext.getSession().getDirectoryService().isAccessControlEnabled() )
+        {
+            next.move( moveContext );
+            return;
+        }
+
+        protectCriticalEntries( oriChildName );
+
+        // bypass authz code but manage caches if operation is performed by the admin
+        if ( isPrincipalAnAdministrator( principalDn ) )
+        {
+            next.move( moveContext );
+            tupleCache.subentryRenamed( oriChildName, newDn );
+            groupCache.groupRenamed( oriChildName, newDn );
+            return;
+        }
+
+        Set<Dn> userGroups = groupCache.getGroups( principalDn.getNormName() );
+        Collection<ACITuple> tuples = new HashSet<ACITuple>();
+        addPerscriptiveAciTuples( moveContext, tuples, oriChildName, entry );
+        addEntryAciTuples( tuples, entry );
+        addSubentryAciTuples( moveContext, tuples, oriChildName, entry );
+
+        AciContext aciContext = new AciContext( schemaManager, moveContext );
+        aciContext.setUserGroupNames( userGroups );
+        aciContext.setUserDn( principalDn );
+        aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+        aciContext.setEntryDn( oriChildName );
+        aciContext.setMicroOperations( EXPORT_PERMS );
+        aciContext.setAciTuples( tuples );
+        aciContext.setEntry( entry );
+
+        engine.checkPermission( aciContext );
+
+        // Get the entry again without operational attributes
+        // because access control subentry operational attributes
+        // will not be valid at the new location.
+        // This will certainly be fixed by the SubentryInterceptor,
+        // but after this service.
+        Entry importedEntry = moveContext.lookup( oriChildName, ByPassConstants.LOOKUP_EXCLUDING_OPR_ATTRS_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
+
+        // As the target entry does not exist yet and so
+        // its subentry operational attributes are not there,
+        // we need to construct an entry to represent it
+        // at least with minimal requirements which are object class
+        // and access control subentry operational attributes.
+        SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) chain.get( SubentryInterceptor.class
+            .getSimpleName() );
+        Entry subentryAttrs = subentryInterceptor.getSubentryAttributes( newDn, importedEntry );
+
+        for ( Attribute attribute : importedEntry )
+        {
+            subentryAttrs.put( attribute );
+        }
+
+        Collection<ACITuple> destTuples = new HashSet<ACITuple>();
+        // Import permission is only valid for prescriptive ACIs
+        addPerscriptiveAciTuples( moveContext, destTuples, newDn, subentryAttrs );
+
+        // Evaluate the target context to see whether it
+        // allows an entry named newName to be imported as a subordinate.
+        aciContext = new AciContext( schemaManager, moveContext );
+        aciContext.setUserGroupNames( userGroups );
+        aciContext.setUserDn( principalDn );
+        aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+        aciContext.setEntryDn( newDn );
+        aciContext.setMicroOperations( IMPORT_PERMS );
+        aciContext.setAciTuples( destTuples );
+        aciContext.setEntry( subentryAttrs );
+
+        engine.checkPermission( aciContext );
+
+        next.move( moveContext );
+        tupleCache.subentryRenamed( oriChildName, newDn );
+        groupCache.groupRenamed( oriChildName, newDn );
+    }
+
+
+    public EntryFilteringCursor list( NextInterceptor next, ListOperationContext listContext ) throws LdapException
+    {
+        LdapPrincipal user = listContext.getSession().getEffectivePrincipal();
+        EntryFilteringCursor cursor = next.list( listContext );
+
+        if ( isPrincipalAnAdministrator( user.getDn() )
+            || !listContext.getSession().getDirectoryService().isAccessControlEnabled() )
+        {
+            return cursor;
+        }
+
+        AuthorizationFilter authzFilter = new AuthorizationFilter();
+        cursor.addEntryFilter( authzFilter );
+        return cursor;
+    }
+
+
+    public EntryFilteringCursor search( NextInterceptor next, SearchOperationContext searchContext ) throws LdapException
+    {
+        LdapPrincipal user = searchContext.getSession().getEffectivePrincipal();
+        Dn principalDn = user.getDn();
+        EntryFilteringCursor cursor = next.search( searchContext );
+
+        boolean isSubschemaSubentryLookup = subschemaSubentryDn.equals( searchContext.getDn().getNormName() );
+        SearchControls searchCtls = searchContext.getSearchControls();
+        boolean isRootDSELookup = searchContext.getDn().size() == 0
+            && searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE;
+
+        if ( isPrincipalAnAdministrator( principalDn )
+            || !searchContext.getSession().getDirectoryService().isAccessControlEnabled() || isRootDSELookup
+            || isSubschemaSubentryLookup )
+        {
+            return cursor;
+        }
+
+        cursor.addEntryFilter( new AuthorizationFilter() );
+        return cursor;
+    }
+
+
+    public final boolean isPrincipalAnAdministrator( Dn principalDn )
+    {
+        return groupCache.isPrincipalAnAdministrator( principalDn );
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean compare( NextInterceptor next, CompareOperationContext compareContext ) throws LdapException
+    {
+        CoreSession session = compareContext.getSession();
+        Dn dn = compareContext.getDn();
+        String oid = compareContext.getOid();
+        Value<?> value = compareContext.getValue();
+
+        Entry entry = compareContext.getOriginalEntry();
+
+        LdapPrincipal principal = session.getEffectivePrincipal();
+        Dn principalDn = principal.getDn();
+
+        if ( isPrincipalAnAdministrator( principalDn ) || !session.getDirectoryService().isAccessControlEnabled() )
+        {
+            return next.compare( compareContext );
+        }
+
+        Set<Dn> userGroups = groupCache.getGroups( principalDn.getNormName() );
+        Collection<ACITuple> tuples = new HashSet<ACITuple>();
+        addPerscriptiveAciTuples( compareContext, tuples, dn, entry );
+        addEntryAciTuples( tuples, entry );
+        addSubentryAciTuples( compareContext, tuples, dn, entry );
+
+        AciContext aciContext = new AciContext( schemaManager, compareContext );
+        aciContext.setUserGroupNames( userGroups );
+        aciContext.setUserDn( principalDn );
+        aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+        aciContext.setEntryDn( dn );
+        aciContext.setMicroOperations( READ_PERMS );
+        aciContext.setAciTuples( tuples );
+        aciContext.setEntry( entry );
+
+        engine.checkPermission( aciContext );
+
+        AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( oid );
+
+        aciContext = new AciContext( schemaManager, compareContext );
+        aciContext.setUserGroupNames( userGroups );
+        aciContext.setUserDn( principalDn );
+        aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+        aciContext.setEntryDn( dn );
+        aciContext.setAttributeType( attributeType );
+        aciContext.setMicroOperations( COMPARE_PERMS );
+        aciContext.setAciTuples( tuples );
+        aciContext.setEntry( entry );
+
+        engine.checkPermission( aciContext );
+
+        return next.compare( compareContext );
+    }
+
+
+    public void cacheNewGroup( Dn name, Entry entry ) throws Exception
+    {
+        groupCache.groupAdded( name, entry );
+    }
+
+
+    private boolean filter( OperationContext opContext, Dn normName, Entry clonedEntry ) throws Exception
+    {
+        /*
+         * First call hasPermission() for entry level "Browse" and "ReturnDN" perm
+         * tests.  If we hasPermission() returns false we immediately short the
+         * process and return false.
+         */
+
+        LdapPrincipal principal = opContext.getSession().getEffectivePrincipal();
+        Dn userDn = principal.getDn();
+        Set<Dn> userGroups = groupCache.getGroups( userDn.getNormName() );
+        Collection<ACITuple> tuples = new HashSet<ACITuple>();
+        addPerscriptiveAciTuples( opContext, tuples, normName, clonedEntry );
+        addEntryAciTuples( tuples, ((ClonedServerEntry)clonedEntry).getOriginalEntry() );
+        addSubentryAciTuples( opContext, tuples, normName, ((ClonedServerEntry)clonedEntry).getOriginalEntry() );
+
+        AciContext aciContext = new AciContext( schemaManager, opContext );
+        aciContext.setUserGroupNames( userGroups );
+        aciContext.setUserDn( userDn );
+        aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+        aciContext.setEntryDn( normName );
+        aciContext.setMicroOperations( SEARCH_ENTRY_PERMS );
+        aciContext.setAciTuples( tuples );
+        aciContext.setEntry( ((ClonedServerEntry)clonedEntry).getOriginalEntry() );
+
+        if ( !engine.hasPermission( aciContext ) )
+        {
+            return false;
+        }
+
+        /*
+         * For each attribute type we check if access is allowed to the type.  If not
+         * the attribute is yanked out of the entry to be returned.  If permission is
+         * allowed we move on to check if the values are allowed.  Values that are
+         * not allowed are removed from the attribute.  If the attribute has no more
+         * values remaining then the entire attribute is removed.
+         */
+        List<AttributeType> attributeToRemove = new ArrayList<AttributeType>();
+
+        for ( Attribute attribute : clonedEntry.getAttributes() )
+        {
+            // if attribute type scope access is not allowed then remove the attribute and continue
+            AttributeType attributeType = attribute.getAttributeType();
+            Attribute attr = clonedEntry.get( attributeType );
+
+            aciContext = new AciContext( schemaManager, opContext );
+            aciContext.setUserGroupNames( userGroups );
+            aciContext.setUserDn( userDn );
+            aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+            aciContext.setEntryDn( normName );
+            aciContext.setAttributeType( attributeType );
+            aciContext.setMicroOperations( SEARCH_ATTRVAL_PERMS );
+            aciContext.setAciTuples( tuples );
+            aciContext.setEntry( clonedEntry );
+
+            if ( !engine.hasPermission( aciContext ) )
+            {
+                attributeToRemove.add( attributeType );
+
+                continue;
+            }
+
+            List<Value<?>> valueToRemove = new ArrayList<Value<?>>();
+
+            // attribute type scope is ok now let's determine value level scope
+            for ( Value<?> value : attr )
+            {
+                aciContext = new AciContext( schemaManager, opContext );
+                aciContext.setUserGroupNames( userGroups );
+                aciContext.setUserDn( userDn );
+                aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
+                aciContext.setEntryDn( normName );
+                aciContext.setAttributeType( attr.getAttributeType() );
+                aciContext.setAttrValue( value );
+                aciContext.setMicroOperations( SEARCH_ATTRVAL_PERMS );
+                aciContext.setAciTuples( tuples );
+                aciContext.setEntry( clonedEntry );
+
+                if ( !engine.hasPermission( aciContext ) )
+                {
+                    valueToRemove.add( value );
+                }
+            }
+
+            for ( Value<?> value : valueToRemove )
+            {
+                attr.remove( value );
+            }
+
+            if ( attr.size() == 0 )
+            {
+                attributeToRemove.add( attributeType );
+            }
+        }
+
+        for ( AttributeType attributeType : attributeToRemove )
+        {
+            clonedEntry.removeAttributes( attributeType );
+        }
+
+        return true;
+    }
+
+    
+    /**
+     * WARNING: create one of these filters fresh every time for each new search.
+     */
+    private class AuthorizationFilter implements EntryFilter
+    {
+        public boolean accept( SearchingOperationContext searchContext, Entry entry ) throws Exception
+        {
+            Dn normName = entry.getDn().apply( schemaManager );
+            
+            return filter( searchContext, normName, entry );
+        }
+    }
+}