com.evolveum.midpoint.testing.conntest.AbstractLdapTest.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.testing.conntest.AbstractLdapTest.java

Source

package com.evolveum.midpoint.testing.conntest;
/*
 * Copyright (c) 2010-2015 Evolveum
 *
 * Licensed 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.
 */

import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertTrue;
import static org.testng.AssertJUnit.assertNotNull;
import static com.evolveum.midpoint.test.IntegrationTestTools.display;
import static org.testng.AssertJUnit.assertEquals;

import java.io.File;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.xml.namespace.QName;

import com.evolveum.midpoint.common.refinery.RefinedResourceSchema;
import com.evolveum.midpoint.model.impl.sync.ReconciliationTaskHandler;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.Holder;
import com.evolveum.midpoint.util.aspect.ProfilingDataManager;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ContextConfiguration;
import org.testng.AssertJUnit;
import org.testng.annotations.AfterClass;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import org.apache.commons.lang.mutable.MutableInt;
import org.apache.directory.api.ldap.codec.api.BinaryAttributeDetector;
import org.apache.directory.api.ldap.codec.api.DefaultConfigurableBinaryAttributeDetector;
import org.apache.directory.api.ldap.model.cursor.CursorException;
import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException;
import org.apache.directory.api.ldap.model.cursor.EntryCursor;
import org.apache.directory.api.ldap.model.cursor.SearchCursor;
import org.apache.directory.api.ldap.model.entry.Attribute;
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.Modification;
import org.apache.directory.api.ldap.model.entry.ModificationOperation;
import org.apache.directory.api.ldap.model.entry.Value;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
import org.apache.directory.api.ldap.model.message.BindRequest;
import org.apache.directory.api.ldap.model.message.BindRequestImpl;
import org.apache.directory.api.ldap.model.message.BindResponse;
import org.apache.directory.api.ldap.model.message.ModifyDnRequest;
import org.apache.directory.api.ldap.model.message.ModifyDnRequestImpl;
import org.apache.directory.api.ldap.model.message.ModifyDnResponse;
import org.apache.directory.api.ldap.model.message.Response;
import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
import org.apache.directory.api.ldap.model.message.SearchRequest;
import org.apache.directory.api.ldap.model.message.SearchRequestImpl;
import org.apache.directory.api.ldap.model.message.SearchResultEntry;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.api.ldap.model.name.Ava;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.name.Rdn;
import org.apache.directory.ldap.client.api.LdapConnectionConfig;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;

import com.evolveum.midpoint.model.test.AbstractModelIntegrationTest;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.delta.PropertyDelta;
import com.evolveum.midpoint.prism.match.MatchingRule;
import com.evolveum.midpoint.prism.match.MatchingRuleRegistry;
import com.evolveum.midpoint.prism.match.StringIgnoreCaseMatchingRule;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.query.EqualFilter;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.prism.query.ObjectPaging;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.prism.util.PrismAsserts;
import com.evolveum.midpoint.prism.util.PrismTestUtil;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.ResultHandler;
import com.evolveum.midpoint.schema.SearchResultList;
import com.evolveum.midpoint.schema.SearchResultMetadata;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.constants.MidPointConstants;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition;
import com.evolveum.midpoint.schema.processor.ResourceAttribute;
import com.evolveum.midpoint.schema.processor.ResourceAttributeDefinition;
import com.evolveum.midpoint.schema.processor.ResourceSchema;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.MiscSchemaUtil;
import com.evolveum.midpoint.schema.util.ObjectQueryUtil;
import com.evolveum.midpoint.schema.util.SchemaTestConstants;
import com.evolveum.midpoint.schema.util.ShadowUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.test.util.MidPointTestConstants;
import com.evolveum.midpoint.test.util.TestUtil;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentPolicyEnforcementType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.RoleType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemObjectsType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType;

/**
 * @author Radovan Semancik
 *
 */
@ContextConfiguration(locations = { "classpath:ctx-conntest-test-main.xml" })
@Listeners({ com.evolveum.midpoint.tools.testng.AlphabeticalMethodInterceptor.class })
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
public abstract class AbstractLdapTest extends AbstractModelIntegrationTest {

    private static final Trace LOGGER = TraceManager.getTrace(AbstractLdapTest.class);

