/*
* ====================
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License("CDDL") (the "License"). You may not use this file
* except in compliance with the License.
*
* You can obtain a copy of the License at
* http://IdentityConnectors.dev.java.net/legal/license.txt
* See the License for the specific language governing permissions and limitations
* under the License.
*
* When distributing the Covered Code, include this CDDL Header Notice in each file
* and include the License file at identityconnectors/legal/license.txt.
* If applicable, add the following below this CDDL Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
* ====================
*/
package org.identityconnectors.ldap.search;
import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertNotNull;
import static org.testng.AssertJUnit.assertTrue;
import org.testng.annotations.Test;
import static java.util.Collections.singleton;
import static org.identityconnectors.common.CollectionUtil.newSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.identityconnectors.common.security.GuardedString;
import org.identityconnectors.common.security.GuardedString.Accessor;
import org.identityconnectors.framework.api.ConnectorFacade;
import org.identityconnectors.framework.common.exceptions.ConnectorException;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.AttributeBuilder;
import org.identityconnectors.framework.common.objects.AttributeInfo;
import org.identityconnectors.framework.common.objects.AttributeInfoUtil;
import org.identityconnectors.framework.common.objects.AttributeUtil;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.Name;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.OperationOptions;
import org.identityconnectors.framework.common.objects.OperationOptionsBuilder;
import org.identityconnectors.framework.common.objects.OperationalAttributes;
import org.identityconnectors.framework.common.objects.QualifiedUid;
import org.identityconnectors.framework.common.objects.ResultsHandler;
import org.identityconnectors.framework.common.objects.Uid;
import org.identityconnectors.framework.common.objects.filter.Filter;
import org.identityconnectors.framework.common.objects.filter.FilterBuilder;
import org.identityconnectors.ldap.LdapConfiguration;
import org.identityconnectors.ldap.LdapConnection;
import org.identityconnectors.ldap.LdapConnectorTestBase;
import org.identityconnectors.test.common.TestHelpers;
import org.identityconnectors.test.common.ToListResultsHandler;
public class LdapSearchTests extends LdapConnectorTestBase {
// TODO operational attributes.
// TODO LDAP directory attributes (entryDN, etc.).
@Override
protected boolean restartServerAfterEachTest() {
return false;
}
@Test
public void testLdapFilter() {
LdapConnection conn = new LdapConnection(newConfiguration());
LdapFilter filter = LdapFilter.forEntryDN(BUGS_BUNNY_DN);
ToListResultsHandler handler = new ToListResultsHandler();
new LdapSearch(conn, ObjectClass.ACCOUNT, filter, new OperationOptionsBuilder().build()).execute(handler);
assertEquals(1, handler.getObjects().size());
filter = filter.withNativeFilter("(foo=bar)");
handler = new ToListResultsHandler();
new LdapSearch(conn, ObjectClass.ACCOUNT, filter, new OperationOptionsBuilder().build()).execute(handler);
assertTrue(handler.getObjects().isEmpty());
}
@Test
public void testLdapFilterWithNonExistingEntryDN() {
LdapFilter filter = LdapFilter.forEntryDN("dc=foo,dc=bar");
// VLV index.
LdapConfiguration config = newConfiguration();
config.setUseBlocks(true);
config.setUsePagedResultControl(false);
searchExpectingNoResult(config, filter);
// Simple paged results.
config = newConfiguration();
config.setUseBlocks(true);
config.setUsePagedResultControl(true);
searchExpectingNoResult(config, filter);
// No paging.
config = newConfiguration();
config.setUseBlocks(false);
searchExpectingNoResult(config, filter);
}
@Test
public void testLdapFilterWithInvalidEntryDN() {
LdapFilter filter = LdapFilter.forEntryDN("dc=foo,,");
// VLV index.
LdapConfiguration config = newConfiguration();
config.setUseBlocks(true);
config.setUsePagedResultControl(false);
searchExpectingNoResult(config, filter);
// Simple paged results.
config = newConfiguration();
config.setUseBlocks(true);
config.setUsePagedResultControl(true);
searchExpectingNoResult(config, filter);
// No paging.
config = newConfiguration();
config.setUseBlocks(false);
searchExpectingNoResult(config, filter);
}
private void searchExpectingNoResult(LdapConfiguration config, LdapFilter filter) {
LdapConnection conn = new LdapConnection(config);
ToListResultsHandler handler = new ToListResultsHandler();
// Should not fail with NameNotFoundException or InvalidNameException.
new LdapSearch(conn, ObjectClass.ACCOUNT, filter, new OperationOptionsBuilder().build()).execute(handler);
assertTrue(handler.getObjects().isEmpty());
}
@Test
public void testCanCancelSearch() {
// VLV Index.
LdapConfiguration config = newConfiguration();
config.setBaseContexts(ACME_DN, BIG_COMPANY_DN);
config.setUseBlocks(true);
config.setUsePagedResultControl(false);
searchExpectingSingleResult(config);
// Simple paged results.
config = newConfiguration();
config.setBaseContexts(ACME_DN, BIG_COMPANY_DN);
config.setUseBlocks(true);
config.setUsePagedResultControl(true);
searchExpectingSingleResult(config);
// No paging.
config = newConfiguration();
config.setBaseContexts(ACME_DN, BIG_COMPANY_DN);
config.setUseBlocks(false);
searchExpectingSingleResult(config);
}
private void searchExpectingSingleResult(LdapConfiguration config) {
LdapConnection conn = new LdapConnection(config);
FirstOnlyResultsHandler handler = new FirstOnlyResultsHandler();
new LdapSearch(conn, ObjectClass.ACCOUNT, null, new OperationOptionsBuilder().build()).execute(handler);
handler.assertSingleResult();
}
@Test
public void testSimplePagedSearch() {
LdapConfiguration config = newConfiguration();
config.setUseBlocks(true);
config.setUsePagedResultControl(true);
ConnectorFacade facade = newFacade(config);
List<ConnectorObject> objects = TestHelpers.searchToList(facade, ObjectClass.ACCOUNT, null);
assertNotNull(getObjectByName(objects, BUGS_BUNNY_DN));
assertNotNull(getObjectByName(objects, USER_0_DN));
// 1000 is the default search size limit for OpenDS.
assertTrue(objects.size() > 1000);
}
@Test
public void testVlvIndexSearch() {
LdapConfiguration config = newConfiguration();
config.setBaseContexts(EXAMPLE_COM_DN);
config.setUseBlocks(true);
config.setUsePagedResultControl(false);
config.setUidAttribute("entryDN");
ConnectorFacade facade = newFacade(config);
List<ConnectorObject> objects = TestHelpers.searchToList(facade, ObjectClass.ACCOUNT, null);
assertNotNull(getObjectByName(objects, USER_0_DN));
// 1000 is the default search size limit for OpenDS.
assertTrue(objects.size() > 1000);
// OpenDS-specific.
OperationOptionsBuilder builder = new OperationOptionsBuilder();
builder.setAttributesToGet("debugsearchindex");
FirstOnlyResultsHandler handler = new FirstOnlyResultsHandler();
facade.search(ObjectClass.ACCOUNT, null, handler, builder.build());
String debugsearch = handler.getSingleResult().getAttributeByName("debugsearchindex").getValue().get(0).toString();
assertTrue(debugsearch.contains("vlv"));
}
@Test(expectedExceptions = ConnectorException.class)
public void testNoUseBlocks() {
LdapConfiguration config = newConfiguration();
config.setUseBlocks(false);
ConnectorFacade facade = newFacade(config);
// This should fail, since the search will exceed the maximum number of
// entries to return.
TestHelpers.searchToList(facade, ObjectClass.ACCOUNT, null);
}
@Test
public void testWithFilter() {
ConnectorFacade facade = newFacade();
ConnectorObject bunny = searchByAttribute(facade, ObjectClass.ACCOUNT, new Name(BUGS_BUNNY_DN));
assertEquals(BUGS_BUNNY_DN, bunny.getName().getNameValue());
}
@Test
public void testWithFilterByBinaryAttribute() {
ConnectorFacade facade = newFacade();
ConnectorObject bunny = searchByAttribute(facade, ObjectClass.ACCOUNT, new Name(BUGS_BUNNY_DN));
byte[] photo = { -4, -3, -2, -1, 0, 1, 2, 3, 63, 127 };
Attribute photoAttr = AttributeBuilder.build("jpegPhoto", photo);
Uid newUid = facade.addAttributeValues(ObjectClass.ACCOUNT, bunny.getUid(), singleton(photoAttr), null);
ConnectorObject bunnyWithPhoto = searchByAttribute(facade, ObjectClass.ACCOUNT, photoAttr, "jpegPhoto");
assertEquals(newUid, bunnyWithPhoto.getUid());
}
@Test
public void testAttributesToGet() {
ConnectorFacade facade = newFacade();
ConnectorObject object = searchByAttribute(facade, ObjectClass.ACCOUNT, new Name(USER_0_DN), "employeeNumber", "telephoneNumber");
Set<Attribute> attrs = newSet(object.getAttributes());
assertTrue(attrs.remove(AttributeUtil.find(Uid.NAME, attrs)));
assertTrue(attrs.remove(AttributeUtil.find(Name.NAME, attrs)));
assertTrue(attrs.remove(AttributeUtil.find("employeeNumber", attrs)));
assertTrue(attrs.remove(AttributeUtil.find("telephoneNumber", attrs)));
assertTrue(attrs.isEmpty());
}
@Test
public void testAttributesReturnedByDefaultWithNoValueAreNotReturned() {
LdapConfiguration config = newConfiguration(true);
ConnectorFacade facade = newFacade(config);
AttributeInfo attr = AttributeInfoUtil.find("givenName", facade.schema().findObjectClassInfo(ObjectClass.ACCOUNT_NAME).getAttributeInfo());
assertTrue(attr.isReturnedByDefault());
ConnectorObject object = searchByAttribute(facade, ObjectClass.ACCOUNT, new Name(BUGS_BUNNY_DN));
assertNull(object.getAttributeByName("givenName"));
}
@Test
public void testAttributesToGetNotPresentInEntryAreEmpty() {
ConnectorFacade facade = newFacade();
ConnectorObject object = searchByAttribute(facade, ObjectClass.ACCOUNT, new Name(BUGS_BUNNY_DN), "employeeNumber");
assertTrue(object.getAttributeByName("employeeNumber").getValue().isEmpty());
}
@Test
public void testScope() {
ConnectorFacade facade = newFacade();
// Find an organization to pass in OP_CONTAINER.
ObjectClass oclass = new ObjectClass("organization");
ConnectorObject organization = searchByAttribute(facade, oclass, new Name(BIG_COMPANY_DN));
// There are no accounts directly under the organization...
OperationOptionsBuilder optionsBuilder = new OperationOptionsBuilder();
optionsBuilder.setScope(OperationOptions.SCOPE_ONE_LEVEL);
optionsBuilder.setContainer(new QualifiedUid(oclass, organization.getUid()));
List<ConnectorObject> objects = TestHelpers.searchToList(facade, ObjectClass.ACCOUNT, null, optionsBuilder.build());
assertTrue(objects.isEmpty());
// ... but there are some in the organization subtree.
optionsBuilder.setScope(OperationOptions.SCOPE_SUBTREE);
objects = TestHelpers.searchToList(facade, ObjectClass.ACCOUNT, null, optionsBuilder.build());
assertFalse(objects.isEmpty());
}
@Test
public void testAccountSearchFilter() {
ConnectorFacade facade = newFacade();
// Find an organization to pass in OP_CONTAINER.
ObjectClass oclass = new ObjectClass("organization");
ConnectorObject organization = searchByAttribute(facade, oclass, new Name(ACME_DN));
// First just check that there really are some users.
OperationOptionsBuilder optionsBuilder = new OperationOptionsBuilder();
optionsBuilder.setScope(OperationOptions.SCOPE_SUBTREE);
optionsBuilder.setContainer(new QualifiedUid(oclass, organization.getUid()));
List<ConnectorObject> objects = TestHelpers.searchToList(facade, ObjectClass.ACCOUNT, null, optionsBuilder.build());
assertNotNull(getObjectByName(objects, BUGS_BUNNY_DN));
assertNotNull(getObjectByName(objects, ELMER_FUDD_DN));
LdapConfiguration config = newConfiguration();
config.setAccountSearchFilter("(uid=" + BUGS_BUNNY_UID + ")");
facade = newFacade(config);
objects = TestHelpers.searchToList(facade, ObjectClass.ACCOUNT, null, optionsBuilder.build());
assertEquals(1, objects.size());
assertNotNull(getObjectByName(objects, BUGS_BUNNY_DN));
}
@Test
public void testAccountSearchFilterOnlyAppliesToAccounts() {
LdapConfiguration config = newConfiguration();
config.setAccountSearchFilter("(cn=foobarbaz)");
ConnectorFacade facade = newFacade(config);
// If the (cn=foobarbaz) filter above applied, the search would return nothing.
assertNotNull(searchByAttribute(facade, new ObjectClass("organization"), new Name(ACME_DN)));
}
@Test
public void testMissingParenthesesAddedToAccountSearchFilter() {
LdapConfiguration config = newConfiguration();
config.setAccountSearchFilter("uid=" + BUGS_BUNNY_UID); // No parentheses enclosing the filter.
ConnectorFacade facade = newFacade(config);
// If parentheses were not added, the search would fail.
assertNotNull(searchByAttribute(facade, ObjectClass.ACCOUNT, new Name(BUGS_BUNNY_DN)));
}
@Test
public void testMultipleBaseDNs() {
ConnectorFacade facade = newFacade();
// This should find accounts from both base DNs.
List<ConnectorObject> objects = TestHelpers.searchToList(facade, ObjectClass.ACCOUNT, null);
assertNotNull(getObjectByName(objects, BUGS_BUNNY_DN));
assertNotNull(getObjectByName(objects, USER_0_DN));
}
@Test
public void testUidAttributeCn() {
LdapConfiguration config = newConfiguration();
assertFalse(config.getUidAttribute().equalsIgnoreCase("cn"));
config.setUidAttribute("cn");
ConnectorFacade facade = newFacade(config);
ConnectorObject bunny = searchByAttribute(facade, ObjectClass.ACCOUNT, new Uid(BUGS_BUNNY_CN));
assertEquals(BUGS_BUNNY_DN, bunny.getName().getNameValue());
}
@Test
public void testUidAttributeEntryDN() {
LdapConfiguration config = newConfiguration();
assertFalse(config.getUidAttribute().equalsIgnoreCase("entryDN"));
config.setUidAttribute("entryDN");
ConnectorFacade facade = newFacade(config);
ConnectorObject bunny = searchByAttribute(facade, ObjectClass.ACCOUNT, new Uid(BUGS_BUNNY_DN));
assertEquals(BUGS_BUNNY_DN, bunny.getName().getNameValue());
}
@Test
public void testSearchArbitraryObjectClass() {
ConnectorFacade facade = newFacade();
// Simplest: try w/o filter.
List<ConnectorObject> objects = TestHelpers.searchToList(facade, new ObjectClass("country"), null, null);
ConnectorObject czechRep = getObjectByName(objects, CZECH_REPUBLIC_DN);
// Try with a name filter and options.
Filter filter = FilterBuilder.equalTo(AttributeBuilder.build(Name.NAME, CZECH_REPUBLIC_DN));
OperationOptionsBuilder builder = new OperationOptionsBuilder();
builder.setAttributesToGet("c");
objects = TestHelpers.searchToList(facade, new ObjectClass("country"), filter, builder.build());
czechRep = getObjectByName(objects, CZECH_REPUBLIC_DN);
assertEquals(CZECH_REPUBLIC_C, AttributeUtil.getAsStringValue(czechRep.getAttributeByName("c")));
}
@Test
public void testCannotReturnPasswordFromSearch() {
ConnectorFacade facade = newFacade();
ConnectorObject bunny = searchByAttribute(facade, ObjectClass.ACCOUNT, new Name(BUGS_BUNNY_DN), OperationalAttributes.PASSWORD_NAME);
GuardedString password = (GuardedString) bunny.getAttributeByName(OperationalAttributes.PASSWORD_NAME).getValue().get(0);
password.access(new Accessor() {
public void access(char[] clearChars) {
assertEquals(0, clearChars.length);
}
});
}
private static ConnectorObject getObjectByName(List<ConnectorObject> objects, String name) {
for (ConnectorObject object : objects) {
if (name.equals(object.getName().getNameValue())) {
return object;
}
}
return null;
}
private static final class FirstOnlyResultsHandler implements ResultsHandler {
private final List<ConnectorObject> objects = new ArrayList<ConnectorObject>();
public boolean handle(ConnectorObject obj) {
objects.add(obj);
return false; // We only want the first one.
}
public void assertSingleResult() {
assertEquals(1, objects.size());
}
public ConnectorObject getSingleResult() {
return objects.get(0);
}
}
}