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/14 07:31:45 UTC

svn commit: r321015 - in /directory/apacheds/trunk/core/src: main/java/org/apache/ldap/server/authz/ test/org/apache/ldap/server/authz/

Author: akarasulu
Date: Thu Oct 13 22:31:38 2005
New Revision: 321015

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

 o Added guards to hasEntry and lookup operations in Authz interceptor
 o With guard on hasEntry requiring browse grants we had to add this 
   to the ACIItems of all tests - cannot get initial context to ou=system
   otherwise.
 o With new guards on lookup() and hasEntry() we cannot use the NonAdmin
   version of the AbstractTest case.  Cannot get initial context as non-admin
   user during test setup without ACI addition.  So now we derive from the
   topmost base class.  
 o Added some tests for the compare() operation


Added:
    directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/CompareAuthorizationTest.java   (with props)
Modified:
    directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/authz/AuthorizationService.java
    directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/AbstractAuthorizationTest.java
    directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/AddAuthorizationTest.java
    directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/DeleteAuthorizationTest.java

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=321015&r1=321014&r2=321015&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 Thu Oct 13 22:31:38 2005
@@ -432,17 +432,23 @@
 
     public boolean hasEntry( NextInterceptor next, Name name ) throws NamingException
     {
-//        Attributes entry = nexus.lookup( name );
-//        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(), name, null,
-//                null, BROWSE_OPS, tuples );
+        Attributes entry = nexus.lookup( name );
+        LdapPrincipal user = ( ( ServerContext ) InvocationStack.getInstance().peek().getCaller() ).getPrincipal();
+
+        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
+        {
+            return next.hasEntry( name );
+        }
+
+        Set userGroups = groupCache.getGroups( user.getName() );
+        Collection tuples = new HashSet();
+        addPerscriptiveAciTuples( tuples, name, entry );
+        addEntryAciTuples( tuples, entry );
+        addSubentryAciTuples( tuples, name, entry );
+
+        // check that we have browse access to the entry
+        engine.checkPermission( next, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, null,
+                null, Collections.singleton( MicroOperation.BROWSE ), tuples, entry );
 
         return next.hasEntry( name );
     }
@@ -472,15 +478,79 @@
     }
 
 
+    /**
+     * Checks if the READ permissions exist to the entry and to each attribute type and
+     * value.
+     *
+     * @todo not sure if we should hide attribute types/values or throw an exception
+     * instead.  I think we're going to have to use a filter to restrict the return
+     * of attribute types and values instead of throwing an exception.  Lack of read
+     * perms to attributes and their values results in their removal when returning
+     * the entry.
+     *
+     * @param next the next interceptor to call in the chain
+     * @param user the user associated with the call
+     * @param dn the name of the entry being looked up
+     * @param entry the raw entry pulled from the nexus
+     * @throws NamingException
+     */
+    private void checkLookupAccess( NextInterceptor next, LdapPrincipal user, Name dn, Attributes entry )
+            throws NamingException
+    {
+        Set userGroups = groupCache.getGroups( user.getName() );
+        Collection tuples = new HashSet();
+        addPerscriptiveAciTuples( tuples, dn, entry );
+        addEntryAciTuples( tuples, entry );
+        addSubentryAciTuples( tuples, dn, entry );
+
+        // 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 );
+
+        // check that we have read access to every attribute type and value
+        Collection perms = Collections.singleton( MicroOperation.READ );
+        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(), dn,
+                        attr.getID(), attr.get( ii ), perms, tuples, entry );
+            }
+        }
+    }
+
+
     public Attributes lookup( NextInterceptor next, Name dn, String[] attrIds ) throws NamingException
     {
-        return super.lookup( next, dn, attrIds );
+        Attributes entry = nexus.lookup( dn, attrIds );
+        LdapPrincipal user = ( ( ServerContext ) InvocationStack.getInstance().peek().getCaller() ).getPrincipal();
+
+        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
+        {
+            return next.lookup( dn, attrIds );
+        }
+
+        checkLookupAccess( next, user, dn, entry );
+
+        return next.lookup( dn, attrIds );
     }
 
 
     public Attributes lookup( NextInterceptor next, Name name ) throws NamingException
     {
-        return super.lookup( next, name );
+        Attributes entry = nexus.lookup( name );
+        LdapPrincipal user = ( ( ServerContext ) InvocationStack.getInstance().peek().getCaller() ).getPrincipal();
+
+        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
+        {
+            return next.lookup( name );
+        }
+
+        checkLookupAccess( next, user, name, entry );
+
+        return next.lookup( name );
     }
 
 

