From commits-return-6532-apmail-directory-commits-archive=directory.apache.org@directory.apache.org Thu Oct 20 05:10:17 2005 Return-Path: Delivered-To: apmail-directory-commits-archive@www.apache.org Received: (qmail 44126 invoked from network); 20 Oct 2005 05:10:17 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 20 Oct 2005 05:10:17 -0000 Received: (qmail 41998 invoked by uid 500); 20 Oct 2005 05:10:17 -0000 Delivered-To: apmail-directory-commits-archive@directory.apache.org Received: (qmail 41946 invoked by uid 500); 20 Oct 2005 05:10:16 -0000 Mailing-List: contact commits-help@directory.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@directory.apache.org Delivered-To: mailing list commits@directory.apache.org Received: (qmail 41935 invoked by uid 99); 20 Oct 2005 05:10:16 -0000 Received: from asf.osuosl.org (HELO asf.osuosl.org) (140.211.166.49) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 19 Oct 2005 22:10:16 -0700 X-ASF-Spam-Status: No, hits=-9.4 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received: from [209.237.227.194] (HELO minotaur.apache.org) (209.237.227.194) by apache.org (qpsmtpd/0.29) with SMTP; Wed, 19 Oct 2005 22:10:15 -0700 Received: (qmail 44022 invoked by uid 65534); 20 Oct 2005 05:09:54 -0000 Message-ID: <20051020050954.44020.qmail@minotaur.apache.org> Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r326825 - in /directory/apacheds/trunk/core/src: main/java/org/apache/ldap/server/authz/ main/java/org/apache/ldap/server/exception/ main/java/org/apache/ldap/server/partition/ test/org/apache/ldap/server/authz/ Date: Thu, 20 Oct 2005 05:09:52 -0000 To: commits@directory.apache.org From: akarasulu@apache.org X-Mailer: svnmailer-1.0.5 X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N Author: akarasulu Date: Wed Oct 19 22:09:43 2005 New Revision: 326825 URL: http://svn.apache.org/viewcvs?rev=326825&view=rev Log: changes ... o added tests for permission to disclose on error o added functionality (guards) to getMatchedName() for disclose on error o created constant unmodifiable collections for all permission collections used in Authz service o made some code in exception service use proxy to getMatchedName() o added new constant for lookups in getMatchedName() so normalization occurs Modified: directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/authz/AuthorizationService.java directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/exception/ExceptionService.java directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/partition/DirectoryPartitionNexusProxy.java directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/SearchAuthorizationTest.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=326825&r1=326824&r2=326825&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 Wed Oct 19 22:09:43 2005 @@ -74,8 +74,57 @@ */ private static final String AC_SUBENTRY_ATTR = "accessControlSubentries"; - /** the partition nexus */ - private DirectoryPartitionNexus nexus; + private static final Collection ADD_PERMS; + private static final Collection READ_PERMS; + private static final Collection COMPARE_PERMS; + private static final Collection SEARCH_ENTRY_PERMS; + private static final Collection SEARCH_ATTRVAL_PERMS; + private static final Collection REMOVE_PERMS; + private static final Collection MATCHEDNAME_PERMS; + private static final Collection BROWSE_PERMS; + private static final Collection LOOKUP_PERMS; + private static final Collection REPLACE_PERMS; + private static final Collection RENAME_PERMS; + private static final Collection EXPORT_PERMS; + private static final Collection IMPORT_PERMS; + private static final Collection MOVERENAME_PERMS; + + + static + { + HashSet set = new HashSet( 2 ); + set.add( MicroOperation.BROWSE ); + set.add( MicroOperation.RETURN_DN ); + SEARCH_ENTRY_PERMS = Collections.unmodifiableCollection( set ); + + set = new HashSet( 2 ); + set.add( MicroOperation.READ ); + set.add( MicroOperation.BROWSE ); + LOOKUP_PERMS = Collections.unmodifiableCollection( set ); + + set = new HashSet( 2 ); + set.add( MicroOperation.ADD ); + set.add( MicroOperation.REMOVE ); + REPLACE_PERMS = Collections.unmodifiableCollection( set ); + + set = new HashSet( 3 ); + set.add( MicroOperation.IMPORT ); + set.add( MicroOperation.EXPORT ); + set.add( MicroOperation.RENAME ); + MOVERENAME_PERMS = Collections.unmodifiableCollection( set ); + + SEARCH_ATTRVAL_PERMS = Collections.singleton( MicroOperation.READ ); + ADD_PERMS = Collections.singleton( MicroOperation.ADD ); + READ_PERMS = Collections.singleton( MicroOperation.READ ); + COMPARE_PERMS = Collections.singleton( MicroOperation.COMPARE ); + REMOVE_PERMS = Collections.singleton( MicroOperation.REMOVE ); + MATCHEDNAME_PERMS = Collections.singleton( MicroOperation.DISCLOSE_ON_ERROR ); + BROWSE_PERMS = Collections.singleton( MicroOperation.BROWSE ); + RENAME_PERMS = Collections.singleton( MicroOperation.RENAME ); + EXPORT_PERMS = Collections.singleton( MicroOperation.EXPORT ); + IMPORT_PERMS = Collections.singleton( MicroOperation.IMPORT ); + } + /** a tupleCache that responds to add, delete, and modify attempts */ private TupleCache tupleCache; /** a groupCache that responds to add, delete, and modify attempts */ @@ -103,7 +152,6 @@ public void init( DirectoryServiceConfiguration factoryCfg, InterceptorConfiguration cfg ) throws NamingException { super.init( factoryCfg, cfg ); - nexus = factoryCfg.getPartitionNexus(); tupleCache = new TupleCache( factoryCfg ); groupCache = new GroupCache( factoryCfg ); attrRegistry = factoryCfg.getGlobalRegistries().getAttributeTypeRegistry(); @@ -300,12 +348,11 @@ // NOTE: entryACI are NOT considered in adds (it would be a security breech) addPerscriptiveAciTuples( invocation.getProxy(), tuples, normName, subentryAttrs ); addSubentryAciTuples( invocation.getProxy(), tuples, normName, subentryAttrs ); - Collection perms = Collections.singleton( MicroOperation.ADD ); // check if entry scope permission is granted DirectoryPartitionNexusProxy proxy = invocation.getProxy(); engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), - normName, null, null, perms, tuples, subentryAttrs ); + normName, null, null, ADD_PERMS, tuples, subentryAttrs ); // now we must check if attribute type and value scope permission is granted NamingEnumeration attributeList = entry.getAll(); @@ -316,7 +363,7 @@ { engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), normName, attr.getID(), - attr.get( ii ), perms, tuples, entry ); + attr.get( ii ), ADD_PERMS, tuples, entry ); } } @@ -352,7 +399,7 @@ addSubentryAciTuples( proxy, tuples, name, entry ); engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, null, - null, Collections.singleton( MicroOperation.REMOVE ), tuples, entry ); + null, REMOVE_PERMS, tuples, entry ); next.delete( name ); tupleCache.subentryDeleted( name, entry ); @@ -389,15 +436,13 @@ switch( modOp ) { case( DirContext.ADD_ATTRIBUTE ): - perms = Collections.singleton( MicroOperation.ADD ); + perms = ADD_PERMS; break; case( DirContext.REMOVE_ATTRIBUTE ): - perms = Collections.singleton( MicroOperation.REMOVE ); + perms = REMOVE_PERMS; break; case( DirContext.REPLACE_ATTRIBUTE ): - perms = new HashSet(); - perms.add( MicroOperation.ADD ); - perms.add( MicroOperation.REMOVE ); + perms = REPLACE_PERMS; break; } @@ -442,24 +487,18 @@ null, Collections.singleton( MicroOperation.MODIFY ), tuples, entry ); Collection perms = null; - Collection remove = Collections.singleton( MicroOperation.REMOVE ); - Collection add = Collections.singleton( MicroOperation.ADD ); - Collection replace = new HashSet(); - replace.add( MicroOperation.ADD ); - replace.add( MicroOperation.REMOVE ); - for ( int ii = 0; ii < mods.length; ii++ ) { switch( mods[ii].getModificationOp() ) { case( DirContext.ADD_ATTRIBUTE ): - perms = add; + perms = ADD_PERMS; break; case( DirContext.REMOVE_ATTRIBUTE ): - perms = remove; + perms = REMOVE_PERMS; break; case( DirContext.REPLACE_ATTRIBUTE ): - perms = replace; + perms = REPLACE_PERMS; break; } @@ -497,7 +536,7 @@ // check that we have browse access to the entry engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, null, - null, Collections.singleton( MicroOperation.BROWSE ), tuples, entry ); + null, BROWSE_PERMS, tuples, entry ); return next.hasEntry( name ); } @@ -528,16 +567,11 @@ addEntryAciTuples( tuples, entry ); addSubentryAciTuples( proxy, tuples, dn, entry ); - Collection perms = new HashSet(); - perms.add( MicroOperation.READ ); - perms.add( MicroOperation.BROWSE ); - // check that we have read access to the entry engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), dn, null, - null, perms, tuples, entry ); + null, LOOKUP_PERMS, tuples, entry ); // check that we have read access to every attribute type and value - perms = Collections.singleton( MicroOperation.READ ); NamingEnumeration attributeList = entry.getAll(); while ( attributeList.hasMore() ) { @@ -545,7 +579,7 @@ for ( int ii = 0; ii < attr.size(); ii++ ) { engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), dn, - attr.getID(), attr.get( ii ), perms, tuples, entry ); + attr.getID(), attr.get( ii ), READ_PERMS, tuples, entry ); } } } @@ -612,7 +646,7 @@ addSubentryAciTuples( proxy, tuples, name, entry ); engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, null, - null, Collections.singleton( MicroOperation.RENAME ), tuples, entry ); + null, RENAME_PERMS, tuples, entry ); // if ( deleteOldRn ) // { @@ -671,19 +705,15 @@ addEntryAciTuples( tuples, entry ); addSubentryAciTuples( proxy, tuples, oriChildName, entry ); - Collection perms = new HashSet(); - perms.add( MicroOperation.IMPORT ); - perms.add( MicroOperation.EXPORT ); - perms.add( MicroOperation.RENAME ); engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), - oriChildName, null, null, perms, tuples, entry ); + oriChildName, null, null, MOVERENAME_PERMS, tuples, entry ); Collection destTuples = new HashSet(); addPerscriptiveAciTuples( proxy, destTuples, oriChildName, entry ); addEntryAciTuples( destTuples, entry ); addSubentryAciTuples( proxy, destTuples, oriChildName, entry ); engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), - oriChildName, null, null, Collections.singleton( MicroOperation.IMPORT ), tuples, entry ); + oriChildName, null, null, IMPORT_PERMS, tuples, entry ); // if ( deleteOldRn ) // { @@ -742,14 +772,14 @@ addSubentryAciTuples( proxy, tuples, oriChildName, entry ); engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), - oriChildName, null, null, Collections.singleton( MicroOperation.EXPORT ), tuples, entry ); + oriChildName, null, null, EXPORT_PERMS, tuples, entry ); Collection destTuples = new HashSet(); addPerscriptiveAciTuples( proxy, destTuples, oriChildName, entry ); addEntryAciTuples( destTuples, entry ); addSubentryAciTuples( proxy, destTuples, oriChildName, entry ); engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), - oriChildName, null, null, Collections.singleton( MicroOperation.IMPORT ), tuples, entry ); + oriChildName, null, null, IMPORT_PERMS, tuples, entry ); next.move( oriChildName, newParentName ); tupleCache.subentryRenamed( oriChildName, newName ); @@ -792,7 +822,6 @@ public boolean compare( NextInterceptor next, Name name, String oid, Object value ) throws NamingException { - // Access the principal requesting the operation, and bypass checks if it is the admin Invocation invocation = InvocationStack.getInstance().peek(); DirectoryPartitionNexusProxy proxy = invocation.getProxy(); @@ -810,31 +839,68 @@ addSubentryAciTuples( proxy, tuples, name, entry ); engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, null, - null, Collections.singleton( MicroOperation.READ ), tuples, entry ); + null, READ_PERMS, tuples, entry ); engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, oid, - value, Collections.singleton( MicroOperation.COMPARE ), tuples, entry ); + value, COMPARE_PERMS, tuples, entry ); return next.compare( name, oid, value ); } - public void cacheNewGroup( String upName, Name normName, Attributes entry ) throws NamingException + public Name getMatchedName( NextInterceptor next, Name dn, boolean normalized ) throws NamingException { - this.groupCache.groupAdded( upName, normName, entry ); + // Access the principal requesting the operation, and bypass checks if it is the admin + Invocation invocation = InvocationStack.getInstance().peek(); + DirectoryPartitionNexusProxy proxy = invocation.getProxy(); + LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal(); + if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled ) + { + return next.getMatchedName( dn, normalized ); + } + + // get the present matched name + Attributes entry; + Name matched = next.getMatchedName( dn, normalized ); + + // check if we have disclose on error permission for the entry at the matched dn + // if not remove rdn and check that until nothing is left in the name and return + // that but if permission is granted then short the process and return the dn + while ( matched.size() > 0 ) + { + if ( normalized ) + { + entry = proxy.lookup( matched, DirectoryPartitionNexusProxy.GETMATCHEDDN_BYPASS ); + } + else + { + entry = proxy.lookup( matched, DirectoryPartitionNexusProxy.LOOKUP_BYPASS ); + } + + Set userGroups = groupCache.getGroups( user.getName() ); + Collection tuples = new HashSet(); + addPerscriptiveAciTuples( proxy, tuples, matched, entry ); + addEntryAciTuples( tuples, entry ); + addSubentryAciTuples( proxy, tuples, matched, entry ); + + if ( engine.hasPermission( proxy, userGroups, user.getJndiName(), + user.getAuthenticationLevel(), matched, null, null, + MATCHEDNAME_PERMS, tuples, entry ) ) + { + return matched; + } + + matched.remove( matched.size() - 1 ); + } + + return matched; } - /** @todo move this up and add more collections that can be made constants */ - private static final Collection SEARCH_ENTRY_PERMS; - private static final Collection SEARCH_ATTRVAL_PERMS; - static + public void cacheNewGroup( String upName, Name normName, Attributes entry ) throws NamingException { - HashSet set = new HashSet( 2 ); - set.add( MicroOperation.BROWSE ); - set.add( MicroOperation.RETURN_DN ); - SEARCH_ENTRY_PERMS = Collections.unmodifiableCollection( set ); - SEARCH_ATTRVAL_PERMS = Collections.singleton( MicroOperation.READ ); + this.groupCache.groupAdded( upName, normName, entry ); } + private boolean filter( Invocation invocation, Name normName, SearchResult result ) throws NamingException { Modified: directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/exception/ExceptionService.java URL: http://svn.apache.org/viewcvs/directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/exception/ExceptionService.java?rev=326825&r1=326824&r2=326825&view=diff ============================================================================== --- directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/exception/ExceptionService.java (original) +++ directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/exception/ExceptionService.java Wed Oct 19 22:09:43 2005 @@ -29,11 +29,14 @@ import org.apache.ldap.common.message.ResultCodeEnum; import org.apache.ldap.common.name.LdapName; import org.apache.ldap.server.DirectoryServiceConfiguration; +import org.apache.ldap.server.invocation.Invocation; +import org.apache.ldap.server.invocation.InvocationStack; import org.apache.ldap.server.configuration.InterceptorConfiguration; import org.apache.ldap.server.interceptor.BaseInterceptor; import org.apache.ldap.server.interceptor.NextInterceptor; import org.apache.ldap.server.partition.DirectoryPartition; import org.apache.ldap.server.partition.DirectoryPartitionNexus; +import org.apache.ldap.server.partition.DirectoryPartitionNexusProxy; /** @@ -302,8 +305,8 @@ * LdapException. */ public void move( NextInterceptor nextInterceptor, - Name oriChildName, Name newParentName, String newRn, - boolean deleteOldRn ) throws NamingException + Name oriChildName, Name newParentName, String newRn, + boolean deleteOldRn ) throws NamingException { // check if child to move exists String msg = "Attempt to move to non-existant parent: "; @@ -332,9 +335,9 @@ /** * Checks to see the entry being searched exists, otherwise throws the appropriate LdapException. */ - public NamingEnumeration search( NextInterceptor nextInterceptor, - Name base, Map env, ExprNode filter, - SearchControls searchCtls ) throws NamingException + public NamingEnumeration search( NextInterceptor nextInterceptor, + Name base, Map env, ExprNode filter, + SearchControls searchCtls ) throws NamingException { String msg = "Attempt to search under non-existant entry: "; @@ -365,6 +368,8 @@ */ private void assertHasEntry( NextInterceptor nextInterceptor, String msg, Name dn ) throws NamingException { + Invocation invocation = InvocationStack.getInstance().peek(); + DirectoryPartitionNexusProxy proxy = invocation.getProxy(); if ( !nextInterceptor.hasEntry( dn ) ) { LdapNameNotFoundException e = null; @@ -378,7 +383,7 @@ e = new LdapNameNotFoundException( dn.toString() ); } - e.setResolvedName( nextInterceptor.getMatchedName( dn, false ) ); + e.setResolvedName( proxy.getMatchedName( dn, false ) ); throw e; } } Modified: directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/partition/DirectoryPartitionNexusProxy.java URL: http://svn.apache.org/viewcvs/directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/partition/DirectoryPartitionNexusProxy.java?rev=326825&r1=326824&r2=326825&view=diff ============================================================================== --- directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/partition/DirectoryPartitionNexusProxy.java (original) +++ directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/partition/DirectoryPartitionNexusProxy.java Wed Oct 19 22:09:43 2005 @@ -56,6 +56,8 @@ { /** safe to use set of bypass instructions to lookup raw entries */ public static final Collection LOOKUP_BYPASS; + /** safe to use set of bypass instructions to getMatchedDn */ + public static final Collection GETMATCHEDDN_BYPASS; /** Bypass String to use when ALL interceptors should be skipped */ public static final String BYPASS_ALL = "*"; /** Bypass String to use when ALL interceptors should be skipped */ @@ -84,6 +86,16 @@ c.add( "operationalAttributeService" ); c.add( "eventService" ); LOOKUP_BYPASS = Collections.unmodifiableCollection( c ); + + c = new HashSet(); + c.add( "authenticationService" ); + c.add( "authorizationService" ); + c.add( "oldAuthorizationService" ); + c.add( "schemaService" ); + c.add( "subentryService" ); + c.add( "operationalAttributeService" ); + c.add( "eventService" ); + GETMATCHEDDN_BYPASS = Collections.unmodifiableCollection( c ); } Modified: directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/SearchAuthorizationTest.java URL: http://svn.apache.org/viewcvs/directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/SearchAuthorizationTest.java?rev=326825&r1=326824&r2=326825&view=diff ============================================================================== --- directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/SearchAuthorizationTest.java (original) +++ directory/apacheds/trunk/core/src/test/org/apache/ldap/server/authz/SearchAuthorizationTest.java Wed Oct 19 22:09:43 2005 @@ -822,4 +822,65 @@ // now we should not be able to access the subentry with a search assertNull( checkCanSearhSubentryAs( "billyd", "billyd", new LdapName( "cn=anybodySearch" ) ) ); } + + + public void testGetMatchedName() throws NamingException + { + // 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 LdapName( "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" ); + } + } }