You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by ak...@apache.org on 2005/10/17 14:55:18 UTC

svn commit: r325884 - in /directory/apacheds/trunk/core/src: main/java/org/apache/ldap/server/authn/SimpleAuthenticator.java main/java/org/apache/ldap/server/authz/AuthorizationService.java test/org/apache/ldap/server/authz/SearchAuthorizationTest.java

Author: akarasulu
Date: Mon Oct 17 05:55:10 2005
New Revision: 325884

URL: http://svn.apache.org/viewcvs?rev=325884&view=rev
Log:
changes ...

 o added tests for search but most is commented out until other changes are 
   to be made to the interceptor mechanism 
 o also added search guards to the Authz interceptor but it is not yet complete
   pending changes to interceptor mechanism
 o added small change to show authentication error cause in SimpleAuthenticator


Added:
    directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/SearchAuthorizationTest.java   (with props)
Modified:
    directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/authn/SimpleAuthenticator.java
    directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/authz/AuthorizationService.java

Modified: directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/authn/SimpleAuthenticator.java
URL: http://svn.apache.org/viewcvs/directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/authn/SimpleAuthenticator.java?rev=325884&r1=325883&r2=325884&view=diff
==============================================================================
--- directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/authn/SimpleAuthenticator.java (original)
+++ directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/authn/SimpleAuthenticator.java Mon Oct 17 05:55:10 2005
@@ -99,9 +99,11 @@
                 throw new LdapAuthenticationException();
             }
         }
-        catch( Exception e )
+        catch( Exception cause )
         {
-            throw new LdapAuthenticationException();
+            LdapAuthenticationException e = new LdapAuthenticationException();
+            e.setRootCause( e );
+            throw e;
         }
 
 

Modified: directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/authz/AuthorizationService.java
URL: http://svn.apache.org/viewcvs/directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/authz/AuthorizationService.java?rev=325884&r1=325883&r2=325884&view=diff
==============================================================================
--- directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/authz/AuthorizationService.java (original)
+++ directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/authz/AuthorizationService.java Mon Oct 17 05:55:10 2005
@@ -18,16 +18,20 @@
 
 
 import org.apache.ldap.server.DirectoryServiceConfiguration;
+import org.apache.ldap.server.enumeration.SearchResultFilter;
+import org.apache.ldap.server.enumeration.SearchResultFilteringEnumeration;
 import org.apache.ldap.server.interceptor.BaseInterceptor;
 import org.apache.ldap.server.interceptor.NextInterceptor;
 import org.apache.ldap.server.interceptor.InterceptorChain;
 import org.apache.ldap.server.jndi.ServerContext;
+import org.apache.ldap.server.jndi.ServerLdapContext;
 import org.apache.ldap.server.configuration.InterceptorConfiguration;
 import org.apache.ldap.server.partition.DirectoryPartitionNexus;
 import org.apache.ldap.server.authz.support.ACDFEngine;
 import org.apache.ldap.server.invocation.InvocationStack;
 import org.apache.ldap.server.authn.LdapPrincipal;
 import org.apache.ldap.server.schema.ConcreteNameComponentNormalizer;
+import org.apache.ldap.server.schema.AttributeTypeRegistry;
 import org.apache.ldap.server.subtree.SubentryService;
 import org.apache.ldap.common.filter.ExprNode;
 import org.apache.ldap.common.aci.MicroOperation;
@@ -35,6 +39,7 @@
 import org.apache.ldap.common.aci.ACIItem;
 import org.apache.ldap.common.exception.LdapNamingException;
 import org.apache.ldap.common.message.ResultCodeEnum;
+import org.apache.ldap.common.name.DnParser;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -42,6 +47,7 @@
 import javax.naming.Name;
 import javax.naming.NamingException;
 import javax.naming.NamingEnumeration;
+import javax.naming.ldap.LdapContext;
 import javax.naming.directory.*;
 import java.util.*;
 import java.text.ParseException;
