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;
+ }
+
+}