    public static final File SYSTEM_CONFIGURATION_FILE = new File(COMMON_DIR, "system-configuration.xml");
    public static final String SYSTEM_CONFIGURATION_OID = SystemObjectsType.SYSTEM_CONFIGURATION.value();

    protected static final File USER_ADMINISTRATOR_FILE = new File(COMMON_DIR, "user-administrator.xml");
    protected static final String USER_ADMINISTRATOR_OID = "00000000-0000-0000-0000-000000000002";
    protected static final String USER_ADMINISTRATOR_USERNAME = "administrator";

    protected static final File ROLE_SUPERUSER_FILE = new File(COMMON_DIR, "role-superuser.xml");
    protected static final String ROLE_SUPERUSER_OID = "00000000-0000-0000-0000-000000000004";

    protected static final File USER_BARBOSSA_FILE = new File(COMMON_DIR, "user-barbossa.xml");
    protected static final String USER_BARBOSSA_OID = "c0c010c0-d34d-b33f-f00d-111111111112";
    protected static final String USER_BARBOSSA_USERNAME = "barbossa";
    protected static final String USER_BARBOSSA_FULL_NAME = "Hector Barbossa";
    protected static final String USER_BARBOSSA_PASSWORD = "deadjack.tellnotales123";

    // Barbossa after rename
    protected static final String USER_CPTBARBOSSA_USERNAME = "cptbarbossa";

    protected static final File USER_GUYBRUSH_FILE = new File(COMMON_DIR, "user-guybrush.xml");
    protected static final String USER_GUYBRUSH_OID = "c0c010c0-d34d-b33f-f00d-111111111116";
    protected static final String USER_GUYBRUSH_USERNAME = "guybrush";
    protected static final String USER_GUYBRUSH_FULL_NAME = "Guybrush Threepwood";

    private static final String LDAP_INETORGPERSON_OBJECTCLASS = "inetOrgPerson";

    @Autowired(required = true)
    protected MatchingRuleRegistry matchingRuleRegistry;

    @Autowired
    protected ReconciliationTaskHandler reconciliationTaskHandler;

    protected ResourceType resourceType;
    protected PrismObject<ResourceType> resource;

    protected MatchingRule<String> ciMatchingRule;

    private static String stopCommand;

    protected ObjectClassComplexTypeDefinition accountObjectClassDefinition;

    protected DefaultConfigurableBinaryAttributeDetector binaryAttributeDetector = new DefaultConfigurableBinaryAttributeDetector();

    @Override
    protected void startResources() throws Exception {
        super.startResources();

        String command = getStartSystemCommand();
        if (command != null) {
            TestUtil.execSystemCommand(command);
        }
        stopCommand = getStopSystemCommand();
    }

    public abstract String getStartSystemCommand();

    public abstract String getStopSystemCommand();

    @AfterClass
    public static void stopResources() throws Exception {
        //end profiling
        ProfilingDataManager.getInstance().printMapAfterTest();
        ProfilingDataManager.getInstance().stopProfilingAfterTest();

        if (stopCommand != null) {
            TestUtil.execSystemCommand(stopCommand);
        }
    }

    protected abstract String getResourceOid();

    protected abstract File getBaseDir();

    protected File getResourceFile() {
        return new File(getBaseDir(), "resource.xml");
    }

    protected File getSyncTaskFile() {
        return new File(getBaseDir(), "task-sync.xml");
    }

    protected File getSyncTaskInetOrgPersonFile() {
        return new File(getBaseDir(), "task-sync-inetorgperson.xml");
    }

    protected abstract String getSyncTaskOid();

    protected QName getAccountObjectClass() {
        return new QName(MidPointConstants.NS_RI, getLdapAccountObjectClass());
    }

    protected String getLdapAccountObjectClass() {
        return LDAP_INETORGPERSON_OBJECTCLASS;
    }

    protected QName getGroupObjectClass() {
        return new QName(MidPointConstants.NS_RI, getLdapGroupObjectClass());
    }

    protected abstract String getLdapServerHost();

    protected abstract int getLdapServerPort();

    protected boolean useSsl() {
        return false;
    }

    protected abstract String getLdapBindDn();

    protected abstract String getLdapBindPassword();

    protected abstract int getSearchSizeLimit();

    protected String getLdapSuffix() {
        return "dc=example,dc=com";
    }

    protected String getPeopleLdapSuffix() {
        return "ou=people," + getLdapSuffix();
    }

