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 [2/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...

Added: directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/DefaultAuthorizationInterceptor.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/DefaultAuthorizationInterceptor.java?rev=1183441&view=auto
==============================================================================
--- directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/DefaultAuthorizationInterceptor.java (added)
+++ directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/DefaultAuthorizationInterceptor.java Fri Oct 14 17:38:30 2011
@@ -0,0 +1,524 @@
+/*
+ *  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.util.HashSet;
+import java.util.Set;
+
+import javax.naming.NoPermissionException;
+
+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.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.Interceptor;
+import org.apache.directory.server.core.api.interceptor.NextInterceptor;
+import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
+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.PartitionNexus;
+import org.apache.directory.server.i18n.I18n;
+import org.apache.directory.shared.ldap.model.constants.AuthenticationLevel;
+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.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.name.Dn;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * An {@link Interceptor} that controls access to {@link DefaultPartitionNexus}.
+ * If a user tries to perform any operations that requires
+ * permission he or she doesn't have, {@link NoPermissionException} will be
+ * thrown and therefore the current invocation chain will terminate.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class DefaultAuthorizationInterceptor extends BaseInterceptor
+{
+    /** the logger for this class */
+    private static final Logger LOG = LoggerFactory.getLogger( DefaultAuthorizationInterceptor.class );
+
+    /** the base distinguished {@link Name} for the admin system */
+    private static Dn ADMIN_SYSTEM_DN;
+
+    /** the base distinguished {@link Name} for all groups */
+    private static Dn GROUP_BASE_DN;
+
+    /** the distinguished {@link Name} for the administrator group */
+    private static Dn ADMIN_GROUP_DN;
+
+    private Set<String> administrators = new HashSet<String>( 2 );
+
+    private PartitionNexus nexus;
+
+    /**
+     * the search result filter to use for collective attribute injection
+     */
+    private class DefaultAuthorizationSearchFilter implements EntryFilter
+    {
+        public boolean accept( SearchingOperationContext operation, Entry entry ) throws Exception
+        {
+            return DefaultAuthorizationInterceptor.this.isSearchable( operation, entry );
+        }
+    }
+
+    
+    /**
+     * Creates a new instance.
+     */
+    public DefaultAuthorizationInterceptor()
+    {
+        // Nothing to do
+    }
+
+
+    public void init( DirectoryService directoryService ) throws LdapException
+    {
+        super.init( directoryService );
+
+        nexus = directoryService.getPartitionNexus();
+
+        ADMIN_SYSTEM_DN = directoryService.getDnFactory().create( ServerDNConstants.ADMIN_SYSTEM_DN );
+
+        GROUP_BASE_DN = directoryService.getDnFactory().create( ServerDNConstants.GROUPS_SYSTEM_DN );
+
+        ADMIN_GROUP_DN = directoryService.getDnFactory().create( ServerDNConstants.ADMINISTRATORS_GROUP_DN );
+
+        loadAdministrators( directoryService );
+    }
+
+
+    private void loadAdministrators( DirectoryService directoryService ) throws LdapException
+    {
+        // read in the administrators and cache their normalized names
+        Set<String> newAdministrators = new HashSet<String>( 2 );
+        Dn adminDn = directoryService.getDnFactory().create( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
+        CoreSession adminSession = new DefaultCoreSession( new LdapPrincipal( schemaManager, adminDn, AuthenticationLevel.STRONG ),
+            directoryService );
+
+        Entry adminGroup = nexus.lookup( new LookupOperationContext( adminSession, ADMIN_GROUP_DN ) );
+
+        if ( adminGroup == null )
+        {
+            return;
+        }
+
+        Attribute uniqueMember = adminGroup.get( UNIQUE_MEMBER_AT );
+
+        for ( Value<?> value : uniqueMember )
+        {
+            Dn memberDn = directoryService.getDnFactory().create( value.getString() );
+            newAdministrators.add( memberDn.getNormName() );
+        }
+
+        administrators = newAdministrators;
+    }
+
+
+    // Note:
+    //    Lookup, search and list operations need to be handled using a filter
+    // and so we need access to the filter service.
+
+    public void delete( NextInterceptor nextInterceptor, DeleteOperationContext deleteContext ) throws LdapException
+    {
+        if ( deleteContext.getSession().getDirectoryService().isAccessControlEnabled() )
+        {
+            nextInterceptor.delete( deleteContext );
+            return;
+        }
+
+        Dn dn = deleteContext.getDn();
+
+        if ( dn.isEmpty() )
+        {
+            String msg = I18n.err( I18n.ERR_12 );
+            LOG.error( msg );
+            throw new LdapNoPermissionException( msg );
+        }
+
+        if ( dn.equals( ADMIN_GROUP_DN ) )
+        {
+            String msg = I18n.err( I18n.ERR_13 );
+            LOG.error( msg );
+            throw new LdapNoPermissionException( msg );
+        }
+
+        Dn principalDn = getPrincipal().getDn();
+
+        if ( dn.equals( ADMIN_SYSTEM_DN ) )
+        {
+            String msg = I18n.err( I18n.ERR_14, principalDn.getName() );
+            LOG.error( msg );
+            throw new LdapNoPermissionException( msg );
+        }
+
+        if ( dn.size() > 2 && !isAnAdministrator(principalDn) )
+        {
+            if ( dn.isDescendantOf( ADMIN_SYSTEM_DN ) )
+            {
+                String msg = I18n.err( I18n.ERR_15, principalDn.getName(), dn.getName() );
+                LOG.error( msg );
+                throw new LdapNoPermissionException( msg );
+            }
+
+            if ( dn.isDescendantOf( GROUP_BASE_DN ) )
+            {
+                String msg = I18n.err( I18n.ERR_16, principalDn.getName(), dn.getName() );
+                LOG.error( msg );
+                throw new LdapNoPermissionException( msg );
+            }
+        }
+
+        nextInterceptor.delete( deleteContext );
+    }
+
+
+    private boolean isTheAdministrator( Dn dn )
+    {
+        return dn.equals( ADMIN_SYSTEM_DN );
+    }
+
+
+    private boolean isAnAdministrator( Dn dn )
+    {
+        return isTheAdministrator( dn ) || administrators.contains( dn.getNormName() );
+    }
+
+
+    // ------------------------------------------------------------------------
+    // Entry Modification Operations
+    // ------------------------------------------------------------------------
+
+    /**
+     * This policy needs to be really tight too because some attributes may take
+     * part in giving the user permissions to protected resources.  We do not want
+     * users to self access these resources.  As far as we're concerned no one but
+     * the admin needs access.
+     */
+    public void modify( NextInterceptor nextInterceptor, ModifyOperationContext modifyContext ) throws LdapException
+    {
+        if ( !modifyContext.getSession().getDirectoryService().isAccessControlEnabled() )
+        {
+            Dn dn = modifyContext.getDn();
+
+            protectModifyAlterations( dn );
+            nextInterceptor.modify( modifyContext );
+
+            // update administrators if we change administrators group
+            if ( dn.equals( ADMIN_GROUP_DN ) )
+            {
+                loadAdministrators( modifyContext.getSession().getDirectoryService() );
+            }
+        }
+        else
+        {
+            nextInterceptor.modify( modifyContext );
+        }
+    }
+
+
+    private void protectModifyAlterations( Dn dn ) throws LdapException
+    {
+        Dn principalDn = getPrincipal().getDn();
+
+        if ( dn.isEmpty() )
+        {
+            String msg = I18n.err( I18n.ERR_17 );
+            LOG.error( msg );
+            throw new LdapNoPermissionException( msg );
+        }
+
+        if ( !isAnAdministrator( principalDn ) )
+        {
+            // allow self modifications
+            if ( dn.equals( getPrincipal() ) )
+            {
+                return;
+            }
+
+            if ( dn.equals( ADMIN_SYSTEM_DN ) )
+            {
+                String msg = I18n.err( I18n.ERR_18, principalDn.getName() );
+                LOG.error( msg );
+                throw new LdapNoPermissionException( msg );
+            }
+
+            if ( dn.size() > 2 )
+            {
+                if ( dn.isDescendantOf( ADMIN_SYSTEM_DN ) )
+                {
+                    String msg = I18n.err( I18n.ERR_19, principalDn.getName(), dn.getName() );
+                    LOG.error( msg );
+                    throw new LdapNoPermissionException( msg );
+                }
+
+                if ( dn.isDescendantOf( GROUP_BASE_DN ) )
+                {
+                    String msg = I18n.err( I18n.ERR_20, principalDn.getName(), dn.getName() );
+                    LOG.error( msg );
+                    throw new LdapNoPermissionException( msg );
+                }
+            }
+        }
+    }
+
+
+    // ------------------------------------------------------------------------
+    // Dn altering operations are a no no for any user entry.  Basically here
+    // are the rules of conduct to follow:
+    //
+    //  o No user should have the ability to move or rename their entry
+    //  o Only the administrator can move or rename non-admin user entries
+    //  o The administrator entry cannot be moved or renamed by anyone
+    // ------------------------------------------------------------------------
+
+    public void rename( NextInterceptor nextInterceptor, RenameOperationContext renameContext ) throws LdapException
+    {
+        if ( !renameContext.getSession().getDirectoryService().isAccessControlEnabled() )
+        {
+            protectDnAlterations( renameContext.getDn() );
+        }
+
+        nextInterceptor.rename( renameContext );
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void move( NextInterceptor nextInterceptor, MoveOperationContext moveContext ) throws LdapException
+    {
+        if ( !moveContext.getSession().getDirectoryService().isAccessControlEnabled() )
+        {
+            protectDnAlterations( moveContext.getDn() );
+        }
+
+        nextInterceptor.move( moveContext );
+    }
+
+
+    public void moveAndRename( NextInterceptor nextInterceptor, MoveAndRenameOperationContext moveAndRenameContext )
+        throws LdapException
+    {
+        if ( !moveAndRenameContext.getSession().getDirectoryService().isAccessControlEnabled() )
+        {
+            protectDnAlterations( moveAndRenameContext.getDn() );
+        }
+
+        nextInterceptor.moveAndRename( moveAndRenameContext );
+    }
+
+
+    private void protectDnAlterations( Dn dn ) throws LdapException
+    {
+        Dn principalDn = getPrincipal().getDn();
+
+        if ( dn.isEmpty() )
+        {
+            String msg = I18n.err( I18n.ERR_234 );
+            LOG.error( msg );
+            throw new LdapNoPermissionException( msg );
+        }
+
+        if ( dn.equals( ADMIN_GROUP_DN ) )
+        {
+            String msg = I18n.err( I18n.ERR_21 );
+            LOG.error( msg );
+            throw new LdapNoPermissionException( msg );
+        }
+
+        if ( isTheAdministrator( dn ) )
+        {
+            String msg = I18n.err( I18n.ERR_22, principalDn.getName(), dn.getName() );
+            LOG.error( msg );
+            throw new LdapNoPermissionException( msg );
+        }
+
+        if ( ( dn.size() > 2 ) && dn.isDescendantOf( ADMIN_SYSTEM_DN ) && !isAnAdministrator( principalDn ) )
+        {
+            String msg = I18n.err( I18n.ERR_23, principalDn.getName(), dn.getName() );
+            LOG.error( msg );
+            throw new LdapNoPermissionException( msg );
+        }
+
+        if ( ( dn.size() > 2 ) && dn.isDescendantOf( GROUP_BASE_DN ) && !isAnAdministrator( principalDn ) )
+        {
+            String msg = I18n.err( I18n.ERR_24, principalDn.getName(), dn.getName() );
+            LOG.error( msg );
+            throw new LdapNoPermissionException( msg );
+        }
+    }
+
+
+    public Entry lookup( NextInterceptor nextInterceptor, LookupOperationContext lookupContext ) throws LdapException
+    {
+        CoreSession session = lookupContext.getSession();
+        Entry entry = nextInterceptor.lookup( lookupContext );
+
+        if ( session.getDirectoryService().isAccessControlEnabled() )
+        {
+            return entry;
+        }
+
+        protectLookUp( session.getEffectivePrincipal().getDn(), lookupContext.getDn() );
+
+        return entry;
+    }
+
+
+    private void protectLookUp( Dn principalDn, Dn normalizedDn ) throws LdapException
+    {
+        if ( !isAnAdministrator( principalDn ) )
+        {
+            if ( normalizedDn.size() > 2 )
+            {
+                if ( normalizedDn.isDescendantOf( ADMIN_SYSTEM_DN ) )
+                {
+                    // allow for self reads
+                    if ( normalizedDn.getNormName().equals( principalDn.getNormName() ) )
+                    {
+                        return;
+                    }
+
+                    String msg = I18n.err( I18n.ERR_25, normalizedDn.getName(), principalDn.getName() );
+                    LOG.error( msg );
+                    throw new LdapNoPermissionException( msg );
+                }
+
+                if ( normalizedDn.isDescendantOf( GROUP_BASE_DN ) )
+                {
+                    // allow for self reads
+                    if ( normalizedDn.equals( principalDn ) )
+                    {
+                        return;
+                    }
+
+                    String msg = I18n.err( I18n.ERR_26, normalizedDn.getName(), principalDn.getName() );
+                    LOG.error( msg );
+                    throw new LdapNoPermissionException( msg );
+                }
+            }
+
+            if ( isTheAdministrator( normalizedDn ) )
+            {
+                // allow for self reads
+                if ( normalizedDn.getNormName().equals( principalDn.getNormName() ) )
+                {
+                    return;
+                }
+
+                String msg = I18n.err( I18n.ERR_27, principalDn.getName() );
+                LOG.error( msg );
+                throw new LdapNoPermissionException( msg );
+            }
+        }
+    }
+
+
+    public EntryFilteringCursor search( NextInterceptor nextInterceptor, SearchOperationContext searchContext )
+        throws LdapException
+    {
+        EntryFilteringCursor cursor = nextInterceptor.search( searchContext );
+
+        if ( searchContext.getSession().getDirectoryService().isAccessControlEnabled() )
+        {
+            return cursor;
+        }
+
+        cursor.addEntryFilter( new DefaultAuthorizationSearchFilter() );
+
+        return cursor;
+    }
+
+
+    public EntryFilteringCursor list( NextInterceptor nextInterceptor, ListOperationContext listContext )
+        throws LdapException
+    {
+        EntryFilteringCursor cursor = nextInterceptor.list( listContext );
+
+        if ( listContext.getSession().getDirectoryService().isAccessControlEnabled() )
+        {
+            return cursor;
+        }
+
+        cursor.addEntryFilter( new DefaultAuthorizationSearchFilter() );
+        return cursor;
+    }
+
+
+    // False positive, we want to keep the comment
+    @SuppressWarnings("PMD.CollapsibleIfStatements")
+    private boolean isSearchable( OperationContext opContext, Entry entry ) throws Exception
+    {
+        Dn principalDn = opContext.getSession().getEffectivePrincipal().getDn();
+        Dn dn = entry.getDn();
+
+        if ( !dn.isSchemaAware() )
+        {
+            dn.apply( opContext.getSession().getDirectoryService().getSchemaManager() );
+        }
+
+        // Admin users gets full access to all entries
+        if ( isAnAdministrator( principalDn ) )
+        {
+            return true;
+        }
+
+        // Users reading their own entries should be allowed to see all
+        boolean isSelfRead = dn.equals( principalDn );
+
+        if ( isSelfRead )
+        {
+            return true;
+        }
+
+        // Block off reads to anything under ou=users and ou=groups if not a self read
+        if ( dn.size() > 2 )
+        {
+            // stuff this if in here instead of up in outer if to prevent
+            // constant needless reexecution for all entries in other depths
+
+            if ( dn.isDescendantOf( ADMIN_SYSTEM_DN ) || dn.isDescendantOf( GROUP_BASE_DN ) )
+            {
+                return false;
+            }
+        }
+
+        // Non-admin users cannot read the admin entry
+        return !isTheAdministrator( dn );
+
+    }
+}

Added: directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/GroupCache.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/GroupCache.java?rev=1183441&view=auto
==============================================================================
--- directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/GroupCache.java (added)
+++ directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/GroupCache.java Fri Oct 14 17:38:30 2011
@@ -0,0 +1,581 @@
+/*
+ *  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.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.naming.directory.SearchControls;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.Element;
+
+import org.apache.directory.server.constants.ServerDNConstants;
+import org.apache.directory.server.core.api.CoreSession;
+import org.apache.directory.server.core.api.DirectoryService;
+import org.apache.directory.server.core.api.DnFactory;
+import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
+import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
+import org.apache.directory.server.core.api.partition.PartitionNexus;
+import org.apache.directory.server.i18n.I18n;
+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.ModificationOperation;
+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.LdapOperationException;
+import org.apache.directory.shared.ldap.model.filter.BranchNode;
+import org.apache.directory.shared.ldap.model.filter.EqualityNode;
+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.SchemaManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * A cache for tracking static group membership.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class GroupCache
+{
+    /** the logger for this class */
+    private static final Logger LOG = LoggerFactory.getLogger( GroupCache.class );
+
+    /** Speedup for logs */
+    private static final boolean IS_DEBUG = LOG.isDebugEnabled();
+
+    /** a handle on the partition nexus */
+    private final PartitionNexus nexus;
+
+    /** A storage for the ObjectClass attributeType */
+    private AttributeType OBJECT_CLASS_AT;
+
+    /** A storage for the member attributeType */
+    private AttributeType MEMBER_AT;
+
+    /** A storage for the uniqueMember attributeType */
+    private AttributeType UNIQUE_MEMBER_AT;
+
+    /**
+     * the schema manager
+     */
+    private SchemaManager schemaManager;
+    
+    /** the Dn factory */
+    private DnFactory dnFactory;
+
+    /** the normalized dn of the administrators group */
+    private Dn administratorsGroupDn;
+
+    private static final Set<Dn> EMPTY_GROUPS = new HashSet<Dn>();
+
+    /** String key for the Dn of a group to a Set (HashSet) for the Strings of member DNs */
+    private Cache ehCache;
+
+    /**
+     * Creates a static group cache.
+     *
+     * @param dirService the directory service core
+     * @throws LdapException if there are failures on initialization
+     */
+    public GroupCache( DirectoryService dirService ) throws LdapException
+    {
+        schemaManager = dirService.getSchemaManager();
+        dnFactory = dirService.getDnFactory();
+        nexus = dirService.getPartitionNexus();
+        OBJECT_CLASS_AT = schemaManager.getAttributeType( SchemaConstants.OBJECT_CLASS_AT );
+        MEMBER_AT = schemaManager.getAttributeType( SchemaConstants.MEMBER_AT );
+        UNIQUE_MEMBER_AT = schemaManager.getAttributeType( SchemaConstants.UNIQUE_MEMBER_AT );
+
+        // stuff for dealing with the admin group
+        administratorsGroupDn = parseNormalized( ServerDNConstants.ADMINISTRATORS_GROUP_DN );
+
+        this.ehCache = dirService.getCacheService().getCache( "groupCache" );
+        
+        initialize( dirService.getAdminSession() );
+    }
+
+
+    private Dn parseNormalized( String name ) throws LdapException
+    {
+        Dn dn = dnFactory.create( name );
+        return dn;
+    }
+
+
+    private void initialize( CoreSession session ) throws LdapException
+    {
+        // search all naming contexts for static groups and generate
+        // normalized sets of members to cache within the map
+
+        Set<String> suffixes = nexus.listSuffixes();
+
+        for ( String suffix:suffixes )
+        {
+            // moving the filter creation to inside loop to fix DIRSERVER-1121
+            // didn't use clone() cause it is creating List objects, which IMO is not worth calling
+            // in this initialization phase
+            BranchNode filter = new OrNode();
+            filter.addNode( new EqualityNode<String>( OBJECT_CLASS_AT, new StringValue(
+                SchemaConstants.GROUP_OF_NAMES_OC ) ) );
+            filter.addNode( new EqualityNode<String>( OBJECT_CLASS_AT, new StringValue(
+                SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC ) ) );
+
+            Dn baseDn = dnFactory.create( suffix );
+            SearchControls ctls = new SearchControls();
+            ctls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+            ctls.setReturningAttributes( new String[]{ "*", "+" } );
+
+            SearchOperationContext searchOperationContext = new SearchOperationContext( session,
+                baseDn, filter, ctls );
+            searchOperationContext.setAliasDerefMode( AliasDerefMode.DEREF_ALWAYS );
+            EntryFilteringCursor results = nexus.search( searchOperationContext );
+
+            try
+            {
+                while ( results.next() )
+                {
+                    Entry result = results.get();
+                    Dn groupDn = result.getDn().apply( schemaManager );
+                    Attribute members = getMemberAttribute( result );
+    
+                    if ( members != null )
+                    {
+                        Set<String> memberSet = new HashSet<String>( members.size() );
+                        addMembers( memberSet, members );
+                        
+                        Element cacheElement = new Element( groupDn.getNormName(), memberSet );
+                        ehCache.put( cacheElement );
+                    }
+                    else
+                    {
+                        LOG.warn( "Found group '{}' without any member or uniqueMember attributes", groupDn.getName() );
+                    }
+                }
+    
+                results.close();
+            }
+            catch ( Exception e )
+            {
+                LdapOperationException le = new LdapOperationException( e.getMessage(), e );
+                le.initCause( e );
+                throw le;
+            }
+        }
+
+        if ( IS_DEBUG )
+        {
+            LOG.debug( "group cache contents on startup:\n {}", ehCache.getAllWithLoader( ehCache.getKeys(), null ) );
+        }
+    }
+
+
+    /**
+     * Gets the member attribute regardless of whether groupOfNames or
+     * groupOfUniqueNames is used.
+     *
+     * @param entry the entry inspected for member attributes
+     * @return the member attribute
+     */
+    private Attribute getMemberAttribute( Entry entry ) throws LdapException
+    {
+        Attribute member = entry.get( MEMBER_AT );
+
+        if ( member != null )
+        {
+            return member;
+        }
+
+        Attribute uniqueMember = entry.get( UNIQUE_MEMBER_AT );
+
+        if ( uniqueMember != null )
+        {
+            return uniqueMember;
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Adds normalized member DNs to the set of normalized member names.
+     *
+     * @param memberSet the set of member Dns (Strings)
+     * @param members the member attribute values being added
+     * @throws LdapException if there are problems accessing the attr values
+     */
+    private void addMembers( Set<String> memberSet, Attribute members ) throws LdapException
+    {
+        for ( Value<?> value : members )
+        {
+
+            // get and normalize the Dn of the member
+            String memberDn = value.getString();
+
+            try
+            {
+                memberDn = parseNormalized( memberDn ).getNormName();
+            }
+            catch ( LdapException e )
+            {
+                LOG.warn( "Malformed member Dn in groupOf[Unique]Names entry.  Member not added to GroupCache.", e );
+            }
+
+            memberSet.add( memberDn );
+        }
+    }
+
+
+    /**
+     * Removes a set of member names from an existing set.
+     *
+     * @param memberSet the set of normalized member DNs
+     * @param members the set of member values
+     * @throws LdapException if there are problems accessing the attr values
+     */
+    private void removeMembers( Set<String> memberSet, Attribute members ) throws LdapException
+    {
+        for ( Value<?> value : members )
+        {
+            // get and normalize the Dn of the member
+            String memberDn = value.getString();
+
+            try
+            {
+                memberDn = parseNormalized( memberDn ).getNormName();
+            }
+            catch ( LdapException e )
+            {
+                LOG.warn( "Malformed member Dn in groupOf[Unique]Names entry.  Member not removed from GroupCache.", e );
+            }
+
+            memberSet.remove( memberDn );
+        }
+    }
+
+
+    /**
+     * Adds a groups members to the cache.  Called by interceptor to account for new
+     * group additions.
+     *
+     * @param name the user provided name for the group entry
+     * @param entry the group entry's attributes
+     * @throws LdapException if there are problems accessing the attr values
+     */
+    public void groupAdded( Dn name, Entry entry ) throws LdapException
+    {
+        Attribute members = getMemberAttribute( entry );
+
+        if ( members == null )
+        {
+            return;
+        }
+
+        Set<String> memberSet = new HashSet<String>( members.size() );
+        addMembers( memberSet, members );
+        
+        Element cacheElement = new Element( name.getNormName(), memberSet );
+        ehCache.put( cacheElement );
+
+        if ( IS_DEBUG )
+        {
+            LOG.debug( "group cache contents after adding '{}' :\n {}", name.getName(), ehCache.getAllWithLoader( ehCache.getKeys(), null ) );
+        }
+    }
+
+
+    /**
+     * Deletes a group's members from the cache.  Called by interceptor to account for
+     * the deletion of groups.
+     *
+     * @param name the normalized Dn of the group entry
+     * @param entry the attributes of entry being deleted
+     */
+    public void groupDeleted( Dn name, Entry entry ) throws LdapException
+    {
+        Attribute members = getMemberAttribute( entry );
+
+        if ( members == null )
+        {
+            return;
+        }
+
+        ehCache.remove( name.getNormName() );
+
+        if ( IS_DEBUG )
+        {
+            LOG.debug( "group cache contents after deleting '{}' :\n {}", name.getName(), ehCache.getAllWithLoader( ehCache.getKeys(), null ) );
+        }
+    }
+
+
+    /**
+     * Utility method to modify a set of member names based on a modify operation
+     * that changes the members of a group.
+     *
+     * @param memberSet the set of members to be altered
+     * @param modOp the type of modify operation being performed
+     * @param members the members being added, removed or replaced
+     * @throws LdapException if there are problems accessing attribute values
+     */
+    private void modify( Set<String> memberSet, ModificationOperation modOp, Attribute members )
+        throws LdapException
+    {
+
+        switch ( modOp )
+        {
+            case ADD_ATTRIBUTE:
+                addMembers( memberSet, members );
+                break;
+
+            case REPLACE_ATTRIBUTE:
+                if ( members.size() > 0 )
+                {
+                    memberSet.clear();
+                    addMembers( memberSet, members );
+                }
+
+                break;
+
+            case REMOVE_ATTRIBUTE:
+                removeMembers( memberSet, members );
+                break;
+
+            default:
+                throw new InternalError( I18n.err( I18n.ERR_235, modOp ) );
+        }
+    }
+
+
+    /**
+     * Modifies the cache to reflect changes via modify operations to the group entries.
+     * Called by the interceptor to account for modify ops on groups.
+     *
+     * @param name the normalized name of the group entry modified
+     * @param mods the modification operations being performed
+     * @param entry the group entry being modified
+     * @throws LdapException if there are problems accessing attribute  values
+     */
+    public void groupModified( Dn name, List<Modification> mods, Entry entry, SchemaManager schemaManager )
+        throws LdapException
+    {
+        Attribute members = null;
+        AttributeType memberAttr = null;
+        Attribute oc = entry.get( OBJECT_CLASS_AT );
+
+        if ( oc.contains( SchemaConstants.GROUP_OF_NAMES_OC ) )
+        {
+            members = entry.get( MEMBER_AT );
+            memberAttr = schemaManager.getAttributeType( SchemaConstants.MEMBER_AT );
+        }
+
+        if ( oc.contains( SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC ) )
+        {
+            members = entry.get( UNIQUE_MEMBER_AT );
+            memberAttr = schemaManager.getAttributeType( SchemaConstants.UNIQUE_MEMBER_AT );
+        }
+
+        if ( members == null )
+        {
+            return;
+        }
+
+        for ( Modification modification : mods )
+        {
+            if ( memberAttr.getOid() == modification.getAttribute().getId() )
+            {
+                Element memSetElement = ehCache.get( name.getNormName() );
+                
+                if ( memSetElement != null )
+                {
+                    Set<String> memberSet = ( Set<String> ) memSetElement.getValue();
+                    modify( memberSet, modification.getOperation(), modification.getAttribute() );
+                }
+
+                break;
+            }
+        }
+
+        if ( IS_DEBUG )
+        {
+            LOG.debug( "group cache contents after modifying '{}' :\n {}", name.getName(), ehCache.getAllWithLoader( ehCache.getKeys(), null ) );
+        }
+    }
+
+
+    /**
+     * Modifies the cache to reflect changes via modify operations to the group entries.
+     * Called by the interceptor to account for modify ops on groups.
+     *
+     * @param name the normalized name of the group entry modified
+     * @param modOp the modify operation being performed
+     * @param mods the modifications being performed
+     * @throws LdapException if there are problems accessing attribute  values
+     */
+    public void groupModified( Dn name, ModificationOperation modOp, Entry mods ) throws LdapException
+    {
+        Attribute members = getMemberAttribute( mods );
+
+        if ( members == null )
+        {
+            return;
+        }
+
+        Element memSetElement = ehCache.get( name.getNormName() );
+
+        if ( memSetElement != null )
+        {
+            Set<String> memberSet = ( Set<String> ) memSetElement.getValue();
+            modify( memberSet, modOp, members );
+        }
+
+        if ( IS_DEBUG )
+        {
+            LOG.debug( "group cache contents after modifying '{}' :\n {}", name.getName(), ehCache.getAllWithLoader( ehCache.getKeys(), null ) );
+        }
+    }
+
+
+    /**
+     * An optimization.  By having this method here we can directly access the group
+     * membership information and lookup to see if the principalDn is contained within.
+     *
+     * @param principalDn the normalized Dn of the user to check if they are an admin
+     * @return true if the principal is an admin or the admin
+     */
+    public final boolean isPrincipalAnAdministrator( Dn principalDn )
+    {
+        if ( principalDn.getNormName().equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED ) )
+        {
+            return true;
+        }
+
+        Element cacheElement = ehCache.get( administratorsGroupDn.getNormName() );
+        
+        if ( cacheElement == null )
+        {
+            LOG.warn( "What do you mean there is no administrators group? This is bad news." );
+            return false;
+        }
+        else
+        {
+            Set<String> members = ( Set<String> ) cacheElement.getValue();            
+            return members.contains( principalDn.getNormName() );
+        }
+    }
+
+
+    /**
+     * Gets the set of groups a user is a member of.  The groups are returned
+     * as normalized Name objects within the set.
+     *
+     * @param member the member (user) to get the groups for
+     * @return a Set of Name objects representing the groups
+     * @throws LdapException if there are problems accessing attribute  values
+     */
+    public Set<Dn> getGroups( String member ) throws LdapException
+    {
+        Dn normMember;
+
+        try
+        {
+            normMember = parseNormalized( member );
+        }
+        catch ( LdapException e )
+        {
+            LOG
+                .warn(
+                    "Malformed member Dn.  Could not find groups for member '{}' in GroupCache. Returning empty set for groups!",
+                    member, e );
+            return EMPTY_GROUPS;
+        }
+
+        Set<Dn> memberGroups = null;
+
+        for ( Object obj : ehCache.getKeys() )
+        {
+            String group = ( String ) obj;
+            Element element = ehCache.get( group );
+            
+            if ( element == null )
+            {
+                continue;
+            }
+            
+            Set<String> members = ( Set<String> ) element.getValue();
+
+            if ( members == null )
+            {
+                continue;
+            }
+
+            if ( members.contains( normMember.getNormName() ) )
+            {
+                if ( memberGroups == null )
+                {
+                    memberGroups = new HashSet<Dn>();
+                }
+
+                memberGroups.add( parseNormalized( group ) );
+            }
+        }
+
+        if ( memberGroups == null )
+        {
+            return EMPTY_GROUPS;
+        }
+
+        return memberGroups;
+    }
+
+
+    public boolean groupRenamed( Dn oldName, Dn newName )
+    {
+        Element membersElement = ehCache.get( oldName.getNormName() );
+        
+        if ( membersElement != null )
+        {
+            Set<String> members = ( Set<String> ) membersElement.getValue();
+            
+            ehCache.remove( oldName.getNormName() );
+            
+            Element cacheElement = new Element( newName.getNormName(), members );
+            ehCache.put( cacheElement );
+
+            if ( IS_DEBUG )
+            {
+                LOG.debug( "group cache contents after renaming '{}' :\n{}", oldName.getName(), ehCache.getAllWithLoader( ehCache.getKeys(), null ) );
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+}

Added: directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/TupleCache.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/TupleCache.java?rev=1183441&view=auto
==============================================================================
--- directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/TupleCache.java (added)
+++ directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/TupleCache.java Fri Oct 14 17:38:30 2011
@@ -0,0 +1,299 @@
+/*
+ *  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.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.naming.directory.SearchControls;
+
+import org.apache.directory.server.core.api.CoreSession;
+import org.apache.directory.server.core.api.DnFactory;
+import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
+import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
+import org.apache.directory.server.core.api.partition.PartitionNexus;
+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.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.LdapOperationErrorException;
+import org.apache.directory.shared.ldap.model.exception.LdapSchemaViolationException;
+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.message.AliasDerefMode;
+import org.apache.directory.shared.ldap.model.message.ResultCodeEnum;
+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.SchemaManager;
+import org.apache.directory.shared.ldap.model.schema.normalizers.ConcreteNameComponentNormalizer;
+import org.apache.directory.shared.ldap.model.schema.normalizers.NameComponentNormalizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * A cache for tuple sets which responds to specific events to perform
+ * cache house keeping as access control subentries are added, deleted
+ * and modified.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class TupleCache
+{
+    /** the logger for this class */
+    private static final Logger LOG = LoggerFactory.getLogger( TupleCache.class );
+
+    /** a map of strings to ACITuple collections */
+    private final Map<String, List<ACITuple>> tuples = new HashMap<String, List<ACITuple>>();
+
+    /** the Dn factory */
+    private final DnFactory dnFactory;
+
+    /** a handle on the partition nexus */
+    private final PartitionNexus nexus;
+
+    /** a normalizing ACIItem parser */
+    private final ACIItemParser aciParser;
+
+    /** A storage for the PrescriptiveACI attributeType */
+    private AttributeType PRESCRIPTIVE_ACI_AT;
+
+    /** A storage for the ObjectClass attributeType */
+    private static AttributeType OBJECT_CLASS_AT;
+
+
+    /**
+     * Creates a ACITuple cache.
+     *
+     * @param session the session with the directory core services
+     * @throws LdapException if initialization fails
+     */
+    public TupleCache( CoreSession session ) throws LdapException
+    {
+        SchemaManager schemaManager = session.getDirectoryService().getSchemaManager();
+        this.dnFactory = session.getDirectoryService().getDnFactory();
+        this.nexus = session.getDirectoryService().getPartitionNexus();
+        NameComponentNormalizer ncn = new ConcreteNameComponentNormalizer( schemaManager );
+        aciParser = new ACIItemParser( ncn, schemaManager );
+        PRESCRIPTIVE_ACI_AT = schemaManager.getAttributeType( SchemaConstants.PRESCRIPTIVE_ACI_AT );
+        OBJECT_CLASS_AT = schemaManager.getAttributeType( SchemaConstants.OBJECT_CLASS_AT );
+        initialize( session );
+    }
+
+
+    private Dn parseNormalized( String name ) throws LdapException
+    {
+        Dn dn = dnFactory.create( name );
+        return dn;
+    }
+
+
+    private void initialize( CoreSession session ) throws LdapException
+    {
+        // search all naming contexts for access control subentenries
+        // generate ACITuple Arrays for each subentry
+        // add that subentry to the hash
+        Set<String> suffixes = nexus.listSuffixes();
+
+        for ( String suffix:suffixes )
+        {
+            Dn baseDn = parseNormalized( suffix );
+            ExprNode filter = new EqualityNode<String>( OBJECT_CLASS_AT,
+                new StringValue( SchemaConstants.ACCESS_CONTROL_SUBENTRY_OC ) );
+            SearchControls ctls = new SearchControls();
+            ctls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+            ctls.setReturningAttributes( new String[]{ "*", "+" } );
+
+            SearchOperationContext searchOperationContext = new SearchOperationContext( session,
+                baseDn, filter, ctls );
+            searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES );
+
+            EntryFilteringCursor results = nexus.search( searchOperationContext );
+
+            try
+            {
+                while ( results.next() )
+                {
+                    Entry result = results.get();
+                    Dn subentryDn = result.getDn().apply( session.getDirectoryService().getSchemaManager() );
+                    Attribute aci = result.get( PRESCRIPTIVE_ACI_AT );
+
+                    if ( aci == null )
+                    {
+                        LOG.warn( "Found accessControlSubentry '" + subentryDn + "' without any "
+                            + SchemaConstants.PRESCRIPTIVE_ACI_AT );
+                        continue;
+                    }
+
+                    subentryAdded( subentryDn, result );
+                }
+
+                results.close();
+            }
+            catch ( Exception e )
+            {
+                throw new LdapOperationErrorException( e.getMessage(), e );
+            }
+        }
+    }
+
+
+    /**
+     * Check if the Entry contains a prescriptiveACI
+     */
+    private boolean hasPrescriptiveACI( Entry entry ) throws LdapException
+    {
+        // only do something if the entry contains prescriptiveACI
+        Attribute aci = entry.get( PRESCRIPTIVE_ACI_AT );
+
+        if ( aci == null )
+        {
+            if ( entry.contains( OBJECT_CLASS_AT, SchemaConstants.ACCESS_CONTROL_SUBENTRY_OC ) )
+            {
+                // should not be necessary because of schema interceptor but schema checking
+                // can be turned off and in this case we must protect against being able to
+                // add access control information to anything other than an AC subentry
+                throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, "" );
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    public void subentryAdded( Dn dn, Entry entry ) throws LdapException
+    {
+        // only do something if the entry contains a prescriptiveACI
+        if ( !hasPrescriptiveACI( entry ) )
+        {
+            return;
+        }
+
+        // Get the prescriptiveACI
+        Attribute prescriptiveAci = entry.get( PRESCRIPTIVE_ACI_AT );
+
+        List<ACITuple> entryTuples = new ArrayList<ACITuple>();
+
+        // Loop on all the ACI, parse each of them and
+        // store the associated tuples into the cache
+        for ( Value<?> value : prescriptiveAci )
+        {
+            String aci = value.getString();
+            ACIItem item = null;
+
+            try
+            {
+                item = aciParser.parse( aci );
+                entryTuples.addAll( item.toTuples() );
+            }
+            catch ( ParseException e )
+            {
+                String msg = I18n.err( I18n.ERR_28, item );
+                LOG.error( msg, e );
+
+                // do not process this ACI Item because it will be null
+                // continue on to process the next ACI item in the entry
+            }
+        }
+
+        tuples.put( dn.getNormName(), entryTuples );
+    }
+
+
+    public void subentryDeleted( Dn normName, Entry entry ) throws LdapException
+    {
+        if ( !hasPrescriptiveACI( entry ) )
+        {
+            return;
+        }
+
+        tuples.remove( normName.toString() );
+    }
+
+
+    public void subentryModified( Dn normName, List<Modification> mods, Entry entry ) throws LdapException
+    {
+        if ( !hasPrescriptiveACI( entry ) )
+        {
+            return;
+        }
+
+        for ( Modification mod : mods )
+        {
+            if ( mod.getAttribute().isInstanceOf( PRESCRIPTIVE_ACI_AT ) )
+            {
+                subentryDeleted( normName, entry );
+                subentryAdded( normName, entry );
+            }
+        }
+    }
+
+
+    public void subentryModified( Dn normName, Entry mods, Entry entry ) throws LdapException
+    {
+        if ( !hasPrescriptiveACI( entry ) )
+        {
+            return;
+        }
+
+        if ( mods.get( PRESCRIPTIVE_ACI_AT ) != null )
+        {
+            subentryDeleted( normName, entry );
+            subentryAdded( normName, entry );
+        }
+    }
+
+
+    @SuppressWarnings("unchecked")
+    public List<ACITuple> getACITuples( String subentryDn )
+    {
+        List<ACITuple> aciTuples = tuples.get( subentryDn );
+        
+        if ( aciTuples == null )
+        {
+            return Collections.EMPTY_LIST;
+        }
+        
+        return Collections.unmodifiableList( aciTuples );
+    }
+
+
+    public void subentryRenamed( Dn oldName, Dn newName )
+    {
+        tuples.put( newName.getNormName(), tuples.remove( oldName.getNormName() ) );
+    }
+}

Added: directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/ACDFEngine.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/ACDFEngine.java?rev=1183441&view=auto
==============================================================================
--- directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/ACDFEngine.java (added)
+++ directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/ACDFEngine.java Fri Oct 14 17:38:30 2011
@@ -0,0 +1,197 @@
+/*
+ *  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.support;
+
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+
+import org.apache.directory.server.core.api.event.Evaluator;
+import org.apache.directory.server.core.event.ExpressionEvaluator;
+import org.apache.directory.server.core.subtree.RefinementEvaluator;
+import org.apache.directory.server.core.subtree.RefinementLeafEvaluator;
+import org.apache.directory.server.core.subtree.SubtreeEvaluator;
+import org.apache.directory.shared.ldap.aci.ACITuple;
+import org.apache.directory.shared.ldap.model.constants.SchemaConstants;
+import org.apache.directory.shared.ldap.model.entry.Entry;
+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.schema.SchemaManager;
+
+
+/**
+ * An implementation of Access Control Decision Function (18.8, X.501).
+ * <br/>
+ * This engine simply filters the collection of tuples using the following
+ * {@link ACITupleFilter}s sequentially:
+ * <ol>
+ * <li>{@link RelatedUserClassFilter}</li>
+ * <li>{@link RelatedProtectedItemFilter}</li>
+ * <li>{@link MaxValueCountFilter}</li>
+ * <li>{@link MaxImmSubFilter}</li>
+ * <li>{@link RestrictedByFilter}</li>
+ * <li>{@link MicroOperationFilter}</li>
+ * <li>{@link HighestPrecedenceFilter}</li>
+ * <li>{@link MostSpecificUserClassFilter}</li>
+ * <li>{@link MostSpecificProtectedItemFilter}</li>
+ * </ol>
+ * <br/>
+ * Operation is determined to be permitted if and only if there is at least one
+ * tuple left and all of them grants the access. (18.8.4. X.501)
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class ACDFEngine
+{
+    private final ACITupleFilter[] filters;
+
+
+    /**
+     * Creates a new instance.
+     *
+     * @param schemaManager The server schemaManager
+     *
+     * @throws LdapException if failed to initialize internal components
+     */
+    public ACDFEngine( SchemaManager schemaManager )
+    {
+        Evaluator entryEvaluator = new ExpressionEvaluator( schemaManager );
+        SubtreeEvaluator subtreeEvaluator = new SubtreeEvaluator( schemaManager );
+        RefinementEvaluator refinementEvaluator = new RefinementEvaluator( new RefinementLeafEvaluator( schemaManager ) );
+
+        filters = new ACITupleFilter[] {
+            new RelatedUserClassFilter( subtreeEvaluator ),
+            new RelatedProtectedItemFilter( refinementEvaluator, entryEvaluator, schemaManager ),
+            new MaxValueCountFilter(),
+            new MaxImmSubFilter( schemaManager ),
+            new RestrictedByFilter(),
+            new MicroOperationFilter(),
+            new HighestPrecedenceFilter(),
+            new MostSpecificUserClassFilter(),
+            new MostSpecificProtectedItemFilter() };
+    }
+
+
+    /**
+     * Checks the user with the specified name can access the specified resource
+     * (entry, attribute type, or attribute value) and throws {@link LdapNoPermissionException}
+     * if the user doesn't have any permission to perform the specified grants.
+     *
+     * @param aciContext the container for ACI items
+     * @throws LdapException if failed to evaluate ACI items
+     */
+    public void checkPermission( AciContext aciContext )throws LdapException
+    {
+        if ( !hasPermission( aciContext ) )
+        {
+            throw new LdapNoPermissionException();
+        }
+    }
+
+    public static final Collection<String> USER_LOOKUP_BYPASS;
+    static
+    {
+        Collection<String> c = new HashSet<String>();
+        c.add( "NormalizationInterceptor" );
+        c.add( "AuthenticationInterceptor" );
+//        c.add( ReferralInterceptor.class.getName() );
+        c.add( "AciAuthorizationInterceptor" );
+        c.add( "DefaultAuthorizationInterceptor" );
+        c.add( "AdministrativePointInterceptor" );
+//        c.add( ExceptionInterceptor.class.getName() );
+        c.add( "OperationalAttributeInterceptor" );
+        c.add( "SchemaInterceptor" );
+        c.add( "SubentryInterceptor" );
+//        c.add( CollectiveAttributeInterceptor.class.getName() );
+        c.add( "EventInterceptor" );
+        c.add( "TriggerInterceptor" );
+        USER_LOOKUP_BYPASS = Collections.unmodifiableCollection( c );
+    }
+
+
+    /**
+     * Returns <tt>true</tt> if the user with the specified name can access the specified resource
+     * (entry, attribute type, or attribute value) and throws {@link org.apache.directory.shared.ldap.model.exception.LdapNoPermissionException}
+     * if the user doesn't have any permission to perform the specified grants.
+     *
+     * @param aciContext the container for ACI items
+     * @throws org.apache.directory.shared.ldap.model.exception.LdapException if failed to evaluate ACI items
+     */
+    public boolean hasPermission( AciContext aciContext ) throws LdapException
+    {
+        if ( aciContext.getEntryDn() == null )
+        {
+            throw new IllegalArgumentException( "entryName" );
+        }
+
+        Entry userEntry = aciContext.getOperationContext().lookup( aciContext.getUserDn(), USER_LOOKUP_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
+
+        // Determine the scope of the requested operation.
+        OperationScope scope;
+
+        if ( aciContext.getAttributeType() == null )
+        {
+            scope = OperationScope.ENTRY;
+        }
+        else if ( aciContext.getAttrValue() == null )
+        {
+            scope = OperationScope.ATTRIBUTE_TYPE;
+        }
+        else
+        {
+            scope = OperationScope.ATTRIBUTE_TYPE_AND_VALUE;
+        }
+
+        // Clone aciTuples in case it is unmodifiable.
+        aciContext.setAciTuples( new ArrayList<ACITuple>( aciContext.getAciTuples() ) );
+
+        // Filter unrelated and invalid tuples
+        for ( ACITupleFilter filter : filters )
+        {
+            if ( aciContext.getAciTuples().size() == 0 )
+            {
+                // No need to continue filtering
+                return false;
+            }
+
+            aciContext.setAciTuples( filter.filter( aciContext, scope, userEntry ) );
+        }
+
+        // Deny access if no tuples left.
+        if ( aciContext.getAciTuples().size() == 0 )
+        {
+            return false;
+        }
+
+        // Grant access if and only if one or more tuples remain and
+        // all grant access. Otherwise deny access.
+        for ( ACITuple tuple : aciContext.getAciTuples() )
+        {
+            if ( !tuple.isGrant() )
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}

Added: directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/ACITupleFilter.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/ACITupleFilter.java?rev=1183441&view=auto
==============================================================================
--- directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/ACITupleFilter.java (added)
+++ directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/ACITupleFilter.java Fri Oct 14 17:38:30 2011
@@ -0,0 +1,56 @@
+/*
+ *  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.support;
+
+
+import java.util.Collection;
+
+import org.apache.directory.shared.ldap.aci.ACITuple;
+import org.apache.directory.shared.ldap.model.constants.Loggers;
+import org.apache.directory.shared.ldap.model.entry.Entry;
+import org.apache.directory.shared.ldap.model.exception.LdapException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * An interface that filters the specified collection of tuples using the
+ * specified extra information.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ *
+ */
+public interface ACITupleFilter
+{
+    /** the dedicated logger for ACI */
+    static final Logger ACI_LOG = LoggerFactory.getLogger( Loggers.ACI_LOG.getName() );
+
+    /**
+     * Returns the collection of the filtered tuples using the specified
+     * extra information.
+     * 
+     * @param aciContext the container for ACI items
+     * @param scope the scope of the operation to be performed
+     * @param userEntry the {@link org.apache.directory.shared.ldap.model.entry.Entry} of the current user entry in the DIT
+     * @return the collection of filtered tuples
+     * @throws org.apache.directory.shared.ldap.model.exception.LdapException if failed to filter the specific tuples
+     */
+    Collection<ACITuple> filter( AciContext aciContext, OperationScope scope, Entry userEntry ) throws LdapException;
+}

Added: directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/AciContext.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/AciContext.java?rev=1183441&view=auto
==============================================================================
--- directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/AciContext.java (added)
+++ directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/AciContext.java Fri Oct 14 17:38:30 2011
@@ -0,0 +1,282 @@
+/*
+ *  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.support;
+
+import java.util.Collection;
+
+import org.apache.directory.server.core.api.interceptor.context.OperationContext;
+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.entry.Entry;
+import org.apache.directory.shared.ldap.model.entry.Value;
+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.SchemaManager;
+
+/**
+ * A container used to pass parameters to the ACDF engine
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class AciContext
+{
+    /** The schema manager */
+    private SchemaManager schemaManager;
+    
+    /** The operation context */
+    private OperationContext operationContext;
+    
+    /** The Users belonging to a group */
+    private Collection<Dn> userGroupNames;
+
+    /** The user's Dn */
+    private Dn userDn;
+    
+    /** The requested Authentication level (default to NONE) */
+    private AuthenticationLevel authenticationLevel = AuthenticationLevel.NONE;
+    
+    /** the entry's Dn */
+    private Dn entryDn;
+    
+    /** The AttributeType */
+    private AttributeType attributeType;
+    
+    /** The attribute's values */
+    private Value<?> attrValue;
+    
+    /** The allowed operations */
+    private Collection<MicroOperation> microOperations;
+    
+    /** The resulting tuples */
+    private Collection<ACITuple> aciTuples;
+    
+    /** The entry */
+    private Entry entry;
+    
+    /** ??? */
+    private Entry entryView;
+    
+    /**
+     * Creates a new instance of AciContext.
+     *
+     * @param schemaManager The SchemaManager instance
+     * @param operationContext The OperationContext instance
+     */
+    public AciContext( SchemaManager schemaManager, OperationContext operationContext )
+    {
+        this.schemaManager = schemaManager;
+        this.operationContext = operationContext;
+    }
+    
+    
+    /**
+     * @return the schemaManager
+     */
+    public SchemaManager getSchemaManager()
+    {
+        return schemaManager;
+    }
+
+    /**
+     * @param schemaManager the schemaManager to set
+     */
+    public void setSchemaManager( SchemaManager schemaManager )
+    {
+        this.schemaManager = schemaManager;
+    }
+
+    /**
+     * @return the operationContext
+     */
+    public OperationContext getOperationContext()
+    {
+        return operationContext;
+    }
+
+    /**
+     * @param operationContext the operationContext to set
+     */
+    public void setOperationContext( OperationContext operationContext )
+    {
+        this.operationContext = operationContext;
+    }
+
+    /**
+     * @return the userGroupNames
+     */
+    public Collection<Dn> getUserGroupNames()
+    {
+        return userGroupNames;
+    }
+
+    /**
+     * @param userGroupNames the userGroupNames to set
+     */
+    public void setUserGroupNames( Collection<Dn> userGroupNames )
+    {
+        this.userGroupNames = userGroupNames;
+    }
+
+    /**
+     * @return the user Dn
+     */
+    public Dn getUserDn()
+    {
+        return userDn;
+    }
+
+    /**
+     * @param userDn the user Dn to set
+     */
+    public void setUserDn( Dn userDn )
+    {
+        this.userDn = userDn;
+    }
+
+    /**
+     * @return the authenticationLevel
+     */
+    public AuthenticationLevel getAuthenticationLevel()
+    {
+        return authenticationLevel;
+    }
+
+    /**
+     * @param authenticationLevel the authenticationLevel to set
+     */
+    public void setAuthenticationLevel( AuthenticationLevel authenticationLevel )
+    {
+        this.authenticationLevel = authenticationLevel;
+    }
+
+    /**
+     * @return the entry Dn
+     */
+    public Dn getEntryDn()
+    {
+        return entryDn;
+    }
+
+    /**
+     * @param entryDn the entry Dn to set
+     */
+    public void setEntryDn( Dn entryDn )
+    {
+        this.entryDn = entryDn;
+    }
+
+    /**
+     * @return the attributeType
+     */
+    public AttributeType getAttributeType()
+    {
+        return attributeType;
+    }
+
+    /**
+     * @param attributeType the attributeType to set
+     */
+    public void setAttributeType( AttributeType attributeType )
+    {
+        this.attributeType = attributeType;
+    }
+
+    /**
+     * @return the attrValue
+     */
+    public Value<?> getAttrValue()
+    {
+        return attrValue;
+    }
+
+    /**
+     * @param attrValue the attrValue to set
+     */
+    public void setAttrValue( Value<?> attrValue )
+    {
+        this.attrValue = attrValue;
+    }
+
+    /**
+     * @return the microOperations
+     */
+    public Collection<MicroOperation> getMicroOperations()
+    {
+        return microOperations;
+    }
+
+    /**
+     * @param microOperations the microOperations to set
+     */
+    public void setMicroOperations( Collection<MicroOperation> microOperations )
+    {
+        this.microOperations = microOperations;
+    }
+
+    /**
+     * @return the aciTuples
+     */
+    public Collection<ACITuple> getAciTuples()
+    {
+        return aciTuples;
+    }
+
+    /**
+     * @param aciTuples the aciTuples to set
+     */
+    public void setAciTuples( Collection<ACITuple> aciTuples )
+    {
+        this.aciTuples = aciTuples;
+    }
+
+    /**
+     * @return the entry
+     */
+    public Entry getEntry()
+    {
+        return entry;
+    }
+
+    /**
+     * @param entry the entry to set
+     */
+    public void setEntry( Entry entry )
+    {
+        this.entry = entry;
+    }
+
+    /**
+     * @return the entryView
+     */
+    public Entry getEntryView()
+    {
+        return entryView;
+    }
+
+    /**
+     * @param entryView the entryView to set
+     */
+    public void setEntryView( Entry entryView )
+    {
+        this.entryView = entryView;
+    }
+}

Added: directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/HighestPrecedenceFilter.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/HighestPrecedenceFilter.java?rev=1183441&view=auto
==============================================================================
--- directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/HighestPrecedenceFilter.java (added)
+++ directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/HighestPrecedenceFilter.java Fri Oct 14 17:38:30 2011
@@ -0,0 +1,74 @@
+/*
+ *  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.support;
+
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.directory.shared.ldap.aci.ACITuple;
+import org.apache.directory.shared.ldap.model.entry.Entry;
+import org.apache.directory.shared.ldap.model.exception.LdapException;
+
+
+/**
+ * An {@link ACITupleFilter} that discards all tuples having a precedence less
+ * than the highest remaining precedence. (18.8.4.1, X.501)
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class HighestPrecedenceFilter implements ACITupleFilter
+{
+    public Collection<ACITuple> filter( AciContext aciContext, OperationScope scope, Entry userEntry )
+        throws LdapException
+    {
+        ACI_LOG.debug( "Filtering HighestPrecedence..." );
+        
+        if ( aciContext.getAciTuples().size() <= 1 )
+        {
+            ACI_LOG.debug( "HighestPrecedence : nothing to do" );
+            return aciContext.getAciTuples();
+        }
+
+        int maxPrecedence = -1;
+
+        // Find the maximum precedence for all tuples.
+        for ( ACITuple tuple:aciContext.getAciTuples() )
+        {
+            if ( ( tuple.getPrecedence() != null ) && ( tuple.getPrecedence() > maxPrecedence ) )
+            {
+                maxPrecedence = tuple.getPrecedence();
+            }
+        }
+
+        // Remove all tuples whose precedences are not the maximum one.
+        for ( Iterator<ACITuple> i = aciContext.getAciTuples().iterator(); i.hasNext(); )
+        {
+            ACITuple tuple = i.next();
+            
+            if ( ( tuple.getPrecedence() != null ) && ( tuple.getPrecedence() != maxPrecedence ) )
+            {
+                i.remove();
+            }
+        }
+
+        return aciContext.getAciTuples();
+    }
+}

Added: directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MaxImmSubFilter.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MaxImmSubFilter.java?rev=1183441&view=auto
==============================================================================
--- directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MaxImmSubFilter.java (added)
+++ directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MaxImmSubFilter.java Fri Oct 14 17:38:30 2011
@@ -0,0 +1,197 @@
+/*
+ *  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.support;
+
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+
+import javax.naming.directory.SearchControls;
+
+import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
+import org.apache.directory.server.core.api.interceptor.context.OperationContext;
+import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
+import org.apache.directory.shared.ldap.aci.ACITuple;
+import org.apache.directory.shared.ldap.aci.ProtectedItem;
+import org.apache.directory.shared.ldap.aci.protectedItem.MaxImmSubItem;
+import org.apache.directory.shared.ldap.model.constants.SchemaConstants;
+import org.apache.directory.shared.ldap.model.entry.Entry;
+import org.apache.directory.shared.ldap.model.exception.LdapException;
+import org.apache.directory.shared.ldap.model.exception.LdapOperationException;
+import org.apache.directory.shared.ldap.model.exception.LdapOtherException;
+import org.apache.directory.shared.ldap.model.filter.ExprNode;
+import org.apache.directory.shared.ldap.model.filter.PresenceNode;
+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.SchemaManager;
+
+
+
+/**
+ * An {@link ACITupleFilter} that discards all tuples that doesn't satisfy
+ * {@link org.apache.directory.shared.ldap.aci.protectedItem.MaxImmSubItem} constraint if available. (18.8.3.3, X.501)
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class MaxImmSubFilter implements ACITupleFilter
+{
+    private final ExprNode childrenFilter;
+    private final SearchControls childrenSearchControls;
+
+
+    public MaxImmSubFilter( SchemaManager schemaManager )
+    {
+        AttributeType objectClassAt = null;
+
+        try
+        {
+            objectClassAt = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.OBJECT_CLASS_AT );
+        }
+        catch ( LdapException le )
+        {
+            // Do nothing
+        }
+
+        childrenFilter = new PresenceNode( objectClassAt );
+        childrenSearchControls = new SearchControls();
+        childrenSearchControls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
+    }
+
+
+    public Collection<ACITuple> filter( AciContext aciContext, OperationScope scope, Entry userEntry )
+        throws LdapException
+    {
+        ACI_LOG.debug( "Filtering MaxImmSub..." );
+
+        if ( aciContext.getEntryDn().isRootDSE() )
+        {
+            return aciContext.getAciTuples();
+        }
+
+        if ( aciContext.getAciTuples().size() == 0 )
+        {
+            return aciContext.getAciTuples();
+        }
+
+        if ( scope != OperationScope.ENTRY )
+        {
+            return aciContext.getAciTuples();
+        }
+
+        int immSubCount = -1;
+
+        for ( Iterator<ACITuple> i = aciContext.getAciTuples().iterator(); i.hasNext(); )
+        {
+            ACITuple tuple = i.next();
+
+            if ( !tuple.isGrant() )
+            {
+                continue;
+            }
+
+            for ( ProtectedItem item : tuple.getProtectedItems() )
+            {
+                if ( item instanceof MaxImmSubItem )
+                {
+                    if ( immSubCount < 0 )
+                    {
+                        immSubCount = getImmSubCount( aciContext.getOperationContext(), aciContext.getEntryDn() );
+                    }
+
+                    MaxImmSubItem mis = ( MaxImmSubItem ) item;
+
+                    if ( immSubCount >= mis.getValue() )
+                    {
+                        i.remove();
+                        break;
+                    }
+                }
+            }
+        }
+
+        return aciContext.getAciTuples();
+    }
+
+    public static final Collection<String> SEARCH_BYPASS;
+    static
+    {
+        Collection<String> c = new HashSet<String>();
+        c.add( "NormalizationInterceptor" );
+        c.add( "AuthenticationInterceptor" );
+        c.add( "AciAuthorizationInterceptor" );
+        c.add( "DefaultAuthorizationInterceptor" );
+        c.add( "AdministrativePointInterceptor" );
+        c.add( "OperationalAttributeInterceptor" );
+        c.add( "SchemaInterceptor" );
+        c.add( "SubentryInterceptor" );
+        c.add( "EventInterceptor" );
+        SEARCH_BYPASS = Collections.unmodifiableCollection( c );
+    }
+
+
+    private int getImmSubCount( OperationContext opContext, Dn entryName ) throws LdapException
+    {
+        int cnt = 0;
+        EntryFilteringCursor results = null;
+
+        try
+        {
+            Dn baseDn = new Dn( opContext.getSession().getDirectoryService().getSchemaManager(), entryName.getRdn( entryName.size() - 1 ) );
+            SearchOperationContext searchContext = new SearchOperationContext( opContext.getSession(),
+                baseDn, childrenFilter, childrenSearchControls );
+            searchContext.setByPassed( SEARCH_BYPASS );
+            searchContext.setAliasDerefMode( AliasDerefMode.DEREF_ALWAYS );
+
+            results = opContext.getSession().getDirectoryService().getOperationManager().search( searchContext );
+
+            try
+            {
+                while ( results.next() )
+                {
+                    results.get();
+                    cnt++;
+                }
+            }
+            catch ( Exception e )
+            {
+                throw new LdapOtherException( e.getMessage(), e );
+            }
+        }
+        finally
+        {
+            if ( results != null )
+            {
+                try
+                {
+                    results.close();
+                }
+                catch ( Exception e )
+                {
+                    throw new LdapOperationException( e.getMessage(), e );
+                }
+            }
+        }
+
+        return cnt;
+    }
+}

Added: directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MaxValueCountFilter.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MaxValueCountFilter.java?rev=1183441&view=auto
==============================================================================
--- directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MaxValueCountFilter.java (added)
+++ directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MaxValueCountFilter.java Fri Oct 14 17:38:30 2011
@@ -0,0 +1,107 @@
+/*
+ *  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.support;
+
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.directory.shared.ldap.aci.ACITuple;
+import org.apache.directory.shared.ldap.aci.ProtectedItem;
+import org.apache.directory.shared.ldap.aci.protectedItem.MaxValueCountElem;
+import org.apache.directory.shared.ldap.aci.protectedItem.MaxValueCountItem;
+import org.apache.directory.shared.ldap.model.entry.Entry;
+import org.apache.directory.shared.ldap.model.entry.Attribute;
+import org.apache.directory.shared.ldap.model.exception.LdapException;
+import org.apache.directory.shared.ldap.model.schema.AttributeType;
+
+
+/**
+ * An {@link ACITupleFilter} that discards all tuples that doesn't satisfy
+ * {@link org.apache.directory.shared.ldap.aci.protectedItem.MaxValueCountItem} constraint if available. (18.8.3.3, X.501)
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class MaxValueCountFilter implements ACITupleFilter
+{
+    public Collection<ACITuple> filter( AciContext aciContext, OperationScope scope, Entry userEntry ) throws LdapException
+    {
+        if ( scope != OperationScope.ATTRIBUTE_TYPE_AND_VALUE )
+        {
+            return aciContext.getAciTuples();
+        }
+
+        if ( aciContext.getAciTuples().size() == 0 )
+        {
+            return aciContext.getAciTuples();
+        }
+
+        for ( Iterator<ACITuple> i = aciContext.getAciTuples().iterator(); i.hasNext(); )
+        {
+            ACITuple tuple = i.next();
+            
+            if ( !tuple.isGrant() )
+            {
+                continue;
+            }
+
+            for ( Iterator<ProtectedItem> j = tuple.getProtectedItems().iterator(); j.hasNext(); )
+            {
+                ProtectedItem item = j.next();
+                
+                if ( item instanceof MaxValueCountItem )
+                {
+                    MaxValueCountItem mvc = ( MaxValueCountItem ) item;
+                    
+                    if ( isRemovable( mvc, aciContext.getAttributeType(), aciContext.getEntryView() ) )
+                    {
+                        i.remove();
+                        break;
+                    }
+                }
+            }
+        }
+
+        return aciContext.getAciTuples();
+    }
+
+
+    private boolean isRemovable( MaxValueCountItem mvc, AttributeType attributeType, Entry entryView ) throws LdapException
+    {
+        for ( Iterator<MaxValueCountElem> k = mvc.iterator(); k.hasNext(); )
+        {
+            MaxValueCountElem mvcItem = k.next();
+            
+            if ( attributeType.equals( mvcItem.getAttributeType() ) )
+            {
+                Attribute attr = entryView.get( attributeType );
+                int attrCount = attr == null ? 0 : attr.size();
+                
+                if ( attrCount > mvcItem.getMaxCount() )
+                {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+}