@@ -57,9 +63,14 @@
 {
     /** the logger for this class */
     private static final Logger log = LoggerFactory.getLogger( AuthorizationService.class );
-
+    /** the entry ACI attribute string: entryACI */
     private static final String ENTRYACI_ATTR = "entryACI";
+    /** the subentry ACI attribute string: subentryACI */
     private static final String SUBENTRYACI_ATTR = "subentryACI";
+    /**
+     * the multivalued op attr used to track the perscriptive access control
+     * subentries that apply to an entry.
+     */
     private static final String AC_SUBENTRY_ATTR = "accessControlSubentries";
 
     /** the partition nexus */
@@ -74,6 +85,8 @@
     private ACDFEngine engine;
     /** interceptor chain */
     private InterceptorChain chain;
+    /** attribute type registry */
+    private AttributeTypeRegistry attrRegistry;
     /** whether or not this interceptor is activated */
     private boolean enabled = false;
 
@@ -89,14 +102,12 @@
     public void init( DirectoryServiceConfiguration factoryCfg, InterceptorConfiguration cfg ) throws NamingException
     {
         super.init( factoryCfg, cfg );
-
         nexus = factoryCfg.getPartitionNexus();
         tupleCache = new TupleCache( factoryCfg );
         groupCache = new GroupCache( factoryCfg );
-        aciParser = new ACIItemParser( new ConcreteNameComponentNormalizer(
-                factoryCfg.getGlobalRegistries().getAttributeTypeRegistry() ) );
-        engine = new ACDFEngine( factoryCfg.getGlobalRegistries().getOidRegistry(),
-                factoryCfg.getGlobalRegistries().getAttributeTypeRegistry() );
+        attrRegistry = factoryCfg.getGlobalRegistries().getAttributeTypeRegistry();
+        aciParser = new ACIItemParser( new ConcreteNameComponentNormalizer( attrRegistry ) );
+        engine = new ACDFEngine( factoryCfg.getGlobalRegistries().getOidRegistry(), attrRegistry );
         chain = factoryCfg.getInterceptorChain();
         enabled = factoryCfg.getStartupConfiguration().isAccessControlEnabled();
     }
@@ -134,7 +145,10 @@
         }
 
         Attribute subentries = entry.get( AC_SUBENTRY_ATTR );