Modified: directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/AbstractAuthorizationTest.java
URL: http://svn.apache.org/viewcvs/directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/AbstractAuthorizationTest.java?rev=321015&r1=321014&r2=321015&view=diff
==============================================================================
--- directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/AbstractAuthorizationTest.java (original)
+++ directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/AbstractAuthorizationTest.java Thu Oct 13 22:31:38 2005
@@ -17,7 +17,8 @@
 package org.apache.ldap.server.authz;
 
 
-import org.apache.ldap.server.AbstractNonAdminTestCase;
+import org.apache.ldap.server.AbstractTestCase;
+import org.apache.ldap.server.partition.DirectoryPartitionNexus;
 import org.apache.ldap.server.subtree.SubentryService;
 import org.apache.ldap.common.name.LdapName;
 
@@ -29,12 +30,16 @@
 
 /**
  * A base class used for authorization tests.  It has some extra utility methods
- * added to it which are required by all authorization tests.
+ * added to it which are required by all authorization tests.  Note that we use
+ * the admin test case otherwise failures will result without browse permission
+ * when setting up the test case for non-admin users.  Anyway we do not use the
+ * context created for the non-admin user since it is anonymous, we get our own
+ * contexts.
  *
  * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
  * @version $Rev$
  */
-public abstract class AbstractAuthorizationTest extends AbstractNonAdminTestCase
+public abstract class AbstractAuthorizationTest extends AbstractTestCase
 {
     /**
      * Creates an abstract authorization test case which enables the
@@ -42,7 +47,7 @@
      */
     public AbstractAuthorizationTest()
     {
-        super();
+        super( DirectoryPartitionNexus.ADMIN_PRINCIPAL, "secret" );
         super.configuration.setAccessControlEnabled( true );
     }
 
@@ -60,7 +65,7 @@
      */
     public DirContext getContextAsAdmin() throws NamingException
     {
-        return getContextAsAdmin( "ou=system" );
+        return getContextAsAdmin( DirectoryPartitionNexus.SYSTEM_PARTITION_SUFFIX );
     }
 
 
@@ -78,7 +83,7 @@
         Hashtable env = ( Hashtable ) sysRoot.getEnvironment().clone();
         env.put( DirContext.PROVIDER_URL, dn );
         env.put( DirContext.SECURITY_AUTHENTICATION, "simple" );
-        env.put( DirContext.SECURITY_PRINCIPAL, "uid=admin,ou=system" );
+        env.put( DirContext.SECURITY_PRINCIPAL, DirectoryPartitionNexus.ADMIN_PRINCIPAL );
         env.put( DirContext.SECURITY_CREDENTIALS, "secret" );
         return new InitialDirContext( env );
     }
@@ -164,7 +169,7 @@
      */
     public DirContext getContextAs( Name user, String password ) throws NamingException
     {
-        return getContextAs( user, password, "ou=system" );
+        return getContextAs( user, password, DirectoryPartitionNexus.SYSTEM_PARTITION_SUFFIX );
     }
 
 

Modified: directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/AddAuthorizationTest.java
URL: http://svn.apache.org/viewcvs/directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/AddAuthorizationTest.java?rev=321015&r1=321014&r2=321015&view=diff
==============================================================================
--- directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/AddAuthorizationTest.java (original)
+++ directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/AddAuthorizationTest.java Thu Oct 13 22:31:38 2005
@@ -96,7 +96,7 @@
                 "userClasses { userGroup { \"cn=Administrators,ou=groups,ou=system\" } }, " +
                 "userPermissions { { " +
                 "protectedItems {entry, allUserAttributeTypesAndValues}, " +
-                "grantsAndDenials { grantAdd } } } } }" );
+                "grantsAndDenials { grantAdd, 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
@@ -132,7 +132,7 @@
                 "userClasses { name { \"uid=billyd,ou=users,ou=system\" } }, " +
                 "userPermissions { { " +
                 "protectedItems {entry, allUserAttributeTypesAndValues}, " +
-                "grantsAndDenials { grantAdd } } } } }" );
+                "grantsAndDenials { grantAdd, grantBrowse } } } } }" );
 
         // should work now that billyd is authorized by name
         assertTrue( checkCanAddEntryAs( "billyd", "billyd", "ou=testou" ) );
@@ -161,7 +161,7 @@
                 "userClasses { subtree { { base \"ou=users,ou=system\" } } }, " +
                 "userPermissions { { " +
                 "protectedItems {entry, allUserAttributeTypesAndValues}, " +