    protected String getGroupsLdapSuffix() {
        return "ou=groups," + getLdapSuffix();
    }

    public String getPrimaryIdentifierAttributeName() {
        return "entryUUID";
    }

    public QName getPrimaryIdentifierAttributeQName() {
        return new QName(MidPointConstants.NS_RI, getPrimaryIdentifierAttributeName());
    }

    protected abstract String getLdapGroupObjectClass();

    protected abstract String getLdapGroupMemberAttribute();

    protected boolean needsGroupFakeMemeberEntry() {
        return false;
    }

    protected String getScriptDirectoryName() {
        return "/opt/Bamboo/local/conntest";
    }

    protected boolean isImportResourceAtInit() {
        return true;
    }

    protected boolean allowDuplicateSearchResults() {
        return false;
    }

    @Override
    public void initSystem(Task initTask, OperationResult initResult) throws Exception {
        super.initSystem(initTask, initResult);

        // System Configuration
        PrismObject<SystemConfigurationType> config;
        try {
            config = repoAddObjectFromFile(SYSTEM_CONFIGURATION_FILE, SystemConfigurationType.class, initResult);
        } catch (ObjectAlreadyExistsException e) {
            throw new ObjectAlreadyExistsException("System configuration already exists in repository;"
                    + "looks like the previous test haven't cleaned it up", e);
        }

        modelService.postInit(initResult);

        // to get profiling facilities (until better API is available)
        //        LoggingConfigurationManager.configure(
        //                ProfilingConfigurationManager.checkSystemProfilingConfiguration(config),
        //                config.asObjectable().getVersion(), initResult);

        // administrator
        PrismObject<UserType> userAdministrator = repoAddObjectFromFile(USER_ADMINISTRATOR_FILE, UserType.class,
                initResult);
        repoAddObjectFromFile(ROLE_SUPERUSER_FILE, RoleType.class, initResult);
        login(userAdministrator);

        // Roles

        // Resources
        if (isImportResourceAtInit()) {
            resource = importAndGetObjectFromFile(ResourceType.class, getResourceFile(), getResourceOid(), initTask,
                    initResult);
            resourceType = resource.asObjectable();
        }

        assumeAssignmentPolicy(AssignmentPolicyEnforcementType.RELATIVE);

        //initProfiling - start
        ProfilingDataManager profilingManager = ProfilingDataManager.getInstance();

        Map<ProfilingDataManager.Subsystem, Boolean> subsystems = new HashMap<>();
        subsystems.put(ProfilingDataManager.Subsystem.MODEL, true);
        subsystems.put(ProfilingDataManager.Subsystem.REPOSITORY, true);
        profilingManager.configureProfilingDataManagerForTest(subsystems, true);

        profilingManager.appendProfilingToTest();
        //initProfiling - end

        ciMatchingRule = matchingRuleRegistry.getMatchingRule(StringIgnoreCaseMatchingRule.NAME,
                DOMUtil.XSD_STRING);
    }

    @Test
    public void test010Connection() throws Exception {
        final String TEST_NAME = "test010Connection";
        TestUtil.displayTestTile(TEST_NAME);

        OperationResult testResult = provisioningService.testResource(getResourceOid());

        display("Test connection result", testResult);
        TestUtil.assertSuccess("Test connection failed", testResult);
    }

