package com.evolveum.midpoint.testing.conntest; /* * Copyright (c) 2010-2017 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.prism.query.builder.QueryBuilder; import com.evolveum.midpoint.util.DOMUtil; 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.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.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.Entry; 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.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.LdapNetworkConnection; import com.evolveum.midpoint.model.test.AbstractModelIntegrationTest; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.match.DistinguishedNameMatchingRule; 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.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.util.PrismAsserts; import com.evolveum.midpoint.schema.CapabilityUtil; 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.ResourceAttributeDefinition; import com.evolveum.midpoint.schema.processor.ResourceSchema; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.statistics.ConnectorOperationalStatus; import com.evolveum.midpoint.schema.util.ObjectQueryUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.test.util.Lsof; 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.CapabilitiesType; import com.evolveum.midpoint.xml.ns._public.common.common_3.CapabilityCollectionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; 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.UserType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CreateCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.DeleteCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ReadCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.UpdateCapabilityType; /** * @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"; protected static final String USER_BARBOSSA_PASSWORD_2 = "hereThereBeMonsters"; // 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"; protected static final File USER_LECHUCK_FILE = new File (COMMON_DIR, "user-lechuck.xml"); protected static final String USER_LECHUCK_OID = "0201583e-ffca-11e5-a949-affff1aa5a60"; protected static final String USER_LECHUCK_USERNAME = "lechuck"; protected static final String USER_LECHUCK_FULL_NAME = "LeChuck"; protected static final String LDAP_INETORGPERSON_OBJECTCLASS = "inetOrgPerson"; protected static final QName ASSOCIATION_GROUP_NAME = new QName(MidPointConstants.NS_RI, "group"); @Autowired(required = true) protected MatchingRuleRegistry matchingRuleRegistry; @Autowired protected ReconciliationTaskHandler reconciliationTaskHandler; protected ResourceType resourceType; protected PrismObject<ResourceType> resource; protected MatchingRule<String> dnMatchingRule; protected MatchingRule<String> ciMatchingRule; private static String stopCommand; protected ObjectClassComplexTypeDefinition accountObjectClassDefinition; protected DefaultConfigurableBinaryAttributeDetector binaryAttributeDetector = new DefaultConfigurableBinaryAttributeDetector(); protected Lsof lsof; @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 String getResourceNamespace() { return MidPointConstants.NS_RI; } 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 boolean isUsingGroupShortcutAttribute() { return true; } protected String getScriptDirectoryName() { return "/opt/Bamboo/local/conntest"; } protected boolean isImportResourceAtInit() { return true; } protected boolean allowDuplicateSearchResults() { return false; } protected boolean isGroupMemberMandatory() { return true; } protected boolean isAssertOpenFiles() { return false; } protected QName getAssociationGroupName() { return new QName(MidPointConstants.NS_RI, "group"); } @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, 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, initResult); repoAddObjectFromFile(ROLE_SUPERUSER_FILE, 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); dnMatchingRule = matchingRuleRegistry.getMatchingRule(DistinguishedNameMatchingRule.NAME, DOMUtil.XSD_STRING); logTrustManagers(); if (isAssertOpenFiles()) { lsof = new Lsof(TestUtil.getPid()); } } @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); if (isAssertOpenFiles()) { // Set lsof baseline only after the first connection. // We will have more reasonable number here. lsof.rememberBaseline(); display("lsof baseline", lsof); } } @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()); assertStableSystem(); } @Test public void test030Capabilities() throws Exception { final String TEST_NAME = "test030Capabilities"; TestUtil.displayTestTile(this, TEST_NAME); CapabilitiesType capabilities = resourceType.getCapabilities(); display("Resource capabilities", capabilities); assertNotNull("Null capabilities", capabilities); CapabilityCollectionType nativeCapabilitiesCollectionType = capabilities.getNative(); assertNotNull("Null native capabilities type", nativeCapabilitiesCollectionType); List<Object> nativeCapabilities = nativeCapabilitiesCollectionType.getAny(); assertNotNull("Null native capabilities", nativeCapabilities); assertFalse("Empty native capabilities", nativeCapabilities.isEmpty()); assertCapability(nativeCapabilities, ReadCapabilityType.class); assertCapability(nativeCapabilities, CreateCapabilityType.class); assertCapability(nativeCapabilities, UpdateCapabilityType.class); assertCapability(nativeCapabilities, DeleteCapabilityType.class); // TODO: assert password capability. Check password readability. ActivationCapabilityType activationCapabilityType = CapabilityUtil.getCapability(nativeCapabilities, ActivationCapabilityType.class); assertActivationCapability(activationCapabilityType); assertAdditionalCapabilities(nativeCapabilities); assertStableSystem(); } protected void assertActivationCapability(ActivationCapabilityType activationCapabilityType) { // for subclasses } protected void assertAdditionalCapabilities(List<Object> nativeCapabilities) { // for subclasses } protected <C extends CapabilityType> void assertCapability(List<Object> capabilities, Class<C> capabilityClass) { C capability = CapabilityUtil.getCapability(capabilities, capabilityClass); assertNotNull("No "+capabilityClass.getSimpleName()+" capability", capability); assertTrue("Capability "+capabilityClass.getSimpleName()+" is disabled", CapabilityUtil.isCapabilityEnabled(capability)); } protected <T> ObjectFilter createAttributeFilter(String attrName, T attrVal) throws SchemaException { ResourceAttributeDefinition ldapAttrDef = accountObjectClassDefinition.findAttributeDefinition(attrName); return QueryBuilder.queryFor(ShadowType.class, prismContext) .itemWithDef(ldapAttrDef, ShadowType.F_ATTRIBUTES, ldapAttrDef.getName()).eq(attrVal) .buildFilter(); } 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); if (expectedSize != foundObjects.size()) { if (foundObjects.size() < 10) { display("Found objects", foundObjects); AssertJUnit.fail("Unexpected number of accounts. Expected "+expectedSize+", found "+foundObjects.size()+": "+foundObjects); } else { AssertJUnit.fail("Unexpected number of accounts. Expected "+expectedSize+", found "+foundObjects.size()+" (too many to display)"); } } 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 getLdapAccountByCn(null, cn); } protected Entry getLdapAccountByCn(UserLdapConnectionConfig config, String cn) throws LdapException, IOException, CursorException { return searchLdapAccount(config, "(cn="+cn+")"); } protected Entry searchLdapAccount(String filter) throws LdapException, IOException, CursorException { return searchLdapAccount(null, filter); } protected Entry searchLdapAccount(UserLdapConnectionConfig config, String filter) throws LdapException, IOException, CursorException { LdapNetworkConnection connection = ldapConnect(config); List<Entry> entries = ldapSearch(config, 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 assertNoLdapGroup(String cn) throws LdapException, IOException, CursorException { LdapNetworkConnection connection = ldapConnect(); List<Entry> entries = ldapSearch(connection, "(&(cn="+cn+")(objectClass="+getLdapGroupObjectClass()+"))"); ldapDisconnect(connection); assertEquals("Unexpected LDAP group "+cn+": "+entries, 0, entries.size()); } 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 assertNoAttribute(Entry entry, String attrName) throws LdapInvalidAttributeValueException { String dn = entry.getDn().toString(); Attribute ldapAttribute = entry.get(attrName); if (ldapAttribute != null) { AssertJUnit.fail("Unexpected attribute "+attrName+" in "+dn+": "+ldapAttribute); } } protected void assertAttributeContains(Entry entry, String attrName, String expectedValue) throws LdapInvalidAttributeValueException, SchemaException { assertAttributeContains(entry, attrName, expectedValue, null); } protected void assertAttributeContains(Entry entry, String attrName, String expectedValue, MatchingRule<String> matchingRule) throws LdapInvalidAttributeValueException, SchemaException { 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 (matchingRule == null) { if (expectedValue.equals(value.getString())) { return; } } else { if (matchingRule.match(expectedValue, 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, SchemaException { assertAttributeNotContains(entry, attrName, expectedValue, null); } protected void assertAttributeNotContains(Entry entry, String attrName, String expectedValue, MatchingRule<String> matchingRule) throws LdapInvalidAttributeValueException, SchemaException { 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 (matchingRule == null) { if (expectedValue.equals(value.getString())) { AssertJUnit.fail("Attribute "+attrName+" in "+dn+" contains value " + expectedValue + ", but it should not have it"); } } else { if (matchingRule.match(expectedValue, 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 assertNoEntry(String dn) throws LdapException, IOException, CursorException { Entry entry = getLdapEntry(dn); assertNull("Expected no entry "+dn+", but found "+entry, entry); } protected void assertLdapGroupMember(Entry accountEntry, String groupName) throws LdapException, IOException, CursorException, SchemaException { assertLdapGroupMember(accountEntry.getDn().toString(), groupName); } protected void assertLdapGroupMember(String accountEntryDn, String groupName) throws LdapException, IOException, CursorException, SchemaException { Entry groupEntry = getLdapGroupByName(groupName); assertAttributeContains(groupEntry, getLdapGroupMemberAttribute(), accountEntryDn, dnMatchingRule); } protected void assertLdapNoGroupMember(Entry accountEntry, String groupName) throws LdapException, IOException, CursorException, SchemaException { assertLdapNoGroupMember(accountEntry.getDn().toString(), groupName); } protected void assertLdapNoGroupMember(String accountEntryDn, String groupName) throws LdapException, IOException, CursorException, SchemaException { Entry groupEntry = getLdapGroupByName(groupName); assertAttributeNotContains(groupEntry, getLdapGroupMemberAttribute(), accountEntryDn, dnMatchingRule); } protected List<Entry> ldapSearch(LdapNetworkConnection connection, String filter) throws LdapException, CursorException { return ldapSearch(null, connection, filter); } protected List<Entry> ldapSearch(UserLdapConnectionConfig config, LdapNetworkConnection connection, String filter) throws LdapException, CursorException { String baseContext = getLdapSuffix(); if (config != null && config.getBaseContext() != null) { baseContext = config.getBaseContext(); } return ldapSearch(connection, baseContext, filter, SearchScope.SUBTREE, "*", "isMemberOf", "memberof", "isMemberOf", 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); } } searchCursor.close(); } catch (IOException e) { throw new IllegalStateException("IO Error: "+e.getMessage(), e); } 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 { assertLdapPassword(null, entry, password); } protected void assertLdapPassword(UserLdapConnectionConfig config, Entry entry, String password) throws LdapException, IOException, CursorException { LdapNetworkConnection conn = ldapConnect(config, entry.getDn().toString(), password); assertTrue("Not connected", conn.isConnected()); assertTrue("Not authenticated", conn.isAuthenticated()); ldapDisconnect(conn); } 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); LOGGER.trace("Adding LDAP entry:\n{}", entry); 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 (isGroupMemberMandatory() && 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 { cleanupDelete(null, dn); } /** * Silent delete. Used to clean up after previous test runs. */ protected void cleanupDelete(UserLdapConnectionConfig config, String dn) throws LdapException, IOException, CursorException { LdapNetworkConnection connection = ldapConnect(config); 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, IOException { return ldapConnect(getLdapBindDn(), getLdapBindPassword()); } protected LdapNetworkConnection ldapConnect(String bindDn, String bindPassword) throws LdapException, IOException { UserLdapConnectionConfig config = new UserLdapConnectionConfig(); config.setLdapHost(getLdapServerHost()); config.setLdapPort(getLdapServerPort()); config.setBindDn(bindDn); config.setBindPassword(bindPassword); return ldapConnect(config); } protected LdapNetworkConnection ldapConnect(UserLdapConnectionConfig config, String bindDn, String bindPassword) throws LdapException, IOException { if (config == null) { config = new UserLdapConnectionConfig(); config.setLdapHost(getLdapServerHost()); config.setLdapPort(getLdapServerPort()); } config.setBindDn(bindDn); config.setBindPassword(bindPassword); return ldapConnect(config); } protected LdapNetworkConnection ldapConnect(UserLdapConnectionConfig config) throws LdapException, IOException { if (config == null) { config = new UserLdapConnectionConfig(); config.setLdapHost(getLdapServerHost()); config.setLdapPort(getLdapServerPort()); config.setBindDn(getLdapBindDn()); config.setBindPassword(getLdapBindPassword()); } LOGGER.trace("LDAP connect to {}:{} as {}", config.getLdapHost(), config.getLdapPort(), config.getBindDn()); 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 "+config.getLdapHost()+":"+config.getLdapPort()); } LOGGER.trace("LDAP connected to {}:{}, executing bind as {}", config.getLdapHost(), config.getLdapPort(), config.getBindDn()); BindRequest bindRequest = new BindRequestImpl(); bindRequest.setDn(new Dn(config.getBindDn())); bindRequest.setCredentials(config.getBindPassword()); bindRequest.setSimple(true); BindResponse bindResponse = connection.bind(bindRequest); if (bindResponse.getLdapResult().getResultCode() != ResultCodeEnum.SUCCESS) { ldapDisconnect(connection); throw new SecurityException("Bind as "+config.getBindDn()+" failed: "+bindResponse.getLdapResult().getDiagnosticMessage()+" ("+bindResponse.getLdapResult().getResultCode()+")"); } LOGGER.trace("LDAP connected to {}:{}, bound as {}", config.getLdapHost(), config.getLdapPort(), config.getBindDn()); return connection; } protected void ldapDisconnect(LdapNetworkConnection connection) throws IOException { LOGGER.trace("LDAP disconnect {}", connection); connection.close(); } protected void assertAccountShadow(PrismObject<ShadowType> shadow, String dn) throws SchemaException { assertShadowCommon(shadow, null, dn, resourceType, getAccountObjectClass(), ciMatchingRule, false); } protected void assertAccountRepoShadow(PrismObject<ShadowType> shadow, String dn) throws SchemaException { assertShadowCommon(shadow, null, dnMatchingRule.normalize(dn), resourceType, getAccountObjectClass(), ciMatchingRule, false); } protected void assertGroupShadow(PrismObject<ShadowType> shadow, String dn) throws SchemaException { assertShadowCommon(shadow, null, dn, resourceType, getGroupObjectClass(), ciMatchingRule, false, true); } protected long roundTsDown(long ts) { return (((long)(ts/1000))*1000); } protected long roundTsUp(long ts) { return (((long)(ts/1000))*1000)+1; } protected void assertStableSystem() throws NumberFormatException, IOException, InterruptedException { if (isAssertOpenFiles()) { lsof.assertStable(); } } protected void assertLdapConnectorInstances(int expectedConnectorInstancesShortcut, int expectedConnectorInstancesNoShortcut) throws NumberFormatException, IOException, InterruptedException, SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException { if (isUsingGroupShortcutAttribute()) { assertLdapConnectorInstances(expectedConnectorInstancesShortcut); } else { assertLdapConnectorInstances(expectedConnectorInstancesNoShortcut); } } protected void assertLdapConnectorInstances(int expectedConnectorInstances) throws NumberFormatException, IOException, InterruptedException, SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException { OperationResult result = new OperationResult(AbstractLdapTest.class.getName() + ".assertLdapConnectorInstances"); ConnectorOperationalStatus stats = provisioningService.getConnectorOperationalStatus(getResourceOid(), result); display("Resource connector stats", stats); result.computeStatus(); TestUtil.assertSuccess(result); assertEquals("Unexpected number of LDAP connector instances", expectedConnectorInstances, stats.getPoolStatusNumIdle() + stats.getPoolStatusNumActive()); if (!isAssertOpenFiles()) { return; } if (expectedConnectorInstances == 1) { assertStableSystem(); } else { lsof.assertFdIncrease((expectedConnectorInstances - 1) * getNumberOfFdsPerLdapConnectorInstance()); } } protected int getNumberOfFdsPerLdapConnectorInstance() { return 7; } }