/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2015 ForgeRock AS */ package org.opends.server.core; import java.io.IOException; import java.net.Socket; import java.util.*; import org.assertj.core.api.SoftAssertions; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.DecodeException; import org.forgerock.opendj.ldap.DereferenceAliasesPolicy; import org.forgerock.opendj.ldap.ResultCode; import org.forgerock.opendj.ldap.SearchScope; import org.opends.server.TestCaseUtils; import org.opends.server.controls.MatchedValuesControl; import org.opends.server.controls.MatchedValuesFilter; import org.opends.server.controls.SubentriesControl; import org.opends.server.plugins.InvocationCounterPlugin; import org.opends.server.protocols.internal.InternalSearchOperation; import org.opends.server.protocols.internal.Requests; import org.opends.server.protocols.internal.SearchRequest; import org.opends.server.protocols.ldap.*; import org.opends.server.tools.LDAPModify; import org.opends.server.tools.LDAPWriter; import org.opends.server.types.*; import org.opends.server.util.StaticUtils; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static org.assertj.core.api.Assertions.*; import static org.opends.server.protocols.internal.InternalClientConnection.*; import static org.opends.server.protocols.internal.Requests.*; import static org.opends.server.util.CollectionUtils.*; import static org.opends.server.util.ServerConstants.*; import static org.testng.Assert.*; @SuppressWarnings("javadoc") public class SearchOperationTestCase extends OperationTestCase { private static final String SUFFIX = "dc=example,dc=com"; private static final String BASE = "o=Test Core Search,dc=example,dc=com"; private Entry testEntry; private int ldapAttrCount; @BeforeClass public void setUp() throws Exception { TestCaseUtils.startServer(); TestCaseUtils.clearBackend("userRoot","dc=example,dc=com"); DN suffixDN = DN.valueOf(SUFFIX); if (!DirectoryServer.entryExists(suffixDN)) { processAdd(StaticUtils.createEntry(suffixDN)); } DN baseDN = DN.valueOf(BASE); if (!DirectoryServer.entryExists(baseDN)) { processAdd(StaticUtils.createEntry(baseDN)); } TestCaseUtils.addEntry( "dn: cn=subentry," + BASE, "objectclass: ldapsubentry"); // Add a test entry. testEntry = TestCaseUtils.makeEntry( "dn: uid=rogasawara," + BASE, "userpassword: password", "objectclass: top", "objectclass: person", "objectclass: organizationalPerson", "objectclass: inetOrgPerson", "uid: rogasawara", "mail: rogasawara@airius.co.jp", "givenname;lang-ja:: 44Ot44OJ44OL44O8", "sn;lang-ja:: 5bCP56yg5Y6f", "cn;lang-ja:: 5bCP56yg5Y6fIOODreODieODi+ODvA==", "title;lang-ja:: 5Za25qWt6YOoIOmDqOmVtw==", "preferredlanguage: ja", "givenname:: 44Ot44OJ44OL44O8", "sn:: 5bCP56yg5Y6f", "cn:: 5bCP56yg5Y6fIOODreODieODi+ODvA==", "title:: 5Za25qWt6YOoIOmDqOmVtw==", "givenname;lang-ja;phonetic:: 44KN44Gp44Gr44O8", "sn;lang-ja;phonetic:: 44GK44GM44GV44KP44KJ", "cn;lang-ja;phonetic:: 44GK44GM44GV44KP44KJIOOCjeOBqeOBq+ODvA==", "title;lang-ja;phonetic:: " + "44GI44GE44GO44KH44GG44G2IOOBtuOBoeOCh+OBhg==", "givenname;lang-en: Rodney", "sn;lang-en: Ogasawara", "cn;lang-en: Rodney Ogasawara", "title;lang-en: Sales, Director" ); // Calculate the total number of LDAP attributes in this entry. ldapAttrCount = 1; // For the objectclass attribute. for (Attribute a : testEntry.getAttributes()) { ldapAttrCount += a.size(); } // The add operation changes the attributes, so let's duplicate the entry. processAdd(testEntry.duplicate(false)); // referral entry. TestCaseUtils.addEntry( "dn: ou=People," + BASE, "objectclass: extensibleobject", "objectclass: referral", "ref: ldap://hostb/OU=People,O=MNN,C=US", "ref: ldap://hostc/OU=People,O=MNN,C=US"); // level 1 entry. TestCaseUtils.addEntry( "dn: ou=level1," + BASE, "objectclass: top", "objectclass: organizationalunit", "ou: level1"); // level 2 entry. TestCaseUtils.addEntry( "dn: ou=level2,ou=level1," + BASE, "objectclass: top", "objectclass: organizationalunit", "ou: level2"); // referral 2 entry. TestCaseUtils.addEntry( "dn: ou=level3,ou=level2,ou=level1," + BASE, "objectclass: extensibleobject", "objectclass: referral", "ref: ldap://hostb/OU=People,O=MNN,C=US", "ref: ldap://hostc/OU=People,O=MNN,C=US", "ref: ldap://hostd/OU=People,O=MNN,C=US"); } private void processAdd(Entry e) throws DirectoryException { AddOperation addOperation = getRootConnection().processAdd(e); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); assertNotNull(DirectoryServer.getEntry(e.getName())); } @Override protected Operation[] createTestOperations() throws Exception { return new Operation[0]; } /** * Invokes a number of operation methods on the provided search operation * for which all processing has been completed. * * @param searchOperation The operation to be tested. */ private void examineCompletedOperation(SearchOperation searchOperation) { assertTrue(searchOperation.getProcessingStartTime() > 0); assertTrue(searchOperation.getProcessingStopTime() > 0); assertTrue(searchOperation.getProcessingTime() >= 0); ensurePostReponseHasRun(); } private Entry getSingleEntry(InternalSearchOperation searchOperation) { assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS); assertEquals(searchOperation.getEntriesSent(), 1); assertEquals(searchOperation.getErrorMessage().length(), 0); examineCompletedOperation(searchOperation); return searchOperation.getSearchEntries().getFirst(); } private SearchResultEntryProtocolOp searchExternalForSingleEntry( SearchRequestProtocolOp searchRequest, ArrayList<Control> controls) throws Exception { // Establish a connection to the server. try (Socket s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort())) { org.opends.server.tools.LDAPReader r = new org.opends.server.tools.LDAPReader(s); LDAPWriter w = new LDAPWriter(s); TestCaseUtils.configureSocket(s); bindAsManager(w, r); // Since we are going to be watching the post-response count, we need to // wait for the server to become idle before kicking off the next request // to ensure that any remaining post-response processing from the previous // operation has completed. assertTrue(DirectoryServer.getWorkQueue().waitUntilIdle(10000)); InvocationCounterPlugin.resetAllCounters(); long searchRequests = ldapStatistics.getSearchRequests(); long searchEntries = ldapStatistics.getSearchResultEntries(); long searchReferences = ldapStatistics.getSearchResultReferences(); long searchesDone = ldapStatistics.getSearchResultsDone(); LDAPMessage message = new LDAPMessage(2, searchRequest, controls); w.writeMessage(message); SearchResultEntryProtocolOp searchResultEntry = null; SearchResultDoneProtocolOp searchResultDone = null; while (searchResultDone == null && (message = r.readMessage()) != null) { switch (message.getProtocolOpType()) { case LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY: searchResultEntry = message.getSearchResultEntryProtocolOp(); searchEntries++; break; case LDAPConstants.OP_TYPE_SEARCH_RESULT_REFERENCE: searchReferences++; break; case LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE: searchResultDone = message.getSearchResultDoneProtocolOp(); assertThat(searchResultDone.getResultCode()).isEqualTo(LDAPResultCode.SUCCESS); // assertEquals(InvocationCounterPlugin.waitForPostResponse(), 1); searchesDone++; break; } } TestCaseUtils.quiesceServer(); assertEquals(ldapStatistics.getSearchRequests(), searchRequests+1); assertEquals(ldapStatistics.getSearchResultEntries(), searchEntries); assertEquals(ldapStatistics.getSearchResultReferences(), searchReferences); assertEquals(ldapStatistics.getSearchResultsDone(), searchesDone); return searchResultEntry; } } private void bindAsManager(LDAPWriter w, org.opends.server.tools.LDAPReader r) throws IOException, LDAPException, DecodeException, InterruptedException { // Since we are going to be watching the post-response count, we need to // wait for the server to become idle before kicking off the next request to // ensure that any remaining post-response processing from the previous // operation has completed. assertTrue(DirectoryServer.getWorkQueue().waitUntilIdle(10000)); InvocationCounterPlugin.resetAllCounters(); BindRequestProtocolOp bindRequest = new BindRequestProtocolOp( ByteString.valueOfUtf8("cn=Directory Manager"), 3, ByteString.valueOfUtf8("password")); LDAPMessage message = new LDAPMessage(1, bindRequest); w.writeMessage(message); message = r.readMessage(); BindResponseProtocolOp bindResponse = message.getBindResponseProtocolOp(); assertEquals(bindResponse.getResultCode(), LDAPResultCode.SUCCESS); } @Test public void testSearchInternal() throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request = newSearchRequest(DN.valueOf(BASE), SearchScope.WHOLE_SUBTREE); InternalSearchOperation searchOperation = getRootConnection().processSearch(request); assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS); assertEquals(searchOperation.getEntriesSent(), 4); assertEquals(searchOperation.getReferencesSent(), 2); assertEquals(searchOperation.getErrorMessage().length(), 0); examineCompletedOperation(searchOperation); } @Test public void testSearchNonExistingDN_baseObject() throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request = Requests.newSearchRequest("uid=doesNotExist," + BASE, SearchScope.BASE_OBJECT, "(objectclass=*)"); InternalSearchOperation searchOp = getRootConnection().processSearch(request); assertThat(searchOp.getResultCode()).isEqualTo(ResultCode.NO_SUCH_OBJECT); } @Test public void testSearchExistingDN_baseObject() throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request = Requests.newSearchRequest(BASE, SearchScope.BASE_OBJECT, "(objectclass=*)"); InternalSearchOperation searchOp = getRootConnection().processSearch(request); assertThat(searchOp.getResultCode()).isEqualTo(ResultCode.SUCCESS); assertThat(getEntryNames(searchOp)).containsExactly(BASE); } @Test public void testSearchNonExistingDN_singleLevel() throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request = Requests.newSearchRequest("uid=doesNotExist," + BASE, SearchScope.SINGLE_LEVEL, "(objectclass=*)"); InternalSearchOperation searchOp = getRootConnection().processSearch(request); assertThat(searchOp.getResultCode()).isEqualTo(ResultCode.NO_SUCH_OBJECT); } @Test public void testSearchExistingDN_singleLevel() throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request = Requests.newSearchRequest(BASE, SearchScope.SINGLE_LEVEL, "(objectclass=*)"); InternalSearchOperation searchOp = getRootConnection().processSearch(request); assertThat(searchOp.getResultCode()).isEqualTo(ResultCode.SUCCESS); assertThat(getEntryNames(searchOp)).containsExactly( "uid=rogasawara," + BASE, "ou=level1," + BASE); } @Test public void testSearchNonExisting_subordinates() throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request = Requests.newSearchRequest("uid=doesNotExist," + BASE, SearchScope.SUBORDINATES, "(objectclass=*)"); InternalSearchOperation searchOp = getRootConnection().processSearch(request); assertThat(searchOp.getResultCode()).isEqualTo(ResultCode.NO_SUCH_OBJECT); } @Test public void testSearchExistingDN_subordinates() throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request = Requests.newSearchRequest(BASE, SearchScope.SUBORDINATES, "(objectclass=*)"); InternalSearchOperation searchOp = getRootConnection().processSearch(request); assertThat(searchOp.getResultCode()).isEqualTo(ResultCode.SUCCESS); assertThat(getEntryNames(searchOp)).containsExactly( "uid=rogasawara," + BASE, "ou=level1," + BASE, "ou=level2,ou=level1," + BASE); } @Test public void testSearchNonExisting_wholeSubtree() throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request = Requests.newSearchRequest("uid=doesNotExist," + BASE, SearchScope.WHOLE_SUBTREE, "(objectclass=*)"); InternalSearchOperation searchOp = getRootConnection().processSearch(request); assertThat(searchOp.getResultCode()).isEqualTo(ResultCode.NO_SUCH_OBJECT); } @Test public void testSearchExistingDN_wholeSubtree() throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request = Requests.newSearchRequest(BASE, SearchScope.WHOLE_SUBTREE, "(objectclass=*)"); InternalSearchOperation searchOp = getRootConnection().processSearch(request); assertThat(searchOp.getResultCode()).isEqualTo(ResultCode.SUCCESS); assertThat(getEntryNames(searchOp)).containsExactly( BASE, "uid=rogasawara," + BASE, "ou=level1," + BASE, "ou=level2,ou=level1," + BASE); } private List<String> getEntryNames(InternalSearchOperation searchOp) { List<SearchResultEntry> entries = searchOp.getSearchEntries(); List<String> results = new ArrayList<>(entries.size()); for (SearchResultEntry entry : entries) { results.add(entry.getName().toString()); } return results; } @Test public void testSearchInternalUnspecifiedAttributes() throws Exception { InternalSearchOperation searchOperation = newInternalSearchOperation("(objectclass=inetorgperson)"); Entry resultEntry = getSingleEntry(searchOperation); assertObjectClassesAndUserAttributes( resultEntry, testEntry.getObjectClasses(), testEntry.getUserAttributes().size() + 1); assertThat(resultEntry.getOperationalAttributes()).isEmpty(); } @Test public void testSearchInternalUnspecifiedAttributesOmitValues() throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request = Requests.newSearchRequest(BASE, SearchScope.WHOLE_SUBTREE, "(objectclass=inetorgperson)") .setTypesOnly(true); Entry resultEntry = getSingleEntry(getRootConnection().processSearch(request)); assertEntrySizes(resultEntry, 0, testEntry.getUserAttributes().size() + 1, 0); } @Test public void testSearchInternalAllOperationalAttributes() throws Exception { InternalSearchOperation searchOperation = newInternalSearchOperation("(objectclass=inetorgperson)", "+"); Entry resultEntry = getSingleEntry(searchOperation); assertThat(resultEntry.getObjectClasses()).isEmpty(); assertThat(resultEntry.getUserAttributes()).isEmpty(); assertThat(resultEntry.getOperationalAttributes()).isNotEmpty(); } @Test public void testSearchInternalAllUserAndOperationalAttributes() throws Exception { InternalSearchOperation searchOperation = newInternalSearchOperation("(objectclass=inetorgperson)", "*", "+"); Entry resultEntry = getSingleEntry(searchOperation); assertObjectClassesAndUserAttributes( resultEntry, testEntry.getObjectClasses(), testEntry.getUserAttributes().size() + 1); assertThat(resultEntry.getOperationalAttributes()).isNotEmpty(); } @Test public void testSearchInternalAllUserAttributesPlusSelectedOperational() throws Exception { InternalSearchOperation searchOperation = newInternalSearchOperation("(objectclass=inetorgperson)", "*", "createtimestamp"); Entry resultEntry = getSingleEntry(searchOperation); assertObjectClassesAndUserAttributes( resultEntry, testEntry.getObjectClasses(), testEntry.getUserAttributes().size() + 1); assertThat(resultEntry.getOperationalAttributes()).hasSize(1); } private void assertObjectClassesAndUserAttributes(Entry resultEntry, Map<ObjectClass, String> objectClasses, int nbUserAttrs) { SoftAssertions softly = new SoftAssertions(); softly.assertThat(resultEntry.getObjectClasses()).as("Object classes").isEqualTo(objectClasses); softly.assertThat(resultEntry.getUserAttributes()).as("User attributes").hasSize(nbUserAttrs); softly.assertAll(); } @Test public void testSearchInternalSelectedAttributes() throws Exception { InternalSearchOperation searchOperation = newInternalSearchOperation("(objectclass=inetorgperson)", "uid", "createtimestamp"); Entry resultEntry = getSingleEntry(searchOperation); assertEntrySizes(resultEntry, 0, 1, 1); } private void assertEntrySizes(Entry resultEntry, int nbOCs, int nbUserAttrs, int nbOperationalAttrs) { SoftAssertions softly = new SoftAssertions(); softly.assertThat(resultEntry.getObjectClasses()).as("Object classes").hasSize(nbOCs); softly.assertThat(resultEntry.getUserAttributes()).as("User attributes").hasSize(nbUserAttrs); softly.assertThat(resultEntry.getOperationalAttributes()).as("Operational attributes").hasSize(nbOperationalAttrs); softly.assertAll(); } private InternalSearchOperation newInternalSearchOperation(String filter, String... attributes) throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request = newSearchRequest(BASE, SearchScope.WHOLE_SUBTREE, filter) .addAttribute(attributes); return getRootConnection().processSearch(request); } @Test public void testSearchExternalUnspecifiedAttributes() throws Exception { SearchRequestProtocolOp searchRequest = new SearchRequestProtocolOp( ByteString.valueOfUtf8(BASE), SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.NEVER, Integer.MAX_VALUE, Integer.MAX_VALUE, false, LDAPFilter.decode("(objectclass=inetorgperson)"), null); SearchResultEntryProtocolOp searchResultEntry = searchExternalForSingleEntry(searchRequest, null); assertThat(searchResultEntry.getAttributes()).hasSize(ldapAttrCount); } @Test public void testSearchExternalAllUserAttributes() throws Exception { SearchRequestProtocolOp searchRequest = new SearchRequestProtocolOp( ByteString.valueOfUtf8(BASE), SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.NEVER, Integer.MAX_VALUE, Integer.MAX_VALUE, false, LDAPFilter.decode("(objectclass=inetorgperson)"), newLinkedHashSet("*")); SearchResultEntryProtocolOp searchResultEntry = searchExternalForSingleEntry(searchRequest, null); assertThat(searchResultEntry.getAttributes()).hasSize(ldapAttrCount); } @Test public void testSearchExternalUnspecifiedAttributesOmitValues() throws Exception { SearchRequestProtocolOp searchRequest = new SearchRequestProtocolOp( ByteString.valueOfUtf8(BASE), SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.NEVER, Integer.MAX_VALUE, Integer.MAX_VALUE, true, LDAPFilter.decode("(objectclass=inetorgperson)"), null); SearchResultEntryProtocolOp searchResultEntry = searchExternalForSingleEntry(searchRequest, null); // The attributes will include the objectclass type. assertThat(searchResultEntry.getAttributes()).hasSize(ldapAttrCount); for (LDAPAttribute a : searchResultEntry.getAttributes()) { assertThat(a.getValues()).isEmpty(); } } @Test public void testSearchExternalAllUserAttributesOmitValues() throws Exception { SearchRequestProtocolOp searchRequest = new SearchRequestProtocolOp( ByteString.valueOfUtf8(BASE), SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.NEVER, Integer.MAX_VALUE, Integer.MAX_VALUE, true, LDAPFilter.decode("(objectclass=inetorgperson)"), newLinkedHashSet("*")); SearchResultEntryProtocolOp searchResultEntry = searchExternalForSingleEntry(searchRequest, null); // The attributes will include the objectclass type. assertThat(searchResultEntry.getAttributes()).hasSize(ldapAttrCount); for (LDAPAttribute a : searchResultEntry.getAttributes()) { assertThat(a.getValues()).isEmpty(); } } @Test public void testSearchExternalObjectClassAttribute() throws Exception { SearchRequestProtocolOp searchRequest = new SearchRequestProtocolOp( ByteString.valueOfUtf8(BASE), SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.NEVER, Integer.MAX_VALUE, Integer.MAX_VALUE, false, LDAPFilter.decode("(objectclass=inetorgperson)"), newLinkedHashSet("objectclass")); SearchResultEntryProtocolOp searchResultEntry = searchExternalForSingleEntry(searchRequest, null); assertThat(searchResultEntry.getAttributes()).hasSize(1); assertThat(searchResultEntry.getAttributes().getFirst().getValues()).hasSize(4); } @Test public void testSearchExternalObjectClassAttributeOmitValues() throws Exception { SearchRequestProtocolOp searchRequest = new SearchRequestProtocolOp( ByteString.valueOfUtf8(BASE), SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.NEVER, Integer.MAX_VALUE, Integer.MAX_VALUE, true, LDAPFilter.decode("(objectclass=inetorgperson)"), newLinkedHashSet("objectclass")); SearchResultEntryProtocolOp searchResultEntry = searchExternalForSingleEntry(searchRequest, null); assertThat(searchResultEntry.getAttributes()).hasSize(1); assertThat(searchResultEntry.getAttributes().getFirst().getValues()).isEmpty(); } @Test public void testSearchExternalSelectedAttributes() throws Exception { SearchRequestProtocolOp searchRequest = new SearchRequestProtocolOp( ByteString.valueOfUtf8(BASE), SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.NEVER, Integer.MAX_VALUE, Integer.MAX_VALUE, false, LDAPFilter.decode("(objectclass=inetorgperson)"), newLinkedHashSet("uid", "createtimestamp")); SearchResultEntryProtocolOp searchResultEntry = searchExternalForSingleEntry(searchRequest, null); assertThat(searchResultEntry.getAttributes()).hasSize(2); } @Test public void testSearchExternalAttributeWithSubtypes() throws Exception { SearchRequestProtocolOp searchRequest = new SearchRequestProtocolOp( ByteString.valueOfUtf8(BASE), SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.NEVER, Integer.MAX_VALUE, Integer.MAX_VALUE, false, LDAPFilter.decode("(objectclass=inetorgperson)"), newLinkedHashSet("title")); SearchResultEntryProtocolOp searchResultEntry = searchExternalForSingleEntry(searchRequest, null); assertThat(searchResultEntry.getAttributes()).hasSize(4); } @Test public void testSearchExternalAttributeWithSubtypesOmitValues() throws Exception { SearchRequestProtocolOp searchRequest = new SearchRequestProtocolOp( ByteString.valueOfUtf8(BASE), SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.NEVER, Integer.MAX_VALUE, Integer.MAX_VALUE, true, LDAPFilter.decode("(objectclass=inetorgperson)"), newLinkedHashSet("title")); SearchResultEntryProtocolOp searchResultEntry = searchExternalForSingleEntry(searchRequest, null); assertThat(searchResultEntry.getAttributes()).hasSize(4); for (LDAPAttribute a : searchResultEntry.getAttributes()) { assertThat(a.getValues()).isEmpty(); } } @Test public void testSearchExternalAttributeWithOptions() throws Exception { SearchRequestProtocolOp searchRequest = new SearchRequestProtocolOp( ByteString.valueOfUtf8(BASE), SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.NEVER, Integer.MAX_VALUE, Integer.MAX_VALUE, false, LDAPFilter.decode("(objectclass=inetorgperson)"), newLinkedHashSet("title;lang-ja;phonetic")); SearchResultEntryProtocolOp searchResultEntry = searchExternalForSingleEntry(searchRequest, null); assertThat(searchResultEntry.getAttributes()).hasSize(1); } @Test public void testSearchExternalMatchedValues() throws Exception { // Add a matched values control. LDAPFilter ldapFilter = LDAPFilter.decode("(title=*director*)"); ArrayList<MatchedValuesFilter> filters = newArrayList(MatchedValuesFilter.createFromLDAPFilter(ldapFilter)); Control mvc = new MatchedValuesControl(true, filters); ArrayList<Control> controls = newArrayList(mvc); SearchRequestProtocolOp searchRequest = new SearchRequestProtocolOp( ByteString.valueOfUtf8(BASE), SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.NEVER, Integer.MAX_VALUE, Integer.MAX_VALUE, false, LDAPFilter.decode("(objectclass=inetorgperson)"), null); SearchResultEntryProtocolOp searchResultEntry = searchExternalForSingleEntry(searchRequest, controls); // Per RFC 3876, an attribute that has no values selected is returned // with an empty set of values. We should therefore expect all the // attributes but only one value. int valueCount = 0; for (LDAPAttribute a : searchResultEntry.getAttributes()) { valueCount += a.getValues().size(); } assertEquals(valueCount, 1); } @Test public void testSearchInternalReferences() throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request = newSearchRequest(BASE, SearchScope.WHOLE_SUBTREE, "(objectclass=inetorgperson)"); InternalSearchOperation searchOperation = getRootConnection().processSearch(request); assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS); assertEquals(searchOperation.getReferencesSent(), 2); assertEquals(searchOperation.getErrorMessage().length(), 0); examineCompletedOperation(searchOperation); List<SearchResultReference> references = searchOperation.getSearchReferences(); assertThat(references).hasSize(2); // One contains 2 URLs, the other contains 3. Cannot guarantee // ordering of the returned references, so just check the total is correct. int urls = references.get(0).getReferralURLs().size() + references.get(1).getReferralURLs().size(); assertEquals(urls, 5); } /** * This does a single-level search that finds one referral object, but * not the one in a deeper subtree. * * @throws Exception */ @Test public void testSearchSingleDeepReferences() throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request = newSearchRequest(BASE, SearchScope.SINGLE_LEVEL, "(ou=*)"); InternalSearchOperation searchOperation = getRootConnection().processSearch(request); assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS); assertEquals(searchOperation.getReferencesSent(), 1); assertEquals(searchOperation.getErrorMessage().length(), 0); examineCompletedOperation(searchOperation); List<SearchResultReference> references = searchOperation.getSearchReferences(); assertThat(references).hasSize(1); assertThat(references.get(0).getReferralURLs()).hasSize(2); } @Test public void testSearchInternalSubEntryEqualityFilter() throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request = newSearchRequest(BASE, SearchScope.WHOLE_SUBTREE, "(objectclass=ldapsubentry)"); InternalSearchOperation searchOperation = getRootConnection().processSearch(request); assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS); assertEquals(searchOperation.getEntriesSent(), 1); assertEquals(searchOperation.getErrorMessage().length(), 0); } @Test public void testSearchInternalSubEntryControl() throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request = Requests.newSearchRequest(DN.valueOf(BASE), SearchScope.WHOLE_SUBTREE) .addControl(new SubentriesControl(true, true)); InternalSearchOperation searchOperation = getRootConnection().processSearch(request); assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS); assertEquals(searchOperation.getEntriesSent(), 1); assertEquals(searchOperation.getErrorMessage().length(), 0); } @Test public void testSearchInternalLegacySubEntryControl() throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request = Requests.newSearchRequest(DN.valueOf(BASE), SearchScope.WHOLE_SUBTREE) .addControl(new LDAPControl(OID_LDUP_SUBENTRIES, true)); InternalSearchOperation searchOperation = getRootConnection().processSearch(request); assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS); assertEquals(searchOperation.getEntriesSent(), 1); assertEquals(searchOperation.getErrorMessage().length(), 0); } @Test public void testSearchInternalSubEntryAndFilter() throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request1 = newSearchRequest(BASE, SearchScope.WHOLE_SUBTREE, "(&(cn=*)(objectclass=ldapsubentry))"); InternalSearchOperation searchOperation = getRootConnection().processSearch(request1); assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS); assertEquals(searchOperation.getEntriesSent(), 1); assertEquals(searchOperation.getErrorMessage().length(), 0); SearchRequest request2 = newSearchRequest(BASE, SearchScope.WHOLE_SUBTREE, "(&(&(cn=*)(objectclass=ldapsubentry)))"); searchOperation = getRootConnection().processSearch(request2); assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS); assertEquals(searchOperation.getEntriesSent(), 1); assertEquals(searchOperation.getErrorMessage().length(), 0); } @Test public void testSearchInternalSubEntryOrFilter() throws Exception { InvocationCounterPlugin.resetAllCounters(); SearchRequest request = newSearchRequest(BASE, SearchScope.WHOLE_SUBTREE, "(|(objectclass=ldapsubentry)(objectclass=top))"); InternalSearchOperation searchOperation = getRootConnection().processSearch(request); assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS); assertEquals(searchOperation.getEntriesSent(), 5); assertEquals(searchOperation.getErrorMessage().length(), 0); } @Test public void testSearchInternalMatchedDN() throws Exception { InvocationCounterPlugin.resetAllCounters(); TestCaseUtils.initializeTestBackend(true); SearchRequest request = newSearchRequest(DN.valueOf("ou=nonexistent,o=test"), SearchScope.WHOLE_SUBTREE); InternalSearchOperation searchOperation = getRootConnection().processSearch(request); assertEquals(searchOperation.getResultCode(), ResultCode.NO_SUCH_OBJECT); assertNotNull(searchOperation.getMatchedDN()); } /** Determines how attributes should be filtered in search operations. */ private enum AttributeFilterType { DEFAULT, WILDCARDS, ENUMERATED; } /** * Returns test data for testSearchInternalAttributeFilters. * * @return The test data. */ @DataProvider(name = "testSearchInternalAttributeFilters") public Object[][] createTestSearchInternalAttributeFiltersData() { return new Object[][] { {AttributeFilterType.DEFAULT, false, false, false}, {AttributeFilterType.DEFAULT, false, false, true}, {AttributeFilterType.DEFAULT, false, true, false}, {AttributeFilterType.DEFAULT, false, true, true}, {AttributeFilterType.DEFAULT, true, false, false}, {AttributeFilterType.DEFAULT, true, false, true}, {AttributeFilterType.DEFAULT, true, true, false}, {AttributeFilterType.DEFAULT, true, true, true}, {AttributeFilterType.WILDCARDS, false, false, false}, {AttributeFilterType.WILDCARDS, false, false, true}, {AttributeFilterType.WILDCARDS, false, true, false}, {AttributeFilterType.WILDCARDS, false, true, true}, {AttributeFilterType.WILDCARDS, true, false, false}, {AttributeFilterType.WILDCARDS, true, false, true}, {AttributeFilterType.WILDCARDS, true, true, false}, {AttributeFilterType.WILDCARDS, true, true, true}, {AttributeFilterType.ENUMERATED, false, false, false}, {AttributeFilterType.ENUMERATED, false, false, true}, {AttributeFilterType.ENUMERATED, false, true, false}, {AttributeFilterType.ENUMERATED, false, true, true}, {AttributeFilterType.ENUMERATED, true, false, false}, {AttributeFilterType.ENUMERATED, true, false, true}, {AttributeFilterType.ENUMERATED, true, true, false}, {AttributeFilterType.ENUMERATED, true, true, true}, }; } /** * Tests that attribute filtering is performed correctly for real and * virtual attributes when various combinations of typesOnly, and the * real-attributes-only and virtual-attributes-only controls are used * (issues 3446 and 3726). * * @param filterType * Specifies how attributes should be filtered. * @param typesOnly * Strip attribute values. * @param stripVirtualAttributes * Strip virtual attributes. * @param stripRealAttributes * Strip real attributes. * @throws Exception * If an unexpected problem occurs. */ @Test(dataProvider = "testSearchInternalAttributeFilters") public void testSearchInternalAttributeFilters( AttributeFilterType filterType, boolean typesOnly, boolean stripVirtualAttributes, boolean stripRealAttributes) throws Exception { TestCaseUtils.initializeTestBackend(true); // Real attributes (these are all user attributes). List<String> realAttrTypes = Arrays.asList("objectclass", "uid", "cn", "sn", "givenname", "userpassword"); // Virtual attributes (these are all operational attributes). List<String> virtualAttrTypes = Arrays.asList("numsubordinates", "hassubordinates", "subschemasubentry", "entrydn", "ismemberof"); String userDNString = "uid=test.user,o=test"; DN userDN = DN.valueOf(userDNString); TestCaseUtils.addEntry("dn: " + userDNString, "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: test.user", "givenName: Test", "sn: User", "cn: Test User", "userPassword: password"); final SearchRequest request = Requests.newSearchRequest(userDN, SearchScope.BASE_OBJECT); request.setTypesOnly(typesOnly); switch (filterType) { case DEFAULT: // Only user attributes. break; case WILDCARDS: request.addAttribute("*", "+"); break; case ENUMERATED: request.addAttribute(realAttrTypes); request.addAttribute(virtualAttrTypes); break; } if (stripRealAttributes) { request.addControl(new LDAPControl(OID_VIRTUAL_ATTRS_ONLY, false)); } if (stripVirtualAttributes) { request.addControl(new LDAPControl(OID_REAL_ATTRS_ONLY, false)); } InternalSearchOperation search = getRootConnection().processSearch(request); assertEquals(search.getResultCode(), ResultCode.SUCCESS); LinkedList<SearchResultEntry> entries = search.getSearchEntries(); assertThat(entries).hasSize(1); Entry entry = entries.getFirst(); assertEquals(entry.getName(), userDN); // Check real attributes and virtual (operational) attributes. List<String> messages = new LinkedList<>(); checkRealAttributes(typesOnly, stripRealAttributes, realAttrTypes, entry, messages); checkVirtualOperationalAttributes(filterType, typesOnly, stripVirtualAttributes, virtualAttrTypes, entry, messages); assertThat(messages).isEmpty(); } private void checkVirtualOperationalAttributes(AttributeFilterType filterType, boolean typesOnly, boolean stripVirtualAttributes, List<String> virtualAttrTypes, Entry entry, List<String> messages) { for (String attrType : virtualAttrTypes) { List<Attribute> attrList = entry.getAttribute(attrType); if (stripVirtualAttributes) { if (attrList != null) { messages.add("Unexpected virtual attribute: " + attrType); } } else if (filterType == AttributeFilterType.DEFAULT) { if (attrList != null) { messages.add("Unexpected operational attribute: " + attrType); } } else if ("ismemberof".equals(attrType)) { // isMemberOf should never be returned as user is not in any groups. if (attrList != null) { messages.add("Unexpected isMemberOf attribute"); } } else { if (attrList == null) { messages.add("Missing virtual attribute: " + attrType); } else { Attribute attr = attrList.get(0); if (typesOnly) { if (!attr.isEmpty()) { messages.add("Unexpected non-empty virtual attribute: " + attrType); } } else { if (attr.isEmpty()) { messages.add("Unexpected empty virtual attribute: " + attrType); } } } } } } private List<String> checkRealAttributes(boolean typesOnly, boolean stripRealAttributes, List<String> realAttrTypes, Entry entry, List<String> messages) { for (String attrType : realAttrTypes) { List<Attribute> attrList = entry.getAttribute(attrType); if (stripRealAttributes) { if (attrList != null) { messages.add("Unexpected real attribute: " + attrType); } } else { if (attrList == null) { messages.add("Missing real attribute: " + attrType); } else { Attribute attr = attrList.get(0); if (typesOnly) { if (!attr.isEmpty()) { messages.add("Unexpected non-empty real attribute: " + attrType); } } else { if (attr.isEmpty()) { messages.add("Unexpected empty real attribute: " + attrType); } } } } } return messages; } /** * Returns test data for testSearchInternalUserAttributeNames. * * @return The test data. */ @DataProvider(name = "testSearchInternalUserAttributeNames") public Object[][] createTestSearchInternalUserAttributeNamesData() { // First array is the requested attributes. // Second array is the expected attribute names in the entry. return new Object[][] { { Arrays.<String>asList(), Arrays.asList("objectClass", "cn", "cn;lang-fr") }, { Arrays.asList("*", "+"), Arrays.asList("objectClass", "cn", "cn;lang-fr", "entryDN", "createTimestamp") }, { Arrays.asList("objectClass", "cn", "cn;lang-fr", "entryDN", "createTimestamp"), Arrays.asList("objectClass", "cn", "cn;lang-fr", "entryDN", "createTimestamp") }, { Arrays.asList("OBJECTCLASS", "commonName", "commonName;LANG-FR", "entrydn", "CREATETIMESTAMP"), Arrays.asList("OBJECTCLASS", "commonName", "commonName;LANG-FR", "entrydn", "CREATETIMESTAMP") }, { Arrays.asList("*", "+", "OBJECTCLASS", "commonName", "commonName;LANG-FR", "entrydn", "CREATETIMESTAMP"), Arrays.asList("OBJECTCLASS", "commonName", "commonName;LANG-FR", "entrydn", "CREATETIMESTAMP") }, { Arrays.asList("name"), Arrays.asList("givenName", "sn", "cn", "cn;lang-fr") }, { Arrays.asList("name;lang-fr"), Arrays.asList("cn;lang-fr") }, { Arrays.asList("name;LANG-FR"), Arrays.asList("cn;LANG-FR") }, }; } /** * Tests that attributes are returned from internal searches using the * attribute name requested by the user. * * @param requestedAttributes * The list of requested attributes names. * @param expectedAttributes * The list of expected attribute names. * @throws Exception * If an unexpected problem occurs. */ @Test(dataProvider = "testSearchInternalUserAttributeNames") public void testSearchInternalUserAttributeNames( List<String> requestedAttributes, List<String> expectedAttributes) throws Exception { TestCaseUtils.initializeTestBackend(true); String userDNString = "uid=test.user,o=test"; DN userDN = DN.valueOf(userDNString); TestCaseUtils.addEntry("dn: " + userDNString, "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: test.user", "givenName: Test", "sn: User", "cn: Test User", "cn;lang-fr: Test Usager", "userPassword: password"); SearchRequest request = Requests.newSearchRequest(userDN, SearchScope.BASE_OBJECT) .addAttribute(requestedAttributes); InternalSearchOperation search = getRootConnection().processSearch(request); assertEquals(search.getResultCode(), ResultCode.SUCCESS); LinkedList<SearchResultEntry> entries = search.getSearchEntries(); assertThat(entries).hasSize(1); Entry entry = entries.getFirst(); assertEquals(entry.getName(), userDN); // Check all expected attributes are present and have the user requested name. assertThat(getAttributeNames(entry)).containsAll(expectedAttributes); } private Set<String> getAttributeNames(Entry entry) { List<Attribute> attrList = entry.getAttributes(); Set<String> actualNames = new HashSet<>(); for (Attribute attribute : attrList) { actualNames.add(attribute.getNameWithOptions()); } return actualNames; } /** * Tests that attributes are returned from external searches using the * attribute name requested by the user. * * @param requestedAttributes * The list of requested attributes names. * @param expectedAttributes * The list of expected attribute names. * @throws Exception * If an unexpected problem occurs. */ @Test(dataProvider = "testSearchInternalUserAttributeNames") public void testSearchExternalUserAttributeNames( List<String> requestedAttributes, List<String> expectedAttributes) throws Exception { TestCaseUtils.initializeTestBackend(true); String userDNString = "uid=test.user,o=test"; DN userDN = DN.valueOf(userDNString); TestCaseUtils.addEntry("dn: " + userDNString, "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: test.user", "givenName: Test", "sn: User", "cn: Test User", "cn;lang-fr: Test Usager", "userPassword: password"); SearchRequestProtocolOp searchRequest = new SearchRequestProtocolOp( ByteString.valueOfUtf8(userDNString), SearchScope.BASE_OBJECT, DereferenceAliasesPolicy.NEVER, Integer.MAX_VALUE, Integer.MAX_VALUE, false, LDAPFilter.objectClassPresent(), new LinkedHashSet<>(requestedAttributes)); SearchResultEntryProtocolOp entry = searchExternalForSingleEntry(searchRequest, null); assertEquals(entry.getDN(), userDN); // Check all expected attributes are present and have the user requested name. assertThat(getAttributeNames(entry)).containsAll(expectedAttributes); } private Set<String> getAttributeNames(SearchResultEntryProtocolOp entry) { LinkedList<LDAPAttribute> attrList = entry.getAttributes(); Set<String> actualNames = new HashSet<>(); for (LDAPAttribute attribute : attrList) { actualNames.add(attribute.getAttributeType()); } return actualNames; } /** Tests the one-level search with a lower allid threshold value. */ @Test public void testOneLevelSearchWithAllIDThreshold() throws Exception { //Set a lower value for allid threshold. We set 2. setAllIdThreshold(2); //Add entries. String filePath = TestCaseUtils.createTempFile( "dn: ou=unit1,dc=example,dc=com\n" + "objectclass: top\n" + "objectclass: organizationalUnit\n" + "ou: unit1\n" + "\n" + "dn: ou=unit2,dc=example,dc=com\n" + "objectclass: top\n" + "objectclass: organizationalUnit\n" + "ou: unit2\n" + "\n" + "dn: ou=unit3,dc=example,dc=com\n" + "objectclass: top\n" + "objectclass: organizationalUnit\n" + "ou: unit3\n"); String[] args = new String [] { "-h", "127.0.0.1", "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), "-D","cn=directory manager", "-w","password", "-a", "-f", filePath }; int err = LDAPModify.mainModify(args, false, null,null); assertEquals(err,0); SearchRequest request = newSearchRequest("dc=example,dc=com", SearchScope.SINGLE_LEVEL, "(objectclass=organizationalUnit)"); InternalSearchOperation searchOperation = getRootConnection().processSearch(request); assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS); assertThat(searchOperation.getSearchEntries()).hasSize(3); //restore the allid threshold. setAllIdThreshold(4000); } /** Sets a value of the allid threshold. */ private void setAllIdThreshold(int value) throws Exception { //Change the allid threshold value. String filePath = TestCaseUtils.createTempFile( "dn: ds-cfg-backend-id=userRoot,cn=Backends,cn=config", "changetype: modify", "delete: ds-cfg-index-entry-limit", "-", "add: ds-cfg-index-entry-limit", "ds-cfg-index-entry-limit: "+value ); String[] args = new String [] { "-h", "127.0.0.1", "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), "-D","cn=directory manager", "-w","password", "-a", "-f", filePath }; int err = LDAPModify.mainModify(args, false, null,null); assertEquals(err, 0); } }