blob: c3a8ec2f442dad599837efbde93818175f1d25b8 [file] [log] [blame]
/*
* 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.getAdminConnection;
import static org.apache.directory.server.core.authz.AutzIntegUtils.getConnectionAs;
import static org.apache.directory.server.core.authz.AutzIntegUtils.removeEntryACI;
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.net.URL;
import java.util.HashMap;
import java.util.Map;
import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.api.ldap.model.cursor.EntryCursor;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.entry.DefaultModification;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.entry.ModificationOperation;
import org.apache.directory.api.ldap.model.exception.LdapNoPermissionException;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.server.core.annotations.ApplyLdifs;
import org.apache.directory.server.core.annotations.CreateDS;
import org.apache.directory.server.core.annotations.LoadSchema;
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
import org.apache.directory.server.core.integ.FrameworkRunner;
import org.apache.directory.server.core.integ.IntegrationUtils;
import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
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>
*/
@RunWith(FrameworkRunner.class)
@CreateDS(
enableAccessControl = true,
name = "SearchAuthorizationIT",
loadedSchemas =
{ @LoadSchema(name = "nis", enabled = true) })
@ApplyLdifs(
{
"dn: ou=tests,ou=system",
"objectclass: top",
"objectclass: organizationalUnit",
"ou: testEntry",
"ou: 0",
"ou: tests",
"telephonenumber: 1",
"",
"dn: ou=0,ou=tests,ou=system",
"objectclass: top",
"objectclass: organizationalUnit",
"ou: testEntry",
"ou: 0",
"telephonenumber: 3",
"",
"dn: ou=1,ou=tests,ou=system",
"objectclass: top",
"objectclass: organizationalUnit",
"ou: testEntry",
"ou: 1",
"telephonenumber: 3",
"",
"dn: ou=2,ou=tests,ou=system",
"objectclass: top",
"objectclass: organizationalUnit",
"ou: testEntry",
"ou: 2",
"telephonenumber: 3",
"",
"dn: ou=0,ou=0,ou=tests,ou=system",
"objectclass: top",
"objectclass: organizationalUnit",
"ou: testEntry",
"ou: 0",
"telephonenumber: 3",
"",
"dn: ou=1,ou=0,ou=tests,ou=system",
"objectclass: top",
"objectclass: organizationalUnit",
"ou: testEntry",
"ou: 1",
"telephonenumber: 3",
"",
"dn: ou=2,ou=0,ou=tests,ou=system",
"objectclass: top",
"objectclass: organizationalUnit",
"ou: testEntry",
"ou: 2",
"telephonenumber: 3",
"",
"dn: ou=0,ou=0,ou=0,ou=tests,ou=system",
"objectclass: top",
"objectclass: organizationalUnit",
"ou: testEntry",
"ou: 0",
"telephonenumber: 3",
"",
"dn: ou=1,ou=0,ou=0,ou=tests,ou=system",
"objectclass: top",
"objectclass: organizationalUnit",
"ou: testEntry",
"ou: 1",
"telephonenumber: 3",
"",
"dn: ou=2,ou=0,ou=0,ou=tests,ou=system",
"objectclass: top",
"objectclass: organizationalUnit",
"ou: testEntry",
"ou: 2",
"telephonenumber: 3"
})
public class SearchAuthorizationIT extends AbstractLdapTestUnit
{
@Before
public void setService() throws Exception
{
AutzIntegUtils.service = getService();
}
@After
public void closeConnections()
{
IntegrationUtils.closeConnections();
}
/**
* The search results of tests are added to this map via put (<String, Entry>)
* the map is also cleared before each search test. This allows further inspections
* of the results for more specific test cases.
*/
private Map<String, Entry> results = new HashMap<String, Entry>();
/**
* 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 Exception if there are problems conducting the search
*/
private boolean checkCanSearchAs( String uid, String password ) throws Exception
{
return checkCanSearchAs( uid, password, "(objectClass=*)", SearchScope.ONELEVEL, 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 Exception if there are problems conducting the search
*/
private boolean checkCanSearchAs( String uid, String password, int resultSetSz ) throws Exception
{
return checkCanSearchAs( uid, password, "(objectClass=*)", SearchScope.ONELEVEL, 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 scope search controls
* @param resultSetSz the expected size of the results
* @return true if the search succeeds as expected, false otherwise
* @throws Exception if there are problems conducting the search
*/
private boolean checkCanSearchAs( String uid, String password, SearchScope scope, int resultSetSz )
throws Exception
{
return checkCanSearchAs( uid, password, "(objectClass=*)", scope, 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 scope search scope
* @param resultSetSz the expected size of the results
* @return true if the search succeeds as expected, false otherwise
* @throws Exception if there are problems conducting the search
*/
private boolean checkCanSearchAs( String uid, String password, String filter, SearchScope scope, int resultSetSz )
throws Exception
{
Dn base = new Dn( "ou=tests,ou=system" );
Dn userDn = new Dn( "uid=" + uid + ",ou=users,ou=system" );
results.clear();
LdapConnection userCtx = getConnectionAs( userDn, password );
EntryCursor cursor = userCtx.search( base.getName(), filter, scope, "*" );
int counter = 0;
while ( cursor.next() )
{
Entry result = cursor.get();
results.put( result.getDn().getName(), result );
counter++;
}
cursor.close();
return counter == resultSetSz;
}
/**
* 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 scope the search controls
* @param dn the rdn
* @param aci the aci
* @param resultSetSz the result sz
* @return true if the search succeeds as expected, false otherwise
* @throws Exception if there are problems conducting the search
*/
private boolean checkSearchAsWithEntryACI( String uid, String password, SearchScope scope, Dn dn, String aci,
int resultSetSz ) throws Exception
{
Dn base = new Dn( "ou=tests,ou=system" );
addEntryACI( base, aci );
Dn userDn = new Dn( "uid=" + uid + ",ou=users,ou=system" );
results.clear();
LdapConnection userCtx = getConnectionAs( userDn, password );
EntryCursor cursor = userCtx.search( base.getName(), "(objectClass=*)", scope, "*" );
int counter = 0;
while ( cursor.next() )
{
Entry result = cursor.get();
results.put( result.getDn().getName(), result );
counter++;
}
cursor.close();
removeEntryACI( base );
return counter == resultSetSz;
}
// -----------------------------------------------------------------------
// All or nothing search ACI rule tests
// -----------------------------------------------------------------------
/**
* Checks to make sure group membership based userClass works for add operations.
*
* @throws Exception if the test encounters an error
*/
@Test
public void testGrantAdministrators() throws Exception
{
// create the non-admin user
createUser( "billyd", "billyd" );
// try a search 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 search that test entry which we could not before
// add or 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 a search 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 Exception if the test encounters an error
*/
@Test
public void testGrantSearchByName() throws Exception
{
// create the non-admin user
createUser( "billyd", "billyd" );
// try a search operation which should fail without any ACI
assertFalse( checkCanSearchAs( "billyd", "billyd" ) );
// now add a subentry that enables user billyd to search 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 Exception if the test encounters an error
*/
@Test
public void testGrantSearchByNameUserDnCase() throws Exception
{
// create the non-admin user
createUser( "billyd", "billyd" );
// try a search operation which should fail without any ACI
assertFalse( checkCanSearchAs( "BillyD", "billyd" ) );
// now add a subentry that enables user billyd to search 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 Exception if the test encounters an error
*/
@Test
public void testGrantSearchBySubtree() throws Exception
{
// create the non-admin user
createUser( "billyd", "billyd" );
// try a search operation which should fail without any ACI
assertFalse( checkCanSearchAs( "billyd", "billyd" ) );
// now add a subentry that enables user billyd to search 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 Exception if the test encounters an error
*/
@Test
public void testGrantSearchAllUsers() throws Exception
{
// create the non-admin user
createUser( "billyd", "billyd" );
// try a 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 Exception if the test encounters an error
*/
@Test
public void testSelectiveGrantsAllUsers() throws Exception
{
// create the non-admin user
createUser( "billyd", "billyd" );
// try a search operation which should fail without any ACI
assertFalse( checkCanSearchAs( "billyd", "billyd", SearchScope.SUBTREE, 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
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 search that test entry which we could not before
// should work now with billyd now that all users are authorized
assertTrue( checkCanSearchAs( "billyd", "billyd", SearchScope.SUBTREE, 4 ) );
}
/**
* Checks to make sure attributeTypes are not present when permissions are
* not given for reading them and their values.
*
* @throws Exception if the test encounters an error
*/
@Test
public void testHidingAttributes() throws Exception
{
// create the non-admin user
createUser( "billyd", "billyd" );
// try a search operation which should fail without any ACI
assertFalse( checkCanSearchAs( "billyd", "billyd", SearchScope.SUBTREE, 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( "excludeTelephoneNumber", "{ 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 search and find 4 entries
assertTrue( checkCanSearchAs( "billyd", "billyd", SearchScope.SUBTREE, 4 ) );
// check to make sure the telephoneNumber attribute is not present in results
for ( Entry result : results.values() )
{
assertNull( result.get( "telephoneNumber" ) );
}
// delete the subentry to test more general rule's inclusion of telephoneNumber
deleteAccessControlSubentry( "excludeTelephoneNumber" );
// 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", SearchScope.SUBTREE, 4 ) );
// check now to make sure the telephoneNumber attribute is present in results
for ( Entry result : results.values() )
{
assertNotNull( result.get( "telephoneNumber" ) );
}
}
/**
* Checks to make sure specific attribute values are not present when
* read permission is denied.
*
* @throws Exception if the test encounters an error
*/
@Test
public void testHidingAttributeValues() throws Exception
{
// create the non-admin user
createUser( "billyd", "billyd" );
// try a search 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( "excludeOUValue", "{ 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 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 ( Entry result : results.values() )
{
assertFalse( result.get( "ou" ).contains( "testEntry" ) );
}
// delete the subentry to test more general rule's inclusion of all values
deleteAccessControlSubentry( "excludeOUValue" );
// 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 ( Entry result : results.values() )
{
assertTrue( result.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 Exception 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
Dn testsDn = new Dn( "ou=system" );
assertFalse( checkSearchAsWithEntryACI( "billyd", "billyd", SearchScope.SUBTREE, testsDn, 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", SearchScope.SUBTREE, testsDn, 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", SearchScope.SUBTREE, 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 Exception if the test is broken
*/
@Test
public void testPerscriptiveGrantWithEntryDenialWithPrecidence() throws Exception
{
// create the non-admin user
createUser( "billyd", "billyd" );
// now add an entryACI denying 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
Dn testsDn = new Dn( "ou=system" );
assertFalse( checkSearchAsWithEntryACI( "billyd", "billyd", SearchScope.SUBTREE, testsDn, 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", SearchScope.SUBTREE, testsDn, 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", SearchScope.SUBTREE, testsDn, 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 dn the relative name to the subentry under the ou=system AP
* @return the single search result if access is allowed or null
* @throws Exception if the search fails w/ exception other than no permission
*/
private Entry checkCanSearchSubentryAs( String uid, String password, Dn dn ) throws Exception
{
LdapConnection userCtx = getConnectionAs( new Dn( "uid=" + uid + ",ou=users,ou=system" ), password );
Entry result = null;
EntryCursor list = null;
list = userCtx.search( dn.getName(), "(objectClass=*)", SearchScope.OBJECT, "*" );
if ( list.next() )
{
result = list.get();
}
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( checkCanSearchSubentryAs( "billyd", "billyd", new Dn( "cn=anybodySearch,ou=system" ) ) );
// 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( checkCanSearchSubentryAs( "billyd", "billyd", new Dn( "cn=anybodySearch,ou=system" ) ) );
}
@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
LdapConnection userCtx = getConnectionAs( "uid=billyd,ou=users,ou=system", "billyd" );
// we should not see ou=groups,ou=system for the remaining name
Entry entry = userCtx.lookup( "cn=blah,ou=groups" );
assertNull( entry );
// 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
entry = userCtx.lookup( "cn=blah,ou=groups" );
assertNull( entry );
}
@Test
public void testUserClassParentOfEntry() throws Exception
{
// create the non-admin user
createUser( "billyd", "billyd" );
// create an entry subordinate to the user
Entry phoneBook = new DefaultEntry( "ou=phoneBook,uid=billyd,ou=users,ou=system" );
phoneBook.add( SchemaConstants.OU_AT, "phoneBook" );
phoneBook.add( SchemaConstants.OBJECT_CLASS_AT, "organizationalUnit" );
getAdminConnection().add( 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( checkCanSearchSubentryAs( "billyd", "billyd", new Dn(
"ou=phoneBook,uid=billyd,ou=users,ou=system" ) ) );
// 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( checkCanSearchSubentryAs( "billyd", "billyd", new Dn( "ou=phoneBook,uid=billyd,ou=users,ou=system" ) ) );
}
/**
* Checks that we can protect a RangeOfValues item
*
* @throws Exception if the test encounters an error
*/
@Test
@Ignore("The test is currently failing")
public void testRangeOfValues() throws Exception
{
// create the non-admin user
createUser( "billyd", "billyd" );
// try a search operation which should fail without any ACI
assertFalse( checkCanSearchAs( "billyd", "billyd" ) );
// now add a subentry that allows a user to read the CN only
createAccessControlSubentry( "rangeOfValues",
"{ " +
" identificationTag \"rangeOfValuesAci\", " +
" precedence 14," +
" authenticationLevel none, " +
" itemOrUserFirst userFirst: " +
" { " +
" userClasses { allUsers }, " +
" userPermissions " +
" { " +
" { " +
" protectedItems { entry }, " +
" grantsAndDenials { grantRead, grantReturnDN, grantBrowse } " +
" }, " +
" { " +
" protectedItems { rangeOfValues (cn=billyd) }, " +
" grantsAndDenials { grantRead, grantReturnDN, grantBrowse } " +
" } " +
" } " +
" } " +
"}" );
// see if we can now search and find 4 entries
assertTrue( checkCanSearchAs( "billyd", "billyd" ) );
// check to make sure the telephoneNumber attribute is not present in results
for ( Entry result : results.values() )
{
assertNotNull( result.get( "cn" ) );
}
}
@Test
public void testLdifFileLoader() throws Exception
{
URL url = getClass().getResource( "LdifFileLoader.ldif" );
URL url2 = getClass().getResource( "LdifFileLoader2.ldif" );
String file = url.getFile();
String file2 = url2.getFile();
LdifFileLoader loader = new LdifFileLoader( service.getAdminSession(), file );
int count = loader.execute();
loader = new LdifFileLoader( service.getAdminSession(), file2 );
count = loader.execute();
// Try to modify the entry with the created user
LdapConnection cnx = getConnectionAs( "uid=READER ,ou=users,ou=system", "secret" );
Entry res = cnx.lookup( "uid=READER ,ou=users,ou=system" );
assertNotNull( res );
try
{
cnx.modify( "uid=READER ,ou=users,ou=system",
new DefaultModification( ModificationOperation.ADD_ATTRIBUTE, "description", "test" ) );
fail(); // expected
}
catch ( LdapNoPermissionException lnpe )
{
assertTrue( true );
}
res = cnx.lookup( "uid=READER ,ou=users,ou=system" );
assertNotNull( res );
cnx.unBind();
}
/**
* Checks to make sure <b>allUsers</b> userClass works for search operations.
*
* @throws Exception if the test encounters an error
*/
@Test
public void testDenySearchUserPassword() throws Exception
{
// create the non-admin user
createUser( "billyd", "billyd" );
// try a search operation which should fail without any ACI
LdapConnection userCtx = getConnectionAs( "uid=billyd,ou=users,ou=system", "billyd" );
EntryCursor cursor = userCtx.search( "ou=users,ou=system", "(ObjectClass=*)", SearchScope.SUBTREE,
"userPassword" );
int counter = 0;
while ( cursor.next() )
{
Entry result = cursor.get();
results.put( result.getDn().getName(), result );
counter++;
}
cursor.close();
assertEquals( 0, counter );
// now add a subentry that enables anyone to search an entry below ou=system
createAccessControlSubentry( "protectUserPassword",
"{" +
" identificationTag \"protectUserPassword\"," +
" precedence 14," +
" authenticationLevel none," +
" itemOrUserFirst itemFirst: " +
" {" +
" protectedItems " +
" {" +
" allAttributeValues { userPassword }" +
" }," +
" itemPermissions " +
" {" +
" {" +
" userClasses " +
" {" +
" allUsers " +
" }," +
" grantsAndDenials { denyBrowse }" +
" }," +
" {" +
" userClasses " +
" {" +
" thisEntry " +
" }," +
" grantsAndDenials { 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
userCtx = getConnectionAs( "uid=billyd,ou=users,ou=system", "billyd" );
cursor = userCtx.search( "ou=users,ou=system", "(ObjectClass=*)", SearchScope.SUBTREE,
"userPassword" );
counter = 0;
while ( cursor.next() )
{
Entry result = cursor.get();
results.put( result.getDn().getName(), result );
counter++;
}
cursor.close();
}
}