preparing interceptor bypass mechanism branch
git-svn-id: https://svn.apache.org/repos/asf/directory/apacheds/branches/interceptor-bypass@325885 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/core/src/main/java/org/apache/ldap/server/authn/SimpleAuthenticator.java b/core/src/main/java/org/apache/ldap/server/authn/SimpleAuthenticator.java
index 247c794..d859af3 100644
--- a/core/src/main/java/org/apache/ldap/server/authn/SimpleAuthenticator.java
+++ b/core/src/main/java/org/apache/ldap/server/authn/SimpleAuthenticator.java
@@ -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;
}
diff --git a/core/src/main/java/org/apache/ldap/server/authz/AuthorizationService.java b/core/src/main/java/org/apache/ldap/server/authz/AuthorizationService.java
index e60115a..b2dca3d 100644
--- a/core/src/main/java/org/apache/ldap/server/authz/AuthorizationService.java
+++ b/core/src/main/java/org/apache/ldap/server/authz/AuthorizationService.java
@@ -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 );
}
@@ -766,4 +793,117 @@
{
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 );
+ }
+ }
}
diff --git a/core/src/test/org/apache/ldap/server/authz/SearchAuthorizationTest.java b/core/src/test/org/apache/ldap/server/authz/SearchAuthorizationTest.java
new file mode 100644
index 0000000..a843931
--- /dev/null
+++ b/core/src/test/org/apache/ldap/server/authz/SearchAuthorizationTest.java
@@ -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" ) );
+// }
+}