-                "grantsAndDenials { grantAdd } } } } }" );
+                "grantsAndDenials { grantAdd, grantBrowse } } } } }" );
 
         // should work now that billyd is authorized by the subtree userClass
         assertTrue( checkCanAddEntryAs( "billyd", "billyd", "ou=testou" ) );
@@ -190,7 +190,7 @@
                 "userClasses { allUsers }, " +
                 "userPermissions { { " +
                 "protectedItems {entry, allUserAttributeTypesAndValues}, " +
-                "grantsAndDenials { grantAdd } } } } }" );
+                "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

Added: directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/CompareAuthorizationTest.java
URL: http://svn.apache.org/viewcvs/directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/CompareAuthorizationTest.java?rev=321015&view=auto
==============================================================================
--- directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/CompareAuthorizationTest.java (added)
+++ directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/CompareAuthorizationTest.java Thu Oct 13 22:31:38 2005
@@ -0,0 +1,218 @@
+/*
+ *   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.exception.LdapNoPermissionException;
+import org.apache.ldap.common.name.LdapName;
+import org.apache.ldap.server.jndi.ServerDirContext;
+import org.apache.ldap.server.jndi.ServerLdapContext;
+
+import javax.naming.NamingException;
+import javax.naming.directory.*;
+
+
+/**
+ * Tests whether or not authorization around entry compare operations work properly.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+public class CompareAuthorizationTest extends AbstractAuthorizationTest
+{
+    /**
+     * Checks if an attribute of a simple entry (an organizationalUnit's telephoneNumber)
+     * with an RDN relative to ou=system can be compared by a specific non-admin user.
+     * If a permission exception is encountered it is caught and false is returned,
+     * otherwise true is returned.  The entry is deleted after being created just in case
+     * subsequent calls to this method are made in the same test case: the admin account
+     * is used to add and delete this test entry so permissions to add and delete are not
+     * required to test the compare operation by the user.
+     *
+     * @param uid the unique identifier for the user (presumed to exist under ou=users,ou=system)
+     * @param password the password of this user
+     * @param entryRdn the relative DN, relative to ou=system where entry is created
+     * for comparison test
+     * @param number the telephone number to compare to this one
+     * @return true if the entry's telephoneNumber can be compared by the user at the
+     * specified location, false otherwise.  A false compare result still returns
+     * true.
+     * @throws javax.naming.NamingException if there are problems conducting the test
+     */
+    public boolean checkCanCompareTelephoneNumberAs( String uid, String password, String entryRdn, String number )
+            throws NamingException
+    {
+        // create the entry with the telephoneNumber attribute to compare
+        Attributes testEntry = new BasicAttributes( "ou", "testou", true );
+        Attribute objectClass = new BasicAttribute( "objectClass" );
+        testEntry.put( objectClass );
+        objectClass.add( "top" );
+        objectClass.add( "organizationalUnit" );
+        testEntry.put( "telephoneNumber", "867-5309" );  // jenny don't change your number
+
+        DirContext adminContext = getContextAsAdmin();
+
+        try
+        {
+            // create the entry as admin
+            LdapName userName = new LdapName( "uid="+uid+",ou=users,ou=system" );
+            adminContext.createSubcontext( entryRdn, testEntry );
+
+            // compare the telephone numbers
+            DirContext userContext = getContextAs( userName, password );
+            ServerLdapContext ctx = ( ServerLdapContext ) userContext.lookup( "" );
+            ctx.compare( new LdapName( entryRdn + ",ou=system" ), "telephoneNumber", number );
+
+            // don't return compare result which can be false but true since op was permitted
+            return true;
+        }
+        catch ( LdapNoPermissionException e )
+        {
+            return false;
+        }
+        finally
+        {
+            // let's clean up
+            adminContext.destroySubcontext( entryRdn );
+        }
+    }
+
+
+    /**
+     * Checks to make sure group membership based userClass works for compare operations.
+     *
+     * @throws javax.naming.NamingException if the test encounters an error
+     */
+    public void testGrantCompareAdministrators() throws NamingException
+    {
+        // create the non-admin user
+        createUser( "billyd", "billyd" );
+
+        // try a compare operation which should fail without any ACI
+        assertFalse( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
+
+        // Gives grantCompare, and grantRead perm to all users in the Administrators group for
+        // entries and all attribute types and values
+        createAccessControlSubentry( "administratorAdd", "{ " +
+                "identificationTag \"addAci\", " +
+                "precedence 14, " +
+                "authenticationLevel none, " +
+                "itemOrUserFirst userFirst: { " +
+                "userClasses { userGroup { \"cn=Administrators,ou=groups,ou=system\" } }, " +
+                "userPermissions { { " +
+                "protectedItems {entry, allUserAttributeTypesAndValues}, " +
+                "grantsAndDenials { grantCompare, grantRead, 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( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
+
+        // 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( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "976-6969" ) );
+    }
+
+
+    /**
+     * Checks to make sure name based userClass works for compare operations.
+     *
+     * @throws javax.naming.NamingException if the test encounters an error
+     */
+    public void testGrantCompareByName() throws NamingException
+    {
+        // create the non-admin user
+        createUser( "billyd", "billyd" );
+
+        // try an compare operation which should fail without any ACI
+        assertFalse( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
+
+        // now add a subentry that enables user billyd to compare 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 { grantCompare, grantRead, grantBrowse } } } } }" );
+
+        // should work now that billyd is authorized by name
+        assertTrue( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
+    }
+
+
+    /**
+     * Checks to make sure subtree based userClass works for compare operations.
+     *
+     * @throws javax.naming.NamingException if the test encounters an error
+     */
+    public void testGrantCompareBySubtree() throws NamingException
+    {
+        // create the non-admin user
+        createUser( "billyd", "billyd" );
+
+        // try a compare operation which should fail without any ACI
+        assertFalse( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
+
+        // now add a subentry that enables user billyd to compare 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 { grantCompare, grantRead, grantBrowse } } } } }" );
+
+        // should work now that billyd is authorized by the subtree userClass
+        assertTrue( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
+    }
+
+
+    /**
+     * Checks to make sure <b>allUsers</b> userClass works for compare operations.
+     *
+     * @throws javax.naming.NamingException if the test encounters an error
+     */
+    public void testGrantCompareAllUsers() throws NamingException
+    {
+        // create the non-admin user
+        createUser( "billyd", "billyd" );
+
+        // try an add operation which should fail without any ACI
+        assertFalse( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
+
+        // 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 { grantCompare, grantRead, grantBrowse } } } } }" );
+
+        // see if we can now compare that test entry's number which we could not before
+        // should work with billyd now that all users are authorized
+        assertTrue( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
+    }
+}

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

Modified: directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/DeleteAuthorizationTest.java
URL: http://svn.apache.org/viewcvs/directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/DeleteAuthorizationTest.java?rev=321015&r1=321014&r2=321015&view=diff
==============================================================================
--- directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/DeleteAuthorizationTest.java (original)
+++ directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/DeleteAuthorizationTest.java Thu Oct 13 22:31:38 2005
@@ -103,7 +103,7 @@
                 "userClasses { userGroup { \"cn=Administrators,ou=groups,ou=system\" } }, " +
                 "userPermissions { { " +
                 "protectedItems {entry}, " +
-                "grantsAndDenials { grantRemove } } } } }" );
+                "grantsAndDenials { grantRemove, grantBrowse } } } } }" );
 
         // see if we can now delete that test entry which we could not before
         // delete op should still fail since billd is not in the admin group
@@ -139,7 +139,7 @@
                 "userClasses { name { \"uid=billyd,ou=users,ou=system\" } }, " +
                 "userPermissions { { " +
                 "protectedItems {entry}, " +
-                "grantsAndDenials { grantRemove } } } } }" );
+                "grantsAndDenials { grantRemove, grantBrowse } } } } }" );
 
         // should work now that billyd is authorized by name
         assertTrue( checkCanDeleteEntryAs( "billyd", "billyd", "ou=testou" ) );
@@ -168,7 +168,7 @@
                 "userClasses { subtree { { base \"ou=users,ou=system\" } } }, " +
                 "userPermissions { { " +
                 "protectedItems {entry}, " +
-                "grantsAndDenials { grantRemove } } } } }" );
+                "grantsAndDenials { grantRemove, grantBrowse } } } } }" );
 
         // should work now that billyd is authorized by the subtree userClass
         assertTrue( checkCanDeleteEntryAs( "billyd", "billyd", "ou=testou" ) );
@@ -197,7 +197,7 @@
                 "userClasses { allUsers }, " +
                 "userPermissions { { " +
                 "protectedItems {entry}, " +
-                "grantsAndDenials { grantRemove } } } } }" );
+                "grantsAndDenials { grantRemove, grantBrowse } } } } }" );
 
         // see if we can now delete that test entry which we could not before
         // should work now with billyd now that all users are authorized