-        if ( subentries == null ) return;
+        if ( subentries == null )
+        {
+            return;
+        }
         for ( int ii = 0; ii < subentries.size(); ii++ )
         {
             String subentryDn = ( String ) subentries.get( ii );
@@ -154,7 +168,10 @@
     private void addEntryAciTuples( Collection tuples, Attributes entry ) throws NamingException
     {
         Attribute entryAci = entry.get( ENTRYACI_ATTR );
-        if ( entryAci == null ) return;
+        if ( entryAci == null )
+        {
+            return;
+        }
 
         for ( int ii = 0; ii < entryAci.size(); ii++ )
         {
@@ -189,7 +206,10 @@
     private void addSubentryAciTuples( Collection tuples, Name dn, Attributes entry ) throws NamingException
     {
         // only perform this for subentries
-        if ( ! entry.get( "objectClass" ).contains( "subentry" ) ) return;
+        if ( ! entry.get("objectClass").contains("subentry") )
+        {
+            return;
+        }
 
         // get the parent or administrative entry for this subentry since it
         // will contain the subentryACI attributes that effect subentries
@@ -198,7 +218,10 @@
         Attributes administrativeEntry = nexus.lookup( parentDn );
         Attribute subentryAci = administrativeEntry.get( SUBENTRYACI_ATTR );
 
-        if ( subentryAci == null ) return;
+        if ( subentryAci == null )
+        {
+            return;
+        }
 
         for ( int ii = 0; ii < subentryAci.size(); ii++ )
         {
@@ -232,10 +255,13 @@
      * since it could introduce a security breech.  So for non-add ops if present a
      * set of ACITuples are generated for all the entryACIs within the entry.  This
      * set is combined with the ACITuples cached for the perscriptiveACI affecting
-     * the target entry.
+     * the target entry.  If the entry is a subentry the ACIs are also processed for
+     * the subentry to generate more ACITuples.  This subentry TupleACI set is joined
+     * with the entry and perscriptive ACI.
      *
      * The union of ACITuples are fed into the engine along with other parameters
-     * to decide where permission is granted or rejected for the specific operation.
+     * to decide whether a permission is granted or rejected for the specific
+     * operation.
      * -------------------------------------------------------------------------------
      */
 
@@ -251,7 +277,7 @@
             return;
         }
 
-        // perform checks below here
+        // perform checks below here for all non-admin users
         SubentryService subentryService = ( SubentryService ) chain.get( "subentryService" );
         Attributes subentryAttrs = subentryService.getSubentryAttributes( normName, entry );
         NamingEnumeration attrList = entry.getAll();
@@ -264,25 +290,34 @@
         Set userGroups = groupCache.getGroups( user.getName() );
         Collection tuples = new HashSet();
 
-        // note that entryACI should not be considered in adds (it's a security breach)
+        // Build the total collection of tuples to be considered for add rights
+        // NOTE: entryACI are NOT considered in adds (it would be a security breech)
         addPerscriptiveAciTuples( tuples, normName, subentryAttrs );
         addSubentryAciTuples( tuples, normName, subentryAttrs );
         Collection perms = Collections.singleton( MicroOperation.ADD );
-        engine.checkPermission( next, userGroups, user.getJndiName(), user.getAuthenticationLevel(), normName, null,
-            null, perms, tuples, subentryAttrs );
+
+        // check if entry scope permission is granted
+        engine.checkPermission( next, userGroups, user.getJndiName(), user.getAuthenticationLevel(),
+                normName, null, null, perms, tuples, subentryAttrs );
+
+        // now we must check if attribute type and value scope permission is granted
         NamingEnumeration attributeList = entry.getAll();
         while ( attributeList.hasMore() )
         {
             Attribute attr = ( Attribute ) attributeList.next();
             for ( int ii = 0; ii < attr.size(); ii++ )
             {
-                engine.checkPermission( next, userGroups, user.getJndiName(), user.getAuthenticationLevel(), normName,
-                        attr.getID(), attr.get( ii ), perms, tuples, entry );
+                engine.checkPermission( next, userGroups, user.getJndiName(),
+                        user.getAuthenticationLevel(), normName, attr.getID(),
+                        attr.get( ii ), perms, tuples, entry );
             }
         }
 
-        // if we've gotten this far then access is granted
+        // if we've gotten this far then access has been granted
         next.add( upName, normName, entry );
+
+        // if the entry added is a subentry or a groupOf[Unique]Names we must
+        // update the ACITuple cache and the groups cache to keep them in sync
         tupleCache.subentryAdded( upName, normName, entry );
         groupCache.groupAdded( upName, normName, entry );
     }
@@ -453,30 +488,6 @@
     }
 
 
-    public NamingEnumeration list( NextInterceptor next, Name base ) throws NamingException
-    {
-//        Attributes entry = nexus.lookup( base );
-//        ServerContext ctx = ( ServerContext ) InvocationStack.getInstance().peek().getCaller();
-//        LdapPrincipal user = ctx.getPrincipal();
-//        Set userGroups = groupCache.getGroups( user.getName() );
-//        Collection tuples = new HashSet();
-//        addPerscriptiveAciTuples( tuples, entry );
-//        addEntryAciTuples( tuples, entry );
-//        addSubentryAciTuples( tuples, entry );
-//
-//        engine.checkPermission( next, userGroups, user.getJndiName(), user.getAuthenticationLevel(), base, null,
-//                null, SEARCH_OPS, tuples );
-
-        return super.list( next, base );
-    }
-
-
-    public Iterator listSuffixes( NextInterceptor next, boolean normalized ) throws NamingException
-    {
-        return super.listSuffixes( next, normalized );
-    }
-
-
     /**
      * Checks if the READ permissions exist to the entry and to each attribute type and
      * value.
@@ -502,12 +513,16 @@
         addEntryAciTuples( tuples, entry );
         addSubentryAciTuples( tuples, dn, entry );
 
+        Collection perms = new HashSet();
+        perms.add( MicroOperation.READ );
+        perms.add( MicroOperation.BROWSE );
+
         // check that we have read access to the entry
         engine.checkPermission( next, userGroups, user.getJndiName(), user.getAuthenticationLevel(), dn, null,
-                null, Collections.singleton( MicroOperation.READ ), tuples, entry );
+                null, perms, tuples, entry );
 
         // check that we have read access to every attribute type and value
-        Collection perms = Collections.singleton( MicroOperation.READ );
+        perms = Collections.singleton( MicroOperation.READ );
         NamingEnumeration attributeList = entry.getAll();
         while ( attributeList.hasMore() )
         {
@@ -684,9 +699,9 @@
     {
         // Access the principal requesting the operation, and bypass checks if it is the admin
         Attributes entry = nexus.lookup( oriChildName );
-        LdapPrincipal user = ( ( ServerContext ) InvocationStack.getInstance().peek().getCaller() ).getPrincipal();
         Name newName = ( Name ) newParentName.clone();
         newName.add( oriChildName.get( oriChildName.size() - 1 ) );
+        LdapPrincipal user = ( ( ServerContext ) InvocationStack.getInstance().peek().getCaller() ).getPrincipal();
         if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
         {
             next.move( oriChildName, newParentName );
@@ -717,22 +732,34 @@
     }
 
 
+    public static final SearchControls DEFUALT_SEARCH_CONTROLS = new SearchControls();
+
+    public NamingEnumeration list( NextInterceptor next, Name base ) throws NamingException
+    {
+        ServerLdapContext ctx = ( ServerLdapContext ) InvocationStack.getInstance().peek().getCaller();
+        LdapPrincipal user = ctx.getPrincipal();
+        NamingEnumeration e = next.list( base );
+        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
+        {
+            return e;
+        }
+        AuthorizationFilter authzFilter = new AuthorizationFilter();
+        return new SearchResultFilteringEnumeration( e, DEFUALT_SEARCH_CONTROLS, ctx, authzFilter );
+    }
+
+
     public NamingEnumeration search( NextInterceptor next, Name base, Map env, ExprNode filter,
                                      SearchControls searchCtls ) throws NamingException
     {
-//        Attributes entry = nexus.lookup( base );
-//        ServerContext ctx = ( ServerContext ) InvocationStack.getInstance().peek().getCaller();
-//        LdapPrincipal user = ctx.getPrincipal();
-//        Set userGroups = groupCache.getGroups( user.getName() );
-//        Collection tuples = new HashSet();
-//        addPerscriptiveAciTuples( tuples, entry );
-//        addEntryAciTuples( tuples, entry );
-//        addSubentryAciTuples( tuples, entry );
-//
-//        engine.checkPermission( next, userGroups, user.getJndiName(), user.getAuthenticationLevel(), base, null,
-//                null, SEARCH_OPS, tuples );
-
-        return super.search( next, base, env, filter, searchCtls );
+        ServerLdapContext ctx = ( ServerLdapContext ) InvocationStack.getInstance().peek().getCaller();
+        LdapPrincipal user = ctx.getPrincipal();
+        NamingEnumeration e = next.search( base, env, filter, searchCtls );
+        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
+        {
+            return e;
+        }
+        AuthorizationFilter authzFilter = new AuthorizationFilter();
+        return new SearchResultFilteringEnumeration( e, searchCtls, ctx, authzFilter );
     }
 
 
@@ -765,5 +792,118 @@
     public void cacheNewGroup( String upName, Name normName, Attributes entry ) throws NamingException
     {
         this.groupCache.groupAdded( upName, normName, entry );
+    }
+
+
+    /** @todo move this up and add more collections that can be made constants */
+    private static final Collection SEARCH_ENTRY_PERMS;
+    private static final Collection SEARCH_ATTRVAL_PERMS;
+    static
+    {
+        HashSet set = new HashSet( 2 );
+        set.add( MicroOperation.BROWSE );
+        set.add( MicroOperation.RETURN_DN );
+        SEARCH_ENTRY_PERMS = Collections.unmodifiableCollection( set );
+        SEARCH_ATTRVAL_PERMS = Collections.singleton( MicroOperation.READ );
+    }
+
+    private boolean filter( ServerLdapContext ctx, Name normName, SearchResult result ) throws NamingException
+    {
+       /*
+        * First call hasPermission() for entry level "Browse" and "ReturnDN" perm
+        * tests.  If we hasPermission() returns false we immediately short the
+        * process and return false.
+        */
+//        NextInterceptor next = chain.getNext( "authorizationService" );
+        NextInterceptor next = null;//chain.getNext( "authorizationService" );
+        Attributes entry = nexus.lookup( normName );
+        Name userDn = ctx.getPrincipal().getJndiName();
+        Set userGroups = groupCache.getGroups( userDn.toString() );
+        Collection tuples = new HashSet();
+        addPerscriptiveAciTuples( tuples, normName, entry );
+        addEntryAciTuples( tuples, entry );
+        addSubentryAciTuples( tuples, normName, entry );
+
+        if ( ! engine.hasPermission( next, userGroups, userDn, ctx.getPrincipal().getAuthenticationLevel(),
+                normName, null, null, SEARCH_ENTRY_PERMS, tuples, entry ) )
+        {
+            return false;
+        }
+
+        /*
+         * For each attribute type we check if access is allowed to the type.  If not
+         * the attribute is yanked out of the entry to be returned.  If permission is
+         * allowed we move on to check if the values are allowed.  Values that are
+         * not allowed are removed from the attribute.  If the attribute has no more
+         * values remaining then the entire attribute is removed.
+         */
+        NamingEnumeration attributeList = result.getAttributes().getAll();
+        while ( attributeList.hasMore() )
+        {
+            // if attribute type scope access is not allowed then remove the attribute and continue
+            Attribute attr = ( Attribute ) attributeList.next();
+            if ( ! engine.hasPermission( next, userGroups, userDn, ctx.getPrincipal().getAuthenticationLevel(),
+                   normName, attr.getID(), null, SEARCH_ATTRVAL_PERMS, tuples, entry ) )
+            {
+                result.getAttributes().remove( attr.getID() );
+
+                if ( attr.size() == 0 )
+                {
+                    result.getAttributes().remove( attr.getID() );
+                }
+                continue;
+            }
+
+            // attribute type scope is ok now let's determine value level scope
+            for ( int ii = 0; ii < attr.size(); ii++ )
+            {
+                if ( ! engine.hasPermission( next, userGroups, userDn,
+                        ctx.getPrincipal().getAuthenticationLevel(), normName,
+                        attr.getID(), attr.get( ii ), SEARCH_ATTRVAL_PERMS, tuples, entry ) )
+                {
+                    attr.remove( ii );
+
+                    if ( ii > 0 )
+                    {
+                        ii--;
+                    }
+                }
+            }
+        }
+
+        return true;
+    }
+
+
+    /**
+     * WARNING: create one of these filters fresh every time for each new search.
+     */
+    class AuthorizationFilter implements SearchResultFilter
+    {
+        /** dedicated normalizing parser for this search - cheaper than synchronization */
+        final DnParser parser;
+
+        public AuthorizationFilter() throws NamingException
+        {
+            parser = new DnParser( new ConcreteNameComponentNormalizer( attrRegistry ) );
+        }
+
+
+        public boolean accept( LdapContext ctx, SearchResult result, SearchControls controls ) throws NamingException
+        {
+            Name normName = parser.parse( result.getName() );
+            ServerLdapContext srvCtx = ( ServerLdapContext ) ctx;
+
+// looks like isRelative returns true even when the names for results are absolute!!!!
+// @todo this is a big bug in JNDI provider
+
+//            if ( result.isRelative() )
+//            {
+//                Name base = parser.parse( ctx.getNameInNamespace() );
+//                normName = base.addAll( normName );
+//            }
+
+            return filter( srvCtx, normName, result );
+        }
     }
 }

Added: directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/SearchAuthorizationTest.java
URL: http://svn.apache.org/viewcvs/directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/SearchAuthorizationTest.java?rev=325884&view=auto
==============================================================================
--- directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/SearchAuthorizationTest.java (added)
+++ directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/SearchAuthorizationTest.java Mon Oct 17 05:55:10 2005
@@ -0,0 +1,376 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed 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.ldap.server.authz;
+
+
+import org.apache.ldap.common.message.LockableAttributesImpl;
+import org.apache.ldap.common.message.LockableAttributeImpl;
+import org.apache.ldap.common.name.LdapName;
+import org.apache.ldap.common.exception.LdapNameNotFoundException;
+import org.apache.ldap.common.exception.LdapNoPermissionException;
+
+import javax.naming.NamingException;
+import javax.naming.Name;
+import javax.naming.NamingEnumeration;
+import javax.naming.directory.*;
+
+
+/**
+ * Tests whether or not authorization around search, list and lookup operations
+ * work properly.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+public class SearchAuthorizationTest extends AbstractAuthorizationTest
+{
+    /**
+     * Generates a set of simple organizationalUnit entries where the
+     * ou of the entry returned is the index of the entry in the array.
+     *
+     * @param count the number of entries to produce
+     * @return an array of entries with length = count
+     */
+    private Attributes[] getTestNodes( final int count )
+    {
+        Attributes[] attributes = new Attributes[count];
+        for ( int ii = 0; ii < count; ii++ )
+        {
+            attributes[ii] = new LockableAttributesImpl();
+            Attribute oc = new LockableAttributeImpl( "objectClass" );
+            oc.add( "top" );
+            oc.add( "organizationalUnit" );
+            attributes[ii].put( oc );
+            attributes[ii].put( "ou", String.valueOf( count ) );
+        }
+
+        return attributes;
+    }
+
+
+    private void recursivelyAddSearchData( Name parent, Attributes[] children, final int sizeLimit, int[] count )
+            throws NamingException
+    {
+        Name[] childRdns = new Name[children.length];
+        for ( int ii = 0; ii < children.length && count[0] < sizeLimit; ii++ )
+        {
+            Name childRdn = new LdapName();
+            childRdn.addAll( parent );
+            childRdn.add( "ou=" + ii );
+            childRdns[ii] = childRdn;
+            sysRoot.createSubcontext( childRdn, children[ii] );
+            count[0]++;
+        }
+
+        if ( count[0] >= sizeLimit )
+        {
+            return;
+        }
+
+        for ( int ii = 0; ii < children.length && count[0] < sizeLimit; ii++ )
+        {
+            recursivelyAddSearchData( childRdns[ii], children, sizeLimit, count );
+        }
+    }
+
+
+    /**
+     * Starts creating nodes under a parent with a set number of children.  First
+     * a single node is created under the parent.  Thereafter a number of children
+     * determined by the branchingFactor is added.  Until a sizeLimit is reached
+     * descendants are created this way in a breath first recursive descent.
+     *
+     * @param parent the parent under which the first node is created
+     * @param branchingFactor
+     * @param sizelimit
+     * @return the immediate child node created under parent which contains the subtree
+     * @throws NamingException
+     */
+    private Name addSearchData( Name parent, int branchingFactor, int sizelimit ) throws NamingException
+    {
+        parent = ( Name ) parent.clone();
+        parent.add( "ou=tests" );
+        sysRoot.createSubcontext( parent, getTestNodes(1)[0] );
+        recursivelyAddSearchData( parent, getTestNodes( branchingFactor ), sizelimit, new int[] { 1 } );
+        return parent;
+    }
+
+
+    /**
+     * Recursively deletes all entries including the base specified.
+     *
+     * @param rdn the relative dn from ou=system of the entry to delete recursively
+     * @throws NamingException if there are problems deleting entries
+     */
+    private void recursivelyDelete( Name rdn ) throws NamingException
+    {
+        NamingEnumeration results = sysRoot.search( rdn, "(objectClass=*)", new SearchControls() );
+        while ( results.hasMore() )
+        {
+            SearchResult result = ( SearchResult ) results.next();
+            Name childRdn = new LdapName( result.getName() );
+            childRdn.remove( 0 );
+            recursivelyDelete( childRdn );
+        }
+        sysRoot.destroySubcontext( rdn );
+    }
+
+
+    /**
+     * Performs a single level search as a specific user on newly created data and checks
+     * that result set count is 3.  The basic (objectClass=*) filter is used.
+     *
+     * @param uid the uid RDN attribute value for the user under ou=users,ou=system
+     * @param password the password of the user
+     * @return true if the search succeeds as expected, false otherwise
+     * @throws NamingException if there are problems conducting the search
+     */
+    private boolean checkCanSearchAs( String uid, String password ) throws NamingException
+    {
+        return checkCanSearchAs( uid, password, "(objectClass=*)", null, 3 );
+    }
+
+
+    /**
+     * Performs a single level search as a specific user on newly created data and checks
+     * that result set count is equal to a user specified amount.  The basic
+     * (objectClass=*) filter is used.
+     *
+     * @param uid the uid RDN attribute value for the user under ou=users,ou=system
+     * @param password the password of the user
+     * @param resultSetSz the expected size of the results
+     * @return true if the search succeeds as expected, false otherwise
+     * @throws NamingException if there are problems conducting the search
+     */
+    private boolean checkCanSearchAs( String uid, String password, int resultSetSz ) throws NamingException
+    {
+        return checkCanSearchAs( uid, password, "(objectClass=*)", null, resultSetSz );
+    }
+
+
+    /**
+     * Performs a search as a specific user on newly created data and checks
+     * that result set count is equal to a user specified amount.  The basic
+     * (objectClass=*) filter is used.
+     *
+     * @param uid the uid RDN attribute value for the user under ou=users,ou=system
+     * @param password the password of the user
+     * @param resultSetSz the expected size of the results
+     * @return true if the search succeeds as expected, false otherwise
+     * @throws NamingException if there are problems conducting the search
+     */
+    private boolean checkCanSearchAs( String uid, String password, SearchControls cons, int resultSetSz )
+            throws NamingException
+    {
+        return checkCanSearchAs( uid, password, "(objectClass=*)", cons, resultSetSz );
+    }
+
+
+    /**
+     * Performs a search as a specific user on newly created data and checks
+     * that result set count is equal to a user specified amount.
+     *
+     * @param uid the uid RDN attribute value for the user under ou=users,ou=system
+     * @param password the password of the user
+     * @param filter the search filter to use
+     * @param resultSetSz the expected size of the results
+     * @return true if the search succeeds as expected, false otherwise
+     * @throws NamingException if there are problems conducting the search
+     */
+    private boolean checkCanSearchAs( String uid, String password, String filter,
+                                      SearchControls cons, int resultSetSz ) throws NamingException
+    {
+        if ( cons == null )
+        {
+            cons = new SearchControls();
+        }
+
+        Name base = addSearchData( new LdapName(), 3, 10 );
+        Name userDn = new LdapName( "uid="+uid+",ou=users,ou=system" );
+        try
+        {
+            DirContext userCtx = getContextAs( userDn, password );
+            NamingEnumeration list = userCtx.search( base, filter, cons );
+            int counter = 0;
+            while ( list.hasMore() )
+            {
+                list.next();
+                counter++;
+            }
+            return counter == resultSetSz;
+        }
+        catch ( LdapNoPermissionException e )
+        {
+            return false;
+        }
+        finally
+        {
+            recursivelyDelete( base );
+        }
+    }
+
+
+    /**
+     * Checks to see that the addSearchData() and the recursiveDelete()
+     * functions in this test work properly.
+     *
+     * @throws NamingException if there is a problem with the implementation of
+     * these utility functions
+     */
+    public void testAddSearchData() throws NamingException
+    {
+//        Name base = addSearchData( new LdapName(), 3, 10 );
+//        SearchControls controls = new SearchControls();
+//        controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+//        NamingEnumeration results = sysRoot.search( base, "(objectClass=*)", controls );
+//        int counter = 0;
+//        while ( results.hasMore() )
+//        {
+//            results.next();
+//            counter++;
+//        }
+//
+//        assertEquals( 10, counter );
+//        recursivelyDelete( base );
+//        try { sysRoot.lookup( base ); fail(); } catch ( LdapNameNotFoundException e ) {}
+    }
+
+
+//    /**
+//     * Checks to make sure group membership based userClass works for add operations.
+//     *
+//     * @throws javax.naming.NamingException if the test encounters an error
+//     */
+//    public void testGrantAdministrators() throws NamingException
+//    {
+//        // create the non-admin user
+//        createUser( "billyd", "billyd" );
+//
+//        // try an add operation which should fail without any ACI
+//        assertFalse( checkCanSearchAs( "billyd", "billyd" ) );
+//
+//        // Gives grantAdd perm to all users in the Administrators group for
+//        // entries and all attribute types and values
+//        createAccessControlSubentry( "searchAdmin", "{ " +
+//                "identificationTag \"addAci\", " +
+//                "precedence 14, " +
+//                "authenticationLevel none, " +
+//                "itemOrUserFirst userFirst: { " +
+//                "userClasses { userGroup { \"cn=Administrators,ou=groups,ou=system\" } }, " +
+//                "userPermissions { { " +
+//                "protectedItems {entry, allUserAttributeTypesAndValues}, " +
+//                "grantsAndDenials { grantRead, grantReturnDN, grantBrowse } } } } }" );
+//
+//        // see if we can now add that test entry which we could not before
+//        // add op should still fail since billd is not in the admin group
+//        assertFalse( checkCanSearchAs( "billyd", "billyd" ) );
+//
+//        // now add billyd to the Administrator group and try again
+//        addUserToGroup( "billyd", "Administrators" );
+//
+//        // try an add operation which should succeed with ACI and group membership change
+//        assertTrue( checkCanSearchAs( "billyd", "billyd" ) );
+//    }
+//
+//
+//    /**
+//     * Checks to make sure name based userClass works for add operations.
+//     *
+//     * @throws javax.naming.NamingException if the test encounters an error
+//     */
+//    public void testGrantAddByName() throws NamingException
+//    {
+//        // create the non-admin user
+//        createUser( "billyd", "billyd" );
+//
+//        // try an add operation which should fail without any ACI
+//        assertFalse( checkCanSearchAs( "billyd", "billyd" ) );
+//
+//        // now add a subentry that enables user billyd to add an entry below ou=system
+//        createAccessControlSubentry( "billydAdd", "{ " +
+//                "identificationTag \"addAci\", " +
+//                "precedence 14, " +
+//                "authenticationLevel none, " +
+//                "itemOrUserFirst userFirst: { " +
+//                "userClasses { name { \"uid=billyd,ou=users,ou=system\" } }, " +
+//                "userPermissions { { " +
+//                "protectedItems {entry, allUserAttributeTypesAndValues}, " +
+//                "grantsAndDenials { grantAdd, grantBrowse } } } } }" );
+//
+//        // should work now that billyd is authorized by name
+//        assertTrue( checkCanSearchAs( "billyd", "billyd" ) );
+//    }
+//
+//
+//    /**
+//     * Checks to make sure subtree based userClass works for add operations.
+//     *
+//     * @throws javax.naming.NamingException if the test encounters an error
+//     */
+//    public void testGrantAddBySubtree() throws NamingException
+//    {
+//        // create the non-admin user
+//        createUser( "billyd", "billyd" );
+//
+//        // try an add operation which should fail without any ACI
+//        assertFalse( checkCanSearchAs( "billyd", "billyd" ) );
+//
+//        // now add a subentry that enables user billyd to add an entry below ou=system
+//        createAccessControlSubentry( "billyAddBySubtree", "{ " +
+//                "identificationTag \"addAci\", " +
+//                "precedence 14, " +
+//                "authenticationLevel none, " +
+//                "itemOrUserFirst userFirst: { " +
+//                "userClasses { subtree { { base \"ou=users,ou=system\" } } }, " +
+//                "userPermissions { { " +
+//                "protectedItems {entry, allUserAttributeTypesAndValues}, " +
+//                "grantsAndDenials { grantAdd, grantBrowse } } } } }" );
+//
+//        // should work now that billyd is authorized by the subtree userClass
+//        assertTrue( checkCanSearchAs( "billyd", "billyd" ) );
+//    }
+//
+//
+//    /**
+//     * Checks to make sure <b>allUsers</b> userClass works for add operations.
+//     *
+//     * @throws javax.naming.NamingException if the test encounters an error
+//     */
+//    public void testGrantAddAllUsers() throws NamingException
+//    {
+//        // create the non-admin user
+//        createUser( "billyd", "billyd" );
+//
+//        // try an add operation which should fail without any ACI
+//        assertFalse( checkCanSearchAs( "billyd", "billyd" ) );
+//
+//        // now add a subentry that enables anyone to add an entry below ou=system
+//        createAccessControlSubentry( "anybodyAdd", "{ " +
+//                "identificationTag \"addAci\", " +
+//                "precedence 14, " +
+//                "authenticationLevel none, " +
+//                "itemOrUserFirst userFirst: { " +
+//                "userClasses { allUsers }, " +
+//                "userPermissions { { " +
+//                "protectedItems {entry, allUserAttributeTypesAndValues}, " +
+//                "grantsAndDenials { grantAdd, grantBrowse } } } } }" );
+//
+//        // see if we can now add that test entry which we could not before
+//        // should work now with billyd now that all users are authorized
+//        assertTrue( checkCanSearchAs( "billyd", "billyd" ) );
+//    }
+}

Propchange: directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/SearchAuthorizationTest.java
------------------------------------------------------------------------------
    svn:eol-style = native