Package org.apache.directory.server.core.authz

Source Code of org.apache.directory.server.core.authz.SearchAuthorizationIT

/*
*  Licensed to the Apache Software Foundation (ASF) under one
*  or more contributor license agreements.  See the NOTICE file
*  distributed with this work for additional information
*  regarding copyright ownership.  The ASF licenses this file
*  to you under the Apache License, Version 2.0 (the
*  "License"); you may not use this file except in compliance
*  with the License.  You may obtain a copy of the License at
*    http://www.apache.org/licenses/LICENSE-2.0
*  Unless required by applicable law or agreed to in writing,
*  software distributed under the License is distributed on an
*  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
*  KIND, either express or implied.  See the License for the
*  specific language governing permissions and limitations
*  under the License.
*/
package org.apache.directory.server.core.authz;


import static org.apache.directory.server.core.authz.AutzIntegUtils.addEntryACI;
import static org.apache.directory.server.core.authz.AutzIntegUtils.addPrescriptiveACI;
import static org.apache.directory.server.core.authz.AutzIntegUtils.addSubentryACI;
import static org.apache.directory.server.core.authz.AutzIntegUtils.addUserToGroup;
import static org.apache.directory.server.core.authz.AutzIntegUtils.createAccessControlSubentry;
import static org.apache.directory.server.core.authz.AutzIntegUtils.createUser;
import static org.apache.directory.server.core.authz.AutzIntegUtils.deleteAccessControlSubentry;
import static org.apache.directory.server.core.authz.AutzIntegUtils.getContextAs;
import static org.apache.directory.server.core.integ.IntegrationUtils.getSystemContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.HashMap;
import java.util.Map;

import javax.naming.Name;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapContext;

import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
import org.apache.directory.server.core.integ.FrameworkRunner;
import org.apache.directory.shared.ldap.exception.LdapNameNotFoundException;
import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
import org.apache.directory.shared.ldap.name.DN;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;


/**
* 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: 919009 $
*/
@RunWith ( FrameworkRunner.class )
public class SearchAuthorizationIT extends AbstractLdapTestUnit
{

    @Before
    public void setService()
    {
       AutzIntegUtils.service = service;
    }
   
   
    /**
     * The search results of tests are added to this map via put (<String, SearchResult>)
     * the map is also cleared before each search test.  This allows further inspections
     * of the results for more specific test cases.
     */
    private Map<String, SearchResult> results = new HashMap<String, SearchResult>();