    @Test
    public void test020Schema() throws Exception {
        final String TEST_NAME = "test020Schema";
        TestUtil.displayTestTile(this, TEST_NAME);

        ResourceSchema resourceSchema = RefinedResourceSchema.getResourceSchema(resource, prismContext);
        display("Resource schema", resourceSchema);

        RefinedResourceSchema refinedSchema = RefinedResourceSchema.getRefinedSchema(resource);
        display("Refined schema", refinedSchema);
        accountObjectClassDefinition = refinedSchema.findObjectClassDefinition(getAccountObjectClass());
        assertNotNull("No definition for object class " + getAccountObjectClass(), accountObjectClassDefinition);
        display("Account object class def", accountObjectClassDefinition);

        ResourceAttributeDefinition<String> cnDef = accountObjectClassDefinition.findAttributeDefinition("cn");
        PrismAsserts.assertDefinition(cnDef, new QName(MidPointConstants.NS_RI, "cn"), DOMUtil.XSD_STRING, 1, -1);
        assertTrue("cn read", cnDef.canRead());
        assertTrue("cn modify", cnDef.canModify());
        assertTrue("cn add", cnDef.canAdd());

        ResourceAttributeDefinition<String> oDef = accountObjectClassDefinition.findAttributeDefinition("o");
        PrismAsserts.assertDefinition(oDef, new QName(MidPointConstants.NS_RI, "o"), DOMUtil.XSD_STRING, 0, -1);
        assertTrue("o read", oDef.canRead());
        assertTrue("o modify", oDef.canModify());
        assertTrue("o add", oDef.canAdd());

        ResourceAttributeDefinition<Long> createTimestampDef = accountObjectClassDefinition
                .findAttributeDefinition("createTimestamp");
        PrismAsserts.assertDefinition(createTimestampDef, new QName(MidPointConstants.NS_RI, "createTimestamp"),
                DOMUtil.XSD_LONG, 0, 1);
        assertTrue("createTimestampDef read", createTimestampDef.canRead());
        assertFalse("createTimestampDef read", createTimestampDef.canModify());
        assertFalse("createTimestampDef read", createTimestampDef.canAdd());

    }

    protected <T> ObjectFilter createAttributeFilter(String attrName, T attrVal) throws SchemaException {
        ResourceAttributeDefinition ldapUidAttrDef = accountObjectClassDefinition.findAttributeDefinition(attrName);
        return EqualFilter.createEqual(new ItemPath(ShadowType.F_ATTRIBUTES, ldapUidAttrDef.getName()),
                ldapUidAttrDef, attrVal);
    }

    protected ObjectQuery createUidQuery(String uid) throws SchemaException {
        ObjectQuery query = ObjectQueryUtil.createResourceAndObjectClassQuery(getResourceOid(),
                getAccountObjectClass(), prismContext);
        ObjectQueryUtil.filterAnd(query.getFilter(), createAttributeFilter("uid", uid));
        return query;
    }