    /**
     * 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 BasicAttributes( true );
            Attribute oc = new BasicAttribute( "objectClass" );
            oc.add( "top" );
            oc.add( "organizationalUnit" );
            attributes[ii].put( oc );
            Attribute ou = new BasicAttribute( "ou" );
            ou.add( String.valueOf( ii ) );
            ou.add( "testEntry" );
            attributes[ii].put( ou );
            attributes[ii].put( "telephoneNumber", String.valueOf( count ) );
        }

        return attributes;
    }


    private void recursivelyAddSearchData( Name parent, Attributes[] children, final long sizeLimit, long[] count )
        throws Exception
    {
        Name[] childRdns = new Name[children.length];
       
        for ( int i = 0; ( i < children.length ) && ( count[0] < sizeLimit ); i++ )
        {
            Name childRdn = new DN();
            childRdn.addAll( parent );
            childRdn.add( "ou=" + i );
            childRdns[i] = childRdn;
            getSystemContext( service ).createSubcontext( childRdn, children[i] );
            count[0]++;
        }

        if ( count[0] >= sizeLimit )
        {
            return;
        }

        for ( int i = 0; ( i < children.length ) && ( count[0] < sizeLimit ); i++ )
        {
            recursivelyAddSearchData( childRdns[i], 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 how to brach the data
     * @param sizelimit the amount of entries
     * @return the immediate child node created under parent which contains the subtree
     * @throws NamingException on error
     */
    private Name addSearchData( Name parent, int branchingFactor, long sizelimit ) throws Exception
    {
        parent = ( Name ) parent.clone();
        parent.add( "ou=tests" );
        getSystemContext( service ).createSubcontext( parent, getTestNodes( 1 )[0] );
        recursivelyAddSearchData( parent, getTestNodes( branchingFactor ), sizelimit, new long[]
            { 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 Exception
    {
        LdapContext sysRoot = getSystemContext( service );
        NamingEnumeration<SearchResult> results = sysRoot.search( rdn, "(objectClass=*)", new SearchControls() );
       
        while ( results.hasMore() )
        {
            SearchResult result = results.next();
            Name childRdn = new DN( 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 Exception
    {
        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 Exception
    {
        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 cons search controls
     * @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 Exception
    {
        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 cons search controls
     * @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 Exception
    {
        if ( cons == null )
        {
            cons = new SearchControls();
        }

        Name base = addSearchData( new DN(), 3, 10 );
        Name userDn = new DN( "uid=" + uid + ",ou=users,ou=system" );
        try
        {
            results.clear();
            DirContext userCtx = getContextAs( userDn, password );
            NamingEnumeration<SearchResult> list = userCtx.search( base, filter, cons );
            int counter = 0;
           
            while ( list.hasMore() )
            {
                SearchResult result = list.next();
                results.put( result.getName(), result );
                counter++;
            }
            return counter == resultSetSz;
        }
        catch ( LdapNoPermissionException e )
        {
            return false;
        }
        finally
        {
            recursivelyDelete( base );
        }
    }


    /**
     * Adds an entryACI to specified entry below ou=system and runs a search.  Then it
     * checks to see the result size is correct.
     *
     * @param uid the uid RDN attribute value for the user under ou=users,ou=system
     * @param password the password of the user
     * @param cons the search controls
     * @param rdn the rdn
     * @param aci the aci
     * @param resultSetSz the result sz
     * @return true if the search succeeds as expected, false otherwise
     * @throws NamingException if there are problems conducting the search
     */
    private boolean checkSearchAsWithEntryACI( String uid, String password, SearchControls cons, Name rdn, String aci,
        int resultSetSz ) throws Exception
    {
        if ( cons == null )
        {
            cons = new SearchControls();
        }

        Name base = addSearchData( new DN(), 3, 10 );
        addEntryACI( rdn, aci );
        Name userDn = new DN( "uid=" + uid + ",ou=users,ou=system" );
        try
        {
            results.clear();
            DirContext userCtx = getContextAs( userDn, password );
            NamingEnumeration<SearchResult> list = userCtx.search( base, "(objectClass=*)", cons );
            int counter = 0;
           
            while ( list.hasMore() )
            {
                SearchResult result = list.next();
                results.put( result.getName(), result );
                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
     */
    @Test
    public void testAddSearchData() throws Exception
    {
        LdapContext sysRoot = getSystemContext( service );
        Name base = addSearchData( new DN(), 3, 10 );
        SearchControls controls = new SearchControls();
        controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
        NamingEnumeration<SearchResult> results = sysRoot.search( base, "(objectClass=*)", controls );
        int counter = 0;
       
        while ( results.hasMore() )
        {
            results.next();
            counter++;
        }

        assertEquals( 10, counter );
        recursivelyDelete( base );
        //noinspection EmptyCatchBlock
        try
        {
            sysRoot.lookup( base );
            fail();
        }
        catch ( LdapNameNotFoundException e )
        {
        }
    }


    // -----------------------------------------------------------------------
    // All or nothing search ACI rule tests
    // -----------------------------------------------------------------------

    /**
     * Checks to make sure group membership based userClass works for add operations.
     *
     * @throws javax.naming.NamingException if the test encounters an error
     */
    @Test
    public void testGrantAdministrators() throws Exception
    {
        // create the non-admin user
        createUser( "billyd", "billyd" );

        // try an add operation which should fail without any ACI
        assertFalse( checkCanSearchAs( "billyd", "billyd" ) );

        // Gives search perms to all users in the Administrators group for
        // entries and all attribute types and values
        createAccessControlSubentry( "searchAdmin", "{ " + "identificationTag \"searchAci\", " + "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 search operations.
     *
     * @throws javax.naming.NamingException if the test encounters an error
     */
    @Test
    public void testGrantSearchByName() throws Exception
    {
        // 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( "billydSearch", "{ " + "identificationTag \"searchAci\", " + "precedence 14, "
            + "authenticationLevel none, " + "itemOrUserFirst userFirst: { "
            + "userClasses { name { \"uid=billyd,ou=users,ou=system\" } }, " + "userPermissions { { "
            + "protectedItems {entry, allUserAttributeTypesAndValues}, "
            + "grantsAndDenials { grantRead, grantReturnDN, grantBrowse } } } } }" );

        // should work now that billyd is authorized by name
        assertTrue( checkCanSearchAs( "billyd", "billyd" ) );
    }


    /**
     * Checks to make sure name based userClass works for search operations
     * when we vary the case of the DN.
     *
     * @throws javax.naming.NamingException if the test encounters an error
     */
    @Test
    public void testGrantSearchByNameUserDnCase() throws Exception
    {
        // 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( "billydSearch", "{ " + "identificationTag \"searchAci\", " + "precedence 14, "
            + "authenticationLevel none, " + "itemOrUserFirst userFirst: { "
            + "userClasses { name { \"uid=billyd,ou=users,ou=system\" } }, " + "userPermissions { { "
            + "protectedItems {entry, allUserAttributeTypesAndValues}, "
            + "grantsAndDenials { grantRead, grantReturnDN, grantBrowse } } } } }" );

        // should work now that billyd is authorized by name
        assertTrue( checkCanSearchAs( "BillyD", "billyd" ) );
    }


    /**
     * Checks to make sure subtree based userClass works for search operations.
     *
     * @throws javax.naming.NamingException if the test encounters an error
     */
    @Test
    public void testGrantSearchBySubtree() throws Exception
    {
        // 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( "billySearchBySubtree", "{ " + "identificationTag \"searchAci\", "
            + "precedence 14, " + "authenticationLevel none, " + "itemOrUserFirst userFirst: { "
            + "userClasses { subtree { { base \"ou=users,ou=system\" } } }, " + "userPermissions { { "
            + "protectedItems {entry, allUserAttributeTypesAndValues}, "
            + "grantsAndDenials {  grantRead, grantReturnDN, 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 search operations.
     *
     * @throws javax.naming.NamingException if the test encounters an error
     */
    @Test
    public void testGrantSearchAllUsers() throws Exception
    {
        // create the non-admin user
        createUser( "billyd", "billyd" );

        // try an search operation which should fail without any ACI
        assertFalse( checkCanSearchAs( "billyd", "billyd" ) );

        // now add a subentry that enables anyone to search an entry below ou=system
        createAccessControlSubentry( "anybodySearch", "{ " + "identificationTag \"searchAci\", " + "precedence 14, "
            + "authenticationLevel none, " + "itemOrUserFirst userFirst: { " + "userClasses { allUsers }, "
            + "userPermissions { { " + "protectedItems {entry, allUserAttributeTypesAndValues}, "
            + "grantsAndDenials { grantRead, grantReturnDN, grantBrowse } } } } }" );

        // see if we can now search that tree which we could not before
        // should work now with billyd now that all users are authorized
        assertTrue( checkCanSearchAs( "billyd", "billyd" ) );
    }


    // -----------------------------------------------------------------------
    //
    // -----------------------------------------------------------------------

    /**
     * Checks to make sure search does not return entries not assigned the
     * perscriptiveACI and that it does not fail with an exception.
     *
     * @throws javax.naming.NamingException if the test encounters an error
     */
    @Test
    public void testSelectiveGrantsAllUsers() throws Exception
    {
        // create the non-admin user
        createUser( "billyd", "billyd" );

        // try an add operation which should fail without any ACI
        SearchControls cons = new SearchControls();
        cons.setSearchScope( SearchControls.SUBTREE_SCOPE );
        assertFalse( checkCanSearchAs( "billyd", "billyd", cons, 4 ) );

        // now add a subentry that enables anyone to add an entry below ou=system
        // down two more rdns for DNs of a max size of 3
        createAccessControlSubentry( "anybodySearch", "{ maximum 2 }", "{ " + "identificationTag \"searchAci\", "
            + "precedence 14, " + "authenticationLevel none, " + "itemOrUserFirst userFirst: { "
            + "userClasses { allUsers }, " + "userPermissions { { "
            + "protectedItems {entry, allUserAttributeTypesAndValues}, "
            + "grantsAndDenials { grantRead, grantReturnDN, 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", cons, 4 ) );
    }


    /**
     * Checks to make sure attributeTypes are not present when permissions are
     * not given for reading them and their values.
     *
     * @throws javax.naming.NamingException if the test encounters an error
     */
    @Test
    public void testHidingAttributes() throws Exception
    {
        // create the non-admin user
        createUser( "billyd", "billyd" );

        // try an add operation which should fail without any ACI
        SearchControls cons = new SearchControls();
        cons.setSearchScope( SearchControls.SUBTREE_SCOPE );
        assertFalse( checkCanSearchAs( "billyd", "billyd", cons, 4 ) );

        // now add a subentry that enables anyone to search an entry below ou=system
        // down two more rdns for DNs of a max size of 3.  It only grants access to
        // the ou and objectClass attributes however.
        createAccessControlSubentry( "excluseTelephoneNumber", "{ maximum 2 }", "{ "
            + "identificationTag \"searchAci\", " + "precedence 14, " + "authenticationLevel none, "
            + "itemOrUserFirst userFirst: { " + "userClasses { allUsers }, " + "userPermissions { { "
            + "protectedItems {entry, allAttributeValues { ou, objectClass } }, "
            + "grantsAndDenials { grantRead, grantReturnDN, grantBrowse } } } } }" );

        // see if we can now add that search and find 4 entries
        assertTrue( checkCanSearchAs( "billyd", "billyd", cons, 4 ) );

        // check to make sure the telephoneNumber attribute is not present in results
        for ( SearchResult result : results.values() )
        {
            assertNull( result.getAttributes().get( "telephoneNumber" ) );
        }

        // delete the subentry to test more general rule's inclusion of telephoneNumber
        deleteAccessControlSubentry( "excluseTelephoneNumber" );

        // now add a subentry that enables anyone to search an entry below ou=system
        // down two more rdns for DNs of a max size of 3.  This time we should be able
        // to see the telephoneNumber attribute
        createAccessControlSubentry( "includeAllAttributeTypesAndValues", "{ maximum 2 }", "{ "
            + "identificationTag \"searchAci\", " + "precedence 14, " + "authenticationLevel none, "
            + "itemOrUserFirst userFirst: { " + "userClasses { allUsers }, " + "userPermissions { { "
            + "protectedItems {entry, allUserAttributeTypesAndValues }, "
            + "grantsAndDenials { grantRead, grantReturnDN, grantBrowse } } } } }" );

        // again we should find four entries
        assertTrue( checkCanSearchAs( "billyd", "billyd", cons, 4 ) );

        // check now to make sure the telephoneNumber attribute is present in results
        for ( SearchResult result : results.values() )
        {
            assertNotNull( result.getAttributes().get( "telephoneNumber" ) );
        }
    }


    /**
     * Checks to make sure specific attribute values are not present when
     * read permission is denied.
     *
     * @throws javax.naming.NamingException if the test encounters an error
     */
    @Test
    public void testHidingAttributeValues() throws Exception
    {
        // create the non-admin user
        createUser( "billyd", "billyd" );

        // try an add operation which should fail without any ACI
        assertFalse( checkCanSearchAs( "billyd", "billyd", 3 ) );

        // now add a subentry that enables anyone to search an entry below ou=system
        // down two more rdns for DNs of a max size of 3.  It only grants access to
        // the ou and objectClass attributes however.
        createAccessControlSubentry(
            "excluseOUValue",
            "{ maximum 2 }",
            "{ "
                + "identificationTag \"searchAci\", "
                + "precedence 14, "
                + "authenticationLevel none, "
                + "itemOrUserFirst userFirst: { "
                + "userClasses { allUsers }, "
                + "userPermissions { { "
                + "protectedItems {entry, attributeType { ou }, allAttributeValues { objectClass }, attributeValue { ou=0, ou=1, ou=2 } }, "
                + "grantsAndDenials { grantRead, grantReturnDN, grantBrowse } } } } }" );

        // see if we can now add that search and find 4 entries
        assertTrue( checkCanSearchAs( "billyd", "billyd", 3 ) );

        // check to make sure the ou attribute value "testEntry" is not present in results
        for ( SearchResult result : results.values() )
        {
            assertFalse( result.getAttributes().get( "ou" ).contains( "testEntry" ) );
        }

        // delete the subentry to test more general rule's inclusion of all values
        deleteAccessControlSubentry( "excluseOUValue" );

        // now add a subentry that enables anyone to search an entry below ou=system
        // down two more rdns for DNs of a max size of 3.  This time we should be able
        // to see the telephoneNumber attribute
        createAccessControlSubentry( "includeAllAttributeTypesAndValues", "{ maximum 2 }", "{ "
            + "identificationTag \"searchAci\", " + "precedence 14, " + "authenticationLevel none, "
            + "itemOrUserFirst userFirst: { " + "userClasses { allUsers }, " + "userPermissions { { "
            + "protectedItems {entry, allUserAttributeTypesAndValues }, "
            + "grantsAndDenials { grantRead, grantReturnDN, grantBrowse } } } } }" );

        // again we should find four entries
        assertTrue( checkCanSearchAs( "billyd", "billyd", 3 ) );

        // check now to make sure the telephoneNumber attribute is present in results
        for ( SearchResult result : results.values() )
        {
            assertTrue( result.getAttributes().get( "ou" ).contains( "testEntry" ) );
        }
    }


    /**
     * Adds a perscriptiveACI to allow search, tests for success, then adds entryACI
     * to deny read, browse and returnDN to a specific entry and checks to make sure
     * that entry cannot be accessed via search as a specific user.
     *
     * @throws NamingException if the test is broken
     */
    @Test
    public void testPerscriptiveGrantWithEntryDenial() throws Exception
    {
        // create the non-admin user
        createUser( "billyd", "billyd" );

        // now add an entryACI denies browse, read and returnDN to a specific entry
        String aci = "{ " + "identificationTag \"denyAci\", " + "precedence 14, " + "authenticationLevel none, "
            + "itemOrUserFirst userFirst: { " + "userClasses { allUsers }, " + "userPermissions { { "
            + "protectedItems {entry, allUserAttributeTypesAndValues}, "
            + "grantsAndDenials { denyRead, denyReturnDN, denyBrowse } } } } }";

        // try a search operation which should fail without any prescriptive ACI
        SearchControls cons = new SearchControls();
        cons.setSearchScope( SearchControls.SUBTREE_SCOPE );
        DN rdn = new DN( "ou=tests" );
        assertFalse( checkSearchAsWithEntryACI( "billyd", "billyd", cons, rdn, aci, 9 ) );

        // now add a subentry that enables anyone to search below ou=system
        createAccessControlSubentry( "anybodySearch", "{ " + "identificationTag \"searchAci\", " + "precedence 14, "
            + "authenticationLevel none, " + "itemOrUserFirst userFirst: { " + "userClasses { allUsers }, "
            + "userPermissions { { " + "protectedItems {entry, allUserAttributeTypesAndValues}, "
            + "grantsAndDenials { grantRead, grantReturnDN, grantBrowse } } } } }" );

        // see if we can now search the tree which we could not before
        // should work with billyd now that all users are authorized
        // we should NOT see the entry we are about to deny access to
        assertTrue( checkSearchAsWithEntryACI( "billyd", "billyd", cons, rdn, aci, 9 ) );
        assertNull( results.get( "ou=tests,ou=system" ) );

        // try without the entry ACI .. just perscriptive and see ou=tests,ou=system
        assertTrue( checkCanSearchAs( "billyd", "billyd", cons, 10 ) );
        assertNotNull( results.get( "ou=tests,ou=system" ) );
    }


    /**
     * Adds a perscriptiveACI to allow search, tests for success, then adds entryACI
     * to deny read, browse and returnDN to a specific entry and checks to make sure
     * that entry cannot be accessed via search as a specific user.  Here the
     * precidence of the ACI is put to the test.
     *
     * @throws NamingException if the test is broken
     */
    @Test
    public void testPerscriptiveGrantWithEntryDenialWithPrecidence() throws Exception
    {
        // create the non-admin user
        createUser( "billyd", "billyd" );

        // now add an entryACI denies browse, read and returnDN to a specific entry
        String aci = "{ " + "identificationTag \"denyAci\", " + "precedence 14, " + "authenticationLevel none, "
            + "itemOrUserFirst userFirst: { " + "userClasses { allUsers }, " + "userPermissions { { "
            + "protectedItems {entry, allUserAttributeTypesAndValues}, "
            + "grantsAndDenials { denyRead, denyReturnDN, denyBrowse } } } } }";

        // try a search operation which should fail without any prescriptive ACI
        SearchControls cons = new SearchControls();
        cons.setSearchScope( SearchControls.SUBTREE_SCOPE );
        DN rdn = new DN( "ou=tests" );
        assertFalse( checkSearchAsWithEntryACI( "billyd", "billyd", cons, rdn, aci, 9 ) );

        // now add a subentry that enables anyone to search below ou=system
        createAccessControlSubentry( "anybodySearch", "{ " + "identificationTag \"searchAci\", " + "precedence 15, "
            + "authenticationLevel none, " + "itemOrUserFirst userFirst: { " + "userClasses { allUsers }, "
            + "userPermissions { { " + "protectedItems {entry, allUserAttributeTypesAndValues}, "
            + "grantsAndDenials { grantRead, grantReturnDN, grantBrowse } } } } }" );

        // see if we can now search the tree which we could not before
        // should work with billyd now that all users are authorized
        // we should also see the entry we are about to deny access to
        // we see it because the precidence of the grant is greater
        // than the precedence of the denial
        assertTrue( checkSearchAsWithEntryACI( "billyd", "billyd", cons, rdn, aci, 10 ) );
        assertNotNull( results.get( "ou=tests,ou=system" ) );

        // now add an entryACI denies browse, read and returnDN to a specific entry
        // but this time the precedence will be higher than that of the grant
        aci = "{ " + "identificationTag \"denyAci\", " + "precedence 16, " + "authenticationLevel none, "
            + "itemOrUserFirst userFirst: { " + "userClasses { allUsers }, " + "userPermissions { { "
            + "protectedItems {entry, allUserAttributeTypesAndValues}, "
            + "grantsAndDenials { denyRead, denyReturnDN, denyBrowse } } } } }";

        // see if we can now search the tree which we could not before
        // should work with billyd now that all users are authorized
        // we should NOT see the entry we are about to deny access to
        // we do NOT see it because the precidence of the grant is less
        // than the precedence of the denial - so the denial wins
        assertTrue( checkSearchAsWithEntryACI( "billyd", "billyd", cons, rdn, aci, 9 ) );
        assertNull( results.get( "ou=tests,ou=system" ) );
    }


    /**
     * Performs an object level search on the specified subentry relative to ou=system as a specific user.
     *
     * @param uid the uid RDN attribute value of the user to perform the search as
     * @param password the password of the user
     * @param rdn the relative name to the subentry under the ou=system AP
     * @return the single search result if access is allowed or null
     * @throws NamingException if the search fails w/ exception other than no permission
     */
    private SearchResult checkCanSearhSubentryAs( String uid, String password, Name rdn ) throws Exception
    {
        DirContext userCtx = getContextAs( new DN( "uid=" + uid + ",ou=users,ou=system" ), password );
        SearchControls cons = new SearchControls();
        cons.setSearchScope( SearchControls.OBJECT_SCOPE );
        SearchResult result = null;
        NamingEnumeration<SearchResult> list = null;

        //noinspection EmptyCatchBlock
        try
        {
            list = userCtx.search( rdn, "(objectClass=*)", cons );
            if ( list.hasMore() )
            {
                result = list.next();
                list.close();
                return result;
            }
        }
        catch ( LdapNoPermissionException e )
        {
            return null;
        }
        finally
        {
            if ( list != null )
            {
                list.close();
            }
        }

        return result;
    }


    @Test
    public void testSubentryAccess() throws Exception
    {
        // create the non-admin user
        createUser( "billyd", "billyd" );

        // now add a subentry that enables anyone to search below ou=system
        createAccessControlSubentry( "anybodySearch", "{ " + "identificationTag \"searchAci\", " + "precedence 14, "
            + "authenticationLevel none, " + "itemOrUserFirst userFirst: { " + "userClasses { allUsers }, "
            + "userPermissions { { " + "protectedItems {entry, allUserAttributeTypesAndValues}, "
            + "grantsAndDenials { grantRead, grantReturnDN, grantBrowse } } } } }" );

        // check and see if we can access the subentry now
        assertNotNull( checkCanSearhSubentryAs( "billyd", "billyd", new DN( "cn=anybodySearch" ) ) );

        // now add a denial to prevent all users except the admin from accessing the subentry
        addSubentryACI( "{ " + "identificationTag \"searchAci\", " + "precedence 14, " + "authenticationLevel none, "
            + "itemOrUserFirst userFirst: { " + "userClasses { allUsers }, " + "userPermissions { { "
            + "protectedItems {entry, allUserAttributeTypesAndValues}, "
            + "grantsAndDenials { denyRead, denyReturnDN, denyBrowse } } } } }" );

        // now we should not be able to access the subentry with a search
        assertNull( checkCanSearhSubentryAs( "billyd", "billyd", new DN( "cn=anybodySearch" ) ) );
    }


    @Test
    public void testGetMatchedName() throws Exception
    {
        // create the non-admin user
        createUser( "billyd", "billyd" );

        // now add a subentry that enables anyone to search/lookup and disclose on error
        // below ou=system, with the exclusion of ou=groups and everything below it
        createAccessControlSubentry( "selectiveDiscloseOnError", "{ specificExclusions { chopBefore:\"ou=groups\" } }",
            "{ " + "identificationTag \"searchAci\", " + "precedence 14, " + "authenticationLevel none, "
                + "itemOrUserFirst userFirst: { " + "userClasses { allUsers }, " + "userPermissions { { "
                + "protectedItems {entry, allUserAttributeTypesAndValues}, "
                + "grantsAndDenials { grantRead, grantReturnDN, grantBrowse, grantDiscloseOnError } } } } }" );

        // get a context as the user and try a lookup of a non-existant entry under ou=groups,ou=system
        DirContext userCtx = getContextAs( new DN( "uid=billyd,ou=users,ou=system" ), "billyd" );
        try
        {
            userCtx.lookup( "cn=blah,ou=groups" );
        }
        catch ( NamingException e )
        {
            Name matched = e.getResolvedName();

            // we should not see ou=groups,ou=system for the remaining name
            assertEquals( matched.toString(), "ou=system" );
        }

        // now delete and replace subentry with one that does not excluse ou=groups,ou=system
        deleteAccessControlSubentry( "selectiveDiscloseOnError" );
        createAccessControlSubentry( "selectiveDiscloseOnError", "{ " + "identificationTag \"searchAci\", "
            + "precedence 14, " + "authenticationLevel none, " + "itemOrUserFirst userFirst: { "
            + "userClasses { allUsers }, " + "userPermissions { { "
            + "protectedItems {entry, allUserAttributeTypesAndValues}, "
            + "grantsAndDenials { grantRead, grantReturnDN, grantBrowse, grantDiscloseOnError } } } } }" );

        // now try a lookup of a non-existant entry under ou=groups,ou=system again
        try
        {
            userCtx.lookup( "cn=blah,ou=groups" );
        }
        catch ( NamingException e )
        {
            Name matched = e.getResolvedName();

            // we should not see ou=groups,ou=system for the remaining name
            assertEquals( matched.toString(), "ou=groups,ou=system" );
        }
    }
   
    @Test
    public void testUserClassParentOfEntry() throws Exception
    {
        // create the non-admin user
        createUser( "billyd", "billyd" );
       
        // create an entry subordinate to the user
        DirContext billydCtx = AutzIntegUtils.getContextAsAdmin("uid=billyd,ou=users,ou=system");
        Attributes phoneBook = new BasicAttributes( "ou", "phoneBook", true );
        Attribute objectClass = new BasicAttribute( "objectClass" );
        phoneBook.put( objectClass );
        objectClass.add( "top" );
        objectClass.add( "organizationalUnit" );
        billydCtx.createSubcontext( "ou=phoneBook", phoneBook );

        // now add a subentry that enables anyone to search below their own entries
        createAccessControlSubentry( "anybodySearchTheirSubordinates", "{ " + "identificationTag \"searchAci\", " + "precedence 14, "
            + "authenticationLevel none, " + "itemOrUserFirst userFirst: { " + "userClasses { allUsers }, "
            + "userPermissions { { " + "protectedItems {entry, allUserAttributeTypesAndValues}, "
            + "grantsAndDenials { grantRead, grantReturnDN, grantBrowse } } } } }" );

        // check and see if we can access the subentry now
        assertNotNull( checkCanSearhSubentryAs( "billyd", "billyd", new DN( "ou=phoneBook,uid=billyd,ou=users" ) ) );

        // now add a denial to prevent all users except the admin from accessing the subentry
        addPrescriptiveACI( "anybodySearchTheirSubordinates", "{ " + "identificationTag \"anybodyDontSearchTheirSubordinates\", " + "precedence 14, " + "authenticationLevel none, "
            + "itemOrUserFirst userFirst: { " + "userClasses { parentOfEntry }, " + "userPermissions { { "
            + "protectedItems {entry, allUserAttributeTypesAndValues}, "
            + "grantsAndDenials { denyRead, denyReturnDN, denyBrowse } } } } }" );

        // now we should not be able to access the subentry with a search
        assertNull( checkCanSearhSubentryAs( "billyd", "billyd", new DN( "ou=phoneBook,uid=billyd,ou=users" ) ) );
    }
}
TOP

Related Classes of org.apache.directory.server.core.authz.SearchAuthorizationIT

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.