    protected SearchResultList<PrismObject<ShadowType>> doSearch(final String TEST_NAME, ObjectQuery query,
            int expectedSize, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException,
            CommunicationException, ConfigurationException, SecurityViolationException {
        return doSearch(TEST_NAME, query, null, expectedSize, task, result);
    }

    protected SearchResultList<PrismObject<ShadowType>> doSearch(final String TEST_NAME, ObjectQuery query,
            GetOperationOptions rootOptions, int expectedSize, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException,
            SecurityViolationException {
        final List<PrismObject<ShadowType>> foundObjects = new ArrayList<PrismObject<ShadowType>>(expectedSize);
        ResultHandler<ShadowType> handler = new ResultHandler<ShadowType>() {
            @Override
            public boolean handle(PrismObject<ShadowType> object, OperationResult parentResult) {
                //            LOGGER.trace("Found {}", object);
                String name = object.asObjectable().getName().getOrig();
                for (PrismObject<ShadowType> foundShadow : foundObjects) {
                    if (!allowDuplicateSearchResults()
                            && foundShadow.asObjectable().getName().getOrig().equals(name)) {
                        AssertJUnit.fail("Duplicate name " + name);
                    }
                }
                foundObjects.add(object);
                return true;
            }
        };

        Collection<SelectorOptions<GetOperationOptions>> options = null;
        if (rootOptions != null) {
            options = SelectorOptions.createCollection(rootOptions);
        }

        rememberConnectorOperationCount();
        rememberConnectorSimulatedPagingSearchCount();

        // WHEN
        TestUtil.displayWhen(TEST_NAME);
        display("Searching shadows, options=" + options + ", query", query);
        SearchResultMetadata searchResultMetadata = modelService.searchObjectsIterative(ShadowType.class, query,
                handler, options, task, result);

        // THEN
        result.computeStatus();
        TestUtil.assertSuccess(result);

        assertEquals("Unexpected number of accounts", expectedSize, foundObjects.size());

        SearchResultList<PrismObject<ShadowType>> resultList = new SearchResultList<>(foundObjects,
                searchResultMetadata);

        return resultList;
    }

    protected Entry getLdapAccountByUid(String uid) throws LdapException, IOException, CursorException {
        return searchLdapAccount("(uid=" + uid + ")");
    }

    protected Entry getLdapAccountByCn(String cn) throws LdapException, IOException, CursorException {
        return searchLdapAccount("(cn=" + cn + ")");
    }

    protected Entry searchLdapAccount(String filter) throws LdapException, IOException, CursorException {
        LdapNetworkConnection connection = ldapConnect();
        List<Entry> entries = ldapSearch(connection, filter);
        ldapDisconnect(connection);

        assertEquals("Unexpected number of entries for " + filter + ": " + entries, 1, entries.size());
        Entry entry = entries.get(0);

        return entry;
    }

    protected Entry assertLdapAccount(String uid, String cn) throws LdapException, IOException, CursorException {
        Entry entry = getLdapAccountByUid(uid);
        assertAttribute(entry, "cn", cn);
        return entry;
    }

    protected Entry getLdapGroupByName(String name) throws LdapException, IOException, CursorException {
        LdapNetworkConnection connection = ldapConnect();
        List<Entry> entries = ldapSearch(connection,
                "(&(cn=" + name + ")(objectClass=" + getLdapGroupObjectClass() + "))");
        ldapDisconnect(connection);

        assertEquals("Unexpected number of entries for group cn=" + name + ": " + entries, 1, entries.size());
        Entry entry = entries.get(0);

        return entry;
    }

    protected Entry assertLdapGroup(String cn) throws LdapException, IOException, CursorException {
        Entry entry = getLdapGroupByName(cn);
        assertAttribute(entry, "cn", cn);
        return entry;
    }

    protected void assertAttribute(Entry entry, String attrName, String expectedValue)
            throws LdapInvalidAttributeValueException {
        String dn = entry.getDn().toString();
        Attribute ldapAttribute = entry.get(attrName);
        if (ldapAttribute == null) {
            if (expectedValue == null) {
                return;
            } else {
                AssertJUnit.fail("No attribute " + attrName + " in " + dn + ", expected: " + expectedValue);
            }
        } else {
            assertEquals("Wrong attribute " + attrName + " in " + dn, expectedValue, ldapAttribute.getString());
        }
    }

    protected void assertAttributeContains(Entry entry, String attrName, String expectedValue)
            throws LdapInvalidAttributeValueException {
        String dn = entry.getDn().toString();
        Attribute ldapAttribute = entry.get(attrName);
        if (ldapAttribute == null) {
            if (expectedValue == null) {
                return;
            } else {
                AssertJUnit.fail("No attribute " + attrName + " in " + dn + ", expected: " + expectedValue);
            }
        } else {
            List<String> vals = new ArrayList<>();
            Iterator<Value<?>> iterator = ldapAttribute.iterator();
            while (iterator.hasNext()) {
                Value<?> value = iterator.next();
                if (expectedValue.equals(value.getString())) {
                    return;
                }
                vals.add(value.getString());
            }
            AssertJUnit.fail("Wrong attribute " + attrName + " in " + dn + " expected to contain value "
                    + expectedValue + " but it has values " + vals);
        }
    }

    protected void assertAttributeNotContains(Entry entry, String attrName, String expectedValue)
            throws LdapInvalidAttributeValueException {
        String dn = entry.getDn().toString();
        Attribute ldapAttribute = entry.get(attrName);
        if (ldapAttribute == null) {
            return;
        } else {
            Iterator<Value<?>> iterator = ldapAttribute.iterator();
            while (iterator.hasNext()) {
                Value<?> value = iterator.next();
                if (expectedValue.equals(value.getString())) {
                    AssertJUnit.fail("Attribute " + attrName + " in " + dn + " contains value " + expectedValue
                            + ", but it should not have it");
                }
            }
        }
    }

    protected Entry getLdapEntry(String dn) throws LdapException, IOException, CursorException {
        LdapNetworkConnection connection = ldapConnect();
        Entry entry = getLdapEntry(connection, dn);
        ldapDisconnect(connection);
        return entry;
    }

    protected Entry getLdapEntry(LdapNetworkConnection connection, String dn)
            throws LdapException, IOException, CursorException {
        List<Entry> entries = ldapSearch(connection, dn, "(objectclass=*)", SearchScope.OBJECT, "*");
        if (entries.isEmpty()) {
            return null;
        }
        return entries.get(0);
    }

    protected void assertNoLdapAccount(String uid) throws LdapException, IOException, CursorException {
        LdapNetworkConnection connection = ldapConnect();
        List<Entry> entries = ldapSearch(connection, "(uid=" + uid + ")");
        ldapDisconnect(connection);

        assertEquals("Unexpected number of entries for uid=" + uid + ": " + entries, 0, entries.size());
    }

    protected void assertLdapGroupMember(Entry accountEntry, String groupName)
            throws LdapException, IOException, CursorException {
        Entry groupEntry = getLdapGroupByName(groupName);
        assertAttributeContains(groupEntry, getLdapGroupMemberAttribute(), accountEntry.getDn().toString());
    }

    protected void assertLdapNoGroupMember(Entry accountEntry, String groupName)
            throws LdapException, IOException, CursorException {
        Entry groupEntry = getLdapGroupByName(groupName);
        assertAttributeNotContains(groupEntry, getLdapGroupMemberAttribute(), accountEntry.getDn().toString());
    }

    protected List<Entry> ldapSearch(LdapNetworkConnection connection, String filter)
            throws LdapException, CursorException {
        return ldapSearch(connection, getLdapSuffix(), filter, SearchScope.SUBTREE, "*",
                getPrimaryIdentifierAttributeName());
    }

    protected List<Entry> ldapSearch(LdapNetworkConnection connection, String baseDn, String filter,
            SearchScope scope, String... attributes) throws LdapException, CursorException {
        LOGGER.trace("LDAP search base={}, filter={}, scope={}, attributes={}",
                new Object[] { baseDn, filter, scope, attributes });

        SearchRequest searchRequest = new SearchRequestImpl();
        searchRequest.setBase(new Dn(baseDn));
        searchRequest.setFilter(filter);
        searchRequest.setScope(scope);
        searchRequest.addAttributes(attributes);
        searchRequest.ignoreReferrals();

        List<Entry> entries = new ArrayList<Entry>();
        try {
            SearchCursor searchCursor = connection.search(searchRequest);
            while (searchCursor.next()) {
                Response response = searchCursor.get();
                if (response instanceof SearchResultEntry) {
                    Entry entry = ((SearchResultEntry) response).getEntry();
                    entries.add(entry);
                }
            }
        } catch (CursorLdapReferralException e) {
            throw new IllegalStateException("Got referral to: " + e.getReferralInfo(), e);
        }
        return entries;
    }

    protected void assertLdapPassword(String uid, String password)
            throws LdapException, IOException, CursorException {
        Entry entry = getLdapAccountByUid(uid);
        assertLdapPassword(entry, password);
    }

    protected void assertLdapPassword(Entry entry, String password)
            throws LdapException, IOException, CursorException {
        LdapNetworkConnection conn = ldapConnect(entry.getDn().toString(), password);
        assertTrue("Not connected", conn.isConnected());
        assertTrue("Not authenticated", conn.isAuthenticated());
    }

    protected Entry addLdapAccount(String uid, String cn, String givenName, String sn)
            throws LdapException, IOException, CursorException {
        LdapNetworkConnection connection = ldapConnect();
        Entry entry = createAccountEntry(uid, cn, givenName, sn);
        try {
            connection.add(entry);
            display("Added LDAP account:\n" + entry);
        } catch (Exception e) {
            display("Error adding entry:\n" + entry + "\nError: " + e.getMessage());
            ldapDisconnect(connection);
            throw e;
        }
        ldapDisconnect(connection);
        return entry;
    }

    protected Entry createAccountEntry(String uid, String cn, String givenName, String sn) throws LdapException {
        Entry entry = new DefaultEntry(toAccountDn(uid), "objectclass", getLdapAccountObjectClass(), "uid", uid,
                "cn", cn, "givenName", givenName, "sn", sn);
        return entry;
    }

    protected Entry addLdapGroup(String cn, String description, String... memberDns)
            throws LdapException, IOException, CursorException {
        LdapNetworkConnection connection = ldapConnect();
        Entry entry = createGroupEntry(cn, description, memberDns);
        connection.add(entry);
        display("Added LDAP group:" + entry);
        ldapDisconnect(connection);
        return entry;
    }

    protected Entry createGroupEntry(String cn, String description, String... memberDns) throws LdapException {
        Entry entry = new DefaultEntry(toGroupDn(cn), "objectclass", getLdapGroupObjectClass(), "cn", cn,
                "description", description);
        if (memberDns != null && memberDns.length > 0) {
            entry.add(getLdapGroupMemberAttribute(), memberDns);
        }
        return entry;
    }

    protected void deleteLdapEntry(String dn) throws LdapException, IOException {
        LdapNetworkConnection connection = ldapConnect();
        connection.delete(dn);
        display("Deleted LDAP entry: " + dn);
        ldapDisconnect(connection);
    }

    /**
     * Silent delete. Used to clean up after previous test runs.
     */
    protected void cleanupDelete(String dn) throws LdapException, IOException, CursorException {
        LdapNetworkConnection connection = ldapConnect();
        Entry entry = getLdapEntry(connection, dn);
        if (entry != null) {
            connection.delete(dn);
            display("Cleaning up LDAP entry: " + dn);
        }
        ldapDisconnect(connection);
    }

    protected String toAccountDn(String username, String fullName) {
        return toAccountDn(username);
    }

    protected String toAccountDn(String username) {
        return "uid=" + username + "," + getPeopleLdapSuffix();
    }

    protected Rdn toAccountRdn(String username, String fullName) {
        try {
            return new Rdn(new Ava("uid", username));
        } catch (LdapInvalidDnException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

    protected String toGroupDn(String cn) {
        return "cn=" + cn + "," + getGroupsLdapSuffix();
    }

    protected String getAttributeAsString(Entry entry, String primaryIdentifierAttributeName)
            throws LdapInvalidAttributeValueException {
        if ("dn".equals(primaryIdentifierAttributeName)) {
            return entry.getDn().toString();
        } else {
            return entry.get(primaryIdentifierAttributeName).getString();
        }
    }

    protected LdapNetworkConnection ldapConnect() throws LdapException {
        return ldapConnect(getLdapBindDn(), getLdapBindPassword());
    }

    protected LdapNetworkConnection ldapConnect(String bindDn, String bindPassword) throws LdapException {
        LOGGER.trace("LDAP connect to {}:{} as {}", getLdapServerHost(), getLdapServerPort(), bindDn);
        LdapConnectionConfig config = new LdapConnectionConfig();
        config.setLdapHost(getLdapServerHost());
        config.setLdapPort(getLdapServerPort());

        if (useSsl()) {
            config.setUseSsl(true);
            TrustManager trustManager = new X509TrustManager() {
                public void checkClientTrusted(X509Certificate[] chain, String authType)
                        throws CertificateException {

                }

                public void checkServerTrusted(X509Certificate[] chain, String authType)
                        throws CertificateException {
                }

                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            };
            config.setTrustManagers(trustManager);
        }

        config.setBinaryAttributeDetector(binaryAttributeDetector);

        LdapNetworkConnection connection = new LdapNetworkConnection(config);
        boolean connected = connection.connect();
        if (!connected) {
            AssertJUnit.fail("Cannot connect to LDAP server " + getLdapServerHost() + ":" + getLdapServerPort());
        }
        LOGGER.trace("LDAP connected to {}:{}, executing bind as {}", getLdapServerHost(), getLdapServerPort(),
                bindDn);
        BindRequest bindRequest = new BindRequestImpl();
        bindRequest.setDn(new Dn(bindDn));
        bindRequest.setCredentials(bindPassword);
        bindRequest.setSimple(true);
        BindResponse bindResponse = connection.bind(bindRequest);
        if (bindResponse.getLdapResult().getResultCode() != ResultCodeEnum.SUCCESS) {
            throw new SecurityException(
                    "Bind as " + bindDn + " failed: " + bindResponse.getLdapResult().getDiagnosticMessage() + " ("
                            + bindResponse.getLdapResult().getResultCode() + ")");
        }
        LOGGER.trace("LDAP connected to {}:{}, bound as {}", getLdapServerHost(), getLdapServerPort(), bindDn);
        return connection;
    }

    protected void ldapDisconnect(LdapNetworkConnection connection) throws IOException {
        connection.close();
    }

    protected void assertAccountShadow(PrismObject<ShadowType> shadow, String dn) throws SchemaException {
        assertShadowCommon(shadow, null, dn, resourceType, getAccountObjectClass(), ciMatchingRule);
    }

    protected long roundTsDown(long ts) {
        return (((long) (ts / 1000)) * 1000);
    }

    protected long roundTsUp(long ts) {
        return (((long) (ts / 1000)) * 1000) + 1;
    }
}