blob: 843fa42dab3f6787664e108a8a9c8554c872cecf [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.syncope.fit.core;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.ws.rs.core.Response;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.syncope.client.lib.SyncopeClient;
import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.request.ResourceDR;
import org.apache.syncope.common.lib.request.PasswordPatch;
import org.apache.syncope.common.lib.request.UserUR;
import org.apache.syncope.common.lib.policy.PullPolicyTO;
import org.apache.syncope.common.lib.request.AnyCR;
import org.apache.syncope.common.lib.request.AnyObjectCR;
import org.apache.syncope.common.lib.request.GroupCR;
import org.apache.syncope.common.lib.request.UserCR;
import org.apache.syncope.common.lib.to.TaskTO;
import org.apache.syncope.common.lib.to.AnyObjectTO;
import org.apache.syncope.common.lib.Attr;
import org.apache.syncope.common.lib.to.ConnInstanceTO;
import org.apache.syncope.common.lib.to.ConnObjectTO;
import org.apache.syncope.common.lib.to.MembershipTO;
import org.apache.syncope.common.lib.to.PagedResult;
import org.apache.syncope.common.lib.to.ResourceTO;
import org.apache.syncope.common.lib.to.GroupTO;
import org.apache.syncope.common.lib.to.ProvisionTO;
import org.apache.syncope.common.lib.to.ItemTO;
import org.apache.syncope.common.lib.to.PullTaskTO;
import org.apache.syncope.common.lib.to.ExecTO;
import org.apache.syncope.common.lib.to.ImplementationTO;
import org.apache.syncope.common.lib.to.ProvisioningResult;
import org.apache.syncope.common.lib.to.RemediationTO;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.CipherAlgorithm;
import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.common.lib.types.ConnConfProperty;
import org.apache.syncope.common.lib.types.ConnectorCapability;
import org.apache.syncope.common.lib.types.ImplementationEngine;
import org.apache.syncope.common.lib.types.PolicyType;
import org.apache.syncope.common.lib.types.ExecStatus;
import org.apache.syncope.common.lib.types.IdMImplementationType;
import org.apache.syncope.common.lib.types.IdRepoImplementationType;
import org.apache.syncope.common.lib.types.MatchingRule;
import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
import org.apache.syncope.common.lib.types.PullMode;
import org.apache.syncope.common.lib.types.ResourceOperation;
import org.apache.syncope.common.lib.types.TaskType;
import org.apache.syncope.common.lib.types.UnmatchingRule;
import org.apache.syncope.common.rest.api.RESTHeaders;
import org.apache.syncope.common.rest.api.beans.AnyQuery;
import org.apache.syncope.common.rest.api.beans.ReconQuery;
import org.apache.syncope.common.rest.api.beans.RemediationQuery;
import org.apache.syncope.common.rest.api.beans.TaskQuery;
import org.apache.syncope.common.rest.api.service.ConnectorService;
import org.apache.syncope.common.rest.api.service.TaskService;
import org.apache.syncope.core.provisioning.java.pushpull.DBPasswordPullActions;
import org.apache.syncope.core.provisioning.java.pushpull.LDAPPasswordPullActions;
import org.apache.syncope.core.spring.security.Encryptor;
import org.apache.syncope.fit.ElasticsearchDetector;
import org.apache.syncope.fit.FlowableDetector;
import org.apache.syncope.fit.core.reference.TestPullActions;
import org.identityconnectors.framework.common.objects.Name;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
public class PullTaskITCase extends AbstractTaskITCase {
@BeforeAll
public static void testPullActionsSetup() {
ImplementationTO pullActions = null;
try {
pullActions = implementationService.read(
IdMImplementationType.PULL_ACTIONS, TestPullActions.class.getSimpleName());
} catch (SyncopeClientException e) {
if (e.getType().getResponseStatus() == Response.Status.NOT_FOUND) {
pullActions = new ImplementationTO();
pullActions.setKey(TestPullActions.class.getSimpleName());
pullActions.setEngine(ImplementationEngine.JAVA);
pullActions.setType(IdMImplementationType.PULL_ACTIONS);
pullActions.setBody(TestPullActions.class.getName());
Response response = implementationService.create(pullActions);
pullActions = implementationService.read(
pullActions.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
assertNotNull(pullActions);
}
}
assertNotNull(pullActions);
PullTaskTO pullTask = taskService.read(TaskType.PULL, PULL_TASK_KEY, true);
pullTask.getActions().add(pullActions.getKey());
taskService.update(TaskType.PULL, pullTask);
}
@Test
public void getPullActionsClasses() {
Set<String> actions = syncopeService.platform().
getJavaImplInfo(IdMImplementationType.PULL_ACTIONS).get().getClasses();
assertNotNull(actions);
assertFalse(actions.isEmpty());
}
@Test
public void list() {
PagedResult<PullTaskTO> tasks = taskService.search(new TaskQuery.Builder(TaskType.PULL).build());
assertFalse(tasks.getResult().isEmpty());
tasks.getResult().stream().
filter(task -> (!(task instanceof PullTaskTO))).
forEach(item -> fail("This should not happen"));
}
@Test
public void create() {
PullTaskTO task = new PullTaskTO();
task.setName("Test create Pull");
task.setDestinationRealm("/");
task.setResource(RESOURCE_NAME_WS2);
task.setPullMode(PullMode.FULL_RECONCILIATION);
UserTO userTemplate = new UserTO();
userTemplate.getResources().add(RESOURCE_NAME_WS2);
userTemplate.getMemberships().add(new MembershipTO.Builder("f779c0d4-633b-4be5-8f57-32eb478a3ca5").build());
task.getTemplates().put(AnyTypeKind.USER.name(), userTemplate);
GroupTO groupTemplate = new GroupTO();
groupTemplate.getResources().add(RESOURCE_NAME_LDAP);
task.getTemplates().put(AnyTypeKind.GROUP.name(), groupTemplate);
Response response = taskService.create(TaskType.PULL, task);
PullTaskTO actual = getObject(response.getLocation(), TaskService.class, PullTaskTO.class);
assertNotNull(actual);
task = taskService.read(TaskType.PULL, actual.getKey(), true);
assertNotNull(task);
assertEquals(actual.getKey(), task.getKey());
assertEquals(actual.getJobDelegate(), task.getJobDelegate());
assertEquals(userTemplate, task.getTemplates().get(AnyTypeKind.USER.name()));
assertEquals(groupTemplate, task.getTemplates().get(AnyTypeKind.GROUP.name()));
}
@Test
public void fromCSV() throws Exception {
assumeFalse(ElasticsearchDetector.isElasticSearchEnabled(syncopeService));
removeTestUsers();
// Attemp to reset CSV content
Properties props = new Properties();
InputStream propStream = null;
InputStream srcStream = null;
OutputStream dstStream = null;
try {
propStream = getClass().getResourceAsStream("/test.properties");
props.load(propStream);
srcStream = new FileInputStream(props.getProperty("test.csv.src"));
dstStream = new FileOutputStream(props.getProperty("test.csv.dst"));
IOUtils.copy(srcStream, dstStream);
} catch (IOException e) {
fail(e::getMessage);
} finally {
if (propStream != null) {
propStream.close();
}
if (srcStream != null) {
srcStream.close();
}
if (dstStream != null) {
dstStream.close();
}
}
// -----------------------------
// Create a new user ... it should be updated applying pull policy
// -----------------------------
UserCR inUserRC = new UserCR();
inUserRC.setRealm(SyncopeConstants.ROOT_REALM);
inUserRC.setPassword("password123");
String userName = "test9";
inUserRC.setUsername(userName);
inUserRC.getPlainAttrs().add(attr("firstname", "nome9"));
inUserRC.getPlainAttrs().add(attr("surname", "cognome"));
inUserRC.getPlainAttrs().add(attr("ctype", "a type"));
inUserRC.getPlainAttrs().add(attr("fullname", "nome cognome"));
inUserRC.getPlainAttrs().add(attr("userId", "puccini@syncope.apache.org"));
inUserRC.getPlainAttrs().add(attr("email", "puccini@syncope.apache.org"));
inUserRC.getAuxClasses().add("csv");
UserTO inUserTO = createUser(inUserRC).getEntity();
assertNotNull(inUserTO);
assertFalse(inUserTO.getResources().contains(RESOURCE_NAME_CSV));
// -----------------------------
try {
int usersPre = userService.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
page(1).size(1).build()).getTotalCount();
assertNotNull(usersPre);
ExecTO exec = execProvisioningTask(taskService, TaskType.PULL, PULL_TASK_KEY, MAX_WAIT_SECONDS, false);
assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(exec.getStatus()));
LOG.debug("Execution of task {}:\n{}", PULL_TASK_KEY, exec);
// check for pull results
int usersPost = userService.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
page(1).size(1).build()).getTotalCount();
assertNotNull(usersPost);
assertEquals(usersPre + 8, usersPost);
// after execution of the pull task the user data should have been pulled from CSV
// and processed by user template
UserTO userTO = userService.read(inUserTO.getKey());
assertNotNull(userTO);
assertEquals(userName, userTO.getUsername());
assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService)
? "active" : "created", userTO.getStatus());
assertEquals("test9@syncope.apache.org", userTO.getPlainAttr("email").get().getValues().get(0));
assertEquals("test9@syncope.apache.org", userTO.getPlainAttr("userId").get().getValues().get(0));
assertTrue(Integer.valueOf(userTO.getPlainAttr("fullname").get().getValues().get(0)) <= 10);
assertTrue(userTO.getResources().contains(RESOURCE_NAME_TESTDB));
assertTrue(userTO.getResources().contains(RESOURCE_NAME_WS2));
// Matching --> Update (no link)
assertFalse(userTO.getResources().contains(RESOURCE_NAME_CSV));
// check for user template
userTO = userService.read("test7");
assertNotNull(userTO);
assertEquals("TYPE_OTHER", userTO.getPlainAttr("ctype").get().getValues().get(0));
assertEquals(3, userTO.getResources().size());
assertTrue(userTO.getResources().contains(RESOURCE_NAME_TESTDB));
assertTrue(userTO.getResources().contains(RESOURCE_NAME_WS2));
assertEquals(1, userTO.getMemberships().size());
assertEquals("f779c0d4-633b-4be5-8f57-32eb478a3ca5", userTO.getMemberships().get(0).getGroupKey());
// Unmatching --> Assign (link) - SYNCOPE-658
assertTrue(userTO.getResources().contains(RESOURCE_NAME_CSV));
assertEquals(1, userTO.getDerAttrs().stream().
filter(attrTO -> "csvuserid".equals(attrTO.getSchema())).count());
userTO = userService.read("test8");
assertNotNull(userTO);
assertEquals("TYPE_8", userTO.getPlainAttr("ctype").get().getValues().get(0));
// Check for ignored user - SYNCOPE-663
try {
userService.read("test2");
fail("This should not happen");
} catch (SyncopeClientException e) {
assertEquals(Response.Status.NOT_FOUND, e.getType().getResponseStatus());
}
// Check for issue 215:
// * expected disabled user test1
// * expected enabled user test3
userTO = userService.read("test1");
assertNotNull(userTO);
assertEquals("suspended", userTO.getStatus());
userTO = userService.read("test3");
assertNotNull(userTO);
assertEquals("active", userTO.getStatus());
Set<String> otherPullTaskKeys = Set.of(
"feae4e57-15ca-40d9-b973-8b9015efca49",
"55d5e74b-497e-4bc0-9156-73abef4b9adc");
execProvisioningTasks(taskService, TaskType.PULL, otherPullTaskKeys, MAX_WAIT_SECONDS, false);
// Matching --> UNLINK
assertFalse(userService.read("test9").getResources().contains(RESOURCE_NAME_CSV));
assertFalse(userService.read("test7").getResources().contains(RESOURCE_NAME_CSV));
} finally {
removeTestUsers();
}
}
@Test
public void dryRun() {
ExecTO execution = execProvisioningTask(taskService, TaskType.PULL, PULL_TASK_KEY, MAX_WAIT_SECONDS, true);
assertEquals("SUCCESS", execution.getStatus());
}
@Test
public void reconcileFromDB() {
UserTO userTO = null;
JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
try {
ExecTO execution = execProvisioningTask(
taskService, TaskType.PULL, "83f7e85d-9774-43fe-adba-ccd856312994", MAX_WAIT_SECONDS, false);
assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(execution.getStatus()));
userTO = userService.read("testuser1");
assertNotNull(userTO);
assertEquals("reconciled@syncope.apache.org", userTO.getPlainAttr("userId").get().getValues().get(0));
assertEquals("suspended", userTO.getStatus());
// enable user on external resource
jdbcTemplate.execute("UPDATE TEST SET status=TRUE WHERE id='testuser1'");
// re-execute the same PullTask: now user must be active
execution = execProvisioningTask(
taskService, TaskType.PULL, "83f7e85d-9774-43fe-adba-ccd856312994", MAX_WAIT_SECONDS, false);
assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(execution.getStatus()));
userTO = userService.read("testuser1");
assertNotNull(userTO);
assertEquals("active", userTO.getStatus());
} finally {
jdbcTemplate.execute("UPDATE TEST SET status=FALSE WHERE id='testuser1'");
if (userTO != null) {
userService.delete(userTO.getKey());
}
}
}
@Test
public void reconcileFromLDAP() {
// First of all, clear any potential conflict with existing user / group
ldapCleanup();
// 0. pull
ExecTO execution = execProvisioningTask(
taskService, TaskType.PULL, "1e419ca4-ea81-4493-a14f-28b90113686d", MAX_WAIT_SECONDS, false);
// 1. verify execution status
assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(execution.getStatus()));
// SYNCOPE-898
PullTaskTO task = taskService.read(TaskType.PULL, "1e419ca4-ea81-4493-a14f-28b90113686d", false);
assertEquals(SyncopeConstants.ROOT_REALM, task.getDestinationRealm());
// 2. verify that pulled group is found
PagedResult<GroupTO> matchingGroups = groupService.search(new AnyQuery.Builder().realm(
SyncopeConstants.ROOT_REALM).
fiql(SyncopeClient.getGroupSearchConditionBuilder().is("name").equalTo("testLDAPGroup").query()).
build());
assertNotNull(matchingGroups);
assertEquals(1, matchingGroups.getResult().size());
assertEquals(SyncopeConstants.ROOT_REALM, matchingGroups.getResult().get(0).getRealm());
// 3. verify that pulled user is found
PagedResult<UserTO> matchingUsers = userService.search(
new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
fiql(SyncopeClient.getUserSearchConditionBuilder().is("username").equalTo("pullFromLDAP").
query()).
build());
assertNotNull(matchingUsers);
assertEquals(1, matchingUsers.getResult().size());
// SYNCOPE-898
assertEquals("/odd", matchingUsers.getResult().get(0).getRealm());
// Check for SYNCOPE-436
assertEquals("pullFromLDAP",
matchingUsers.getResult().get(0).getVirAttr("virtualReadOnly").get().getValues().get(0));
// Check for SYNCOPE-270
assertNotNull(matchingUsers.getResult().get(0).getPlainAttr("obscure"));
// Check for SYNCOPE-123
assertNotNull(matchingUsers.getResult().get(0).getPlainAttr("photo"));
// Check for SYNCOPE-1343
assertEquals("odd", matchingUsers.getResult().get(0).getPlainAttr("title").get().getValues().get(0));
PagedResult<UserTO> matchByLastChangeContext = userService.search(
new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
fiql(SyncopeClient.getUserSearchConditionBuilder().is("lastChangeContext").
equalTo("*PullTask " + task.getKey() + "*").query()).
build());
assertNotNull(matchByLastChangeContext);
assertNotEquals(0, matchByLastChangeContext.getTotalCount());
GroupTO groupTO = matchingGroups.getResult().get(0);
assertNotNull(groupTO);
assertEquals("testLDAPGroup", groupTO.getName());
assertTrue(groupTO.getLastChangeContext().contains("PullTask " + task.getKey()));
assertEquals("true", groupTO.getPlainAttr("show").get().getValues().get(0));
assertEquals(matchingUsers.getResult().get(0).getKey(), groupTO.getUserOwner());
assertNull(groupTO.getGroupOwner());
// SYNCOPE-1343, set value title to null on LDAP
ConnObjectTO userConnObject = resourceService.readConnObject(
RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), matchingUsers.getResult().get(0).getKey());
assertNotNull(userConnObject);
assertEquals("odd", userConnObject.getAttr("title").get().getValues().get(0));
Attr userDn = userConnObject.getAttr(Name.NAME).get();
updateLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD,
userDn.getValues().get(0), Collections.singletonMap("title", (String) null));
// SYNCOPE-317
execProvisioningTask(
taskService, TaskType.PULL, "1e419ca4-ea81-4493-a14f-28b90113686d", MAX_WAIT_SECONDS, false);
// 4. verify that LDAP group membership is pulled as Syncope membership
AtomicReference<Integer> numMembers = new AtomicReference<>();
await().atMost(MAX_WAIT_SECONDS, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).until(() -> {
try {
PagedResult<UserTO> members = userService.search(
new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
fiql(SyncopeClient.getUserSearchConditionBuilder().inGroups(groupTO.getKey()).query()).
build());
numMembers.set(members.getResult().size());
return !members.getResult().isEmpty();
} catch (Exception e) {
return false;
}
});
assertEquals(1, numMembers.get());
// SYNCOPE-1343, verify that the title attribute has been reset
matchingUsers = userService.search(
new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
fiql(SyncopeClient.getUserSearchConditionBuilder().is("username").equalTo("pullFromLDAP").
query()).
build());
assertNull(matchingUsers.getResult().get(0).getPlainAttr("title").orElse(null));
// SYNCOPE-1356 remove group membership from LDAP, pull and check in Syncope
ConnObjectTO groupConnObject = resourceService.readConnObject(
RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), matchingGroups.getResult().get(0).getKey());
assertNotNull(groupConnObject);
Attr groupDn = groupConnObject.getAttr(Name.NAME).get();
updateLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD,
groupDn.getValues().get(0), Map.of("uniquemember", "uid=admin,ou=system"));
execProvisioningTask(
taskService, TaskType.PULL, "1e419ca4-ea81-4493-a14f-28b90113686d", MAX_WAIT_SECONDS, false);
await().atMost(MAX_WAIT_SECONDS, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).until(() -> {
try {
return userService.search(
new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
fiql(SyncopeClient.getUserSearchConditionBuilder().inGroups(groupTO.getKey()).query()).
build()).getResult().isEmpty();
} catch (Exception e) {
return false;
}
});
}
@Test
public void reconcileFromScriptedSQL() throws IOException {
// 0. reset sync token and set MappingItemTransformer
ResourceTO resource = resourceService.read(RESOURCE_NAME_DBSCRIPTED);
ResourceTO originalResource = SerializationUtils.clone(resource);
ProvisionTO provision = resource.getProvision(PRINTER).get();
assertNotNull(provision);
ImplementationTO transformer = null;
try {
transformer = implementationService.read(
IdRepoImplementationType.ITEM_TRANSFORMER, "PrefixItemTransformer");
} catch (SyncopeClientException e) {
if (e.getType().getResponseStatus() == Response.Status.NOT_FOUND) {
transformer = new ImplementationTO();
transformer.setKey("PrefixItemTransformer");
transformer.setEngine(ImplementationEngine.GROOVY);
transformer.setType(IdRepoImplementationType.ITEM_TRANSFORMER);
transformer.setBody(IOUtils.toString(
getClass().getResourceAsStream("/PrefixItemTransformer.groovy"), StandardCharsets.UTF_8));
Response response = implementationService.create(transformer);
transformer = implementationService.read(
transformer.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
assertNotNull(transformer.getKey());
}
}
assertNotNull(transformer);
ItemTO mappingItem = provision.getMapping().getItems().stream().
filter(object -> "location".equals(object.getIntAttrName())).findFirst().get();
assertNotNull(mappingItem);
mappingItem.getTransformers().clear();
mappingItem.getTransformers().add(transformer.getKey());
final String prefix = "PREFIX_";
try {
resourceService.update(resource);
resourceService.removeSyncToken(resource.getKey(), provision.getAnyType());
// insert a deleted record in the external resource (SYNCOPE-923), which will be returned
// as sync event prior to the CREATE_OR_UPDATE events generated by the actions below (before pull)
JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
jdbcTemplate.update(
"INSERT INTO TESTPRINTER (id, printername, location, deleted, lastmodification) VALUES (?,?,?,?,?)",
UUID.randomUUID().toString(), "Mysterious Printer", "Nowhere", true, new Date());
// 1. create printer on external resource
AnyObjectCR anyObjectCR = AnyObjectITCase.getSample("pull");
AnyObjectTO anyObjectTO = createAnyObject(anyObjectCR).getEntity();
assertNotNull(anyObjectTO);
String originalLocation = anyObjectTO.getPlainAttr("location").get().getValues().get(0);
assertFalse(originalLocation.startsWith(prefix));
// 2. verify that PrefixMappingItemTransformer was applied during propagation
// (location starts with given prefix on external resource)
ConnObjectTO connObjectTO = resourceService.readConnObject(
RESOURCE_NAME_DBSCRIPTED, anyObjectTO.getType(), anyObjectTO.getKey());
assertFalse(anyObjectTO.getPlainAttr("location").get().getValues().get(0).startsWith(prefix));
assertTrue(connObjectTO.getAttr("LOCATION").get().getValues().get(0).startsWith(prefix));
// 3. unlink any existing printer and delete from Syncope (printer is now only on external resource)
PagedResult<AnyObjectTO> matchingPrinters = anyObjectService.search(
new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
fiql(SyncopeClient.getAnyObjectSearchConditionBuilder(PRINTER).
is("location").equalTo("pull*").query()).build());
assertTrue(matchingPrinters.getSize() > 0);
for (AnyObjectTO printer : matchingPrinters.getResult()) {
anyObjectService.deassociate(new ResourceDR.Builder().key(printer.getKey()).
action(ResourceDeassociationAction.UNLINK).resource(RESOURCE_NAME_DBSCRIPTED).build());
anyObjectService.delete(printer.getKey());
}
// ensure that the pull task does not have the DELETE capability (SYNCOPE-923)
PullTaskTO pullTask = taskService.read(TaskType.PULL, "30cfd653-257b-495f-8665-281281dbcb3d", false);
assertNotNull(pullTask);
assertFalse(pullTask.isPerformDelete());
// 4. pull
execProvisioningTask(taskService, TaskType.PULL, pullTask.getKey(), MAX_WAIT_SECONDS, false);
if (ElasticsearchDetector.isElasticSearchEnabled(syncopeService)) {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
// ignore
}
}
// 5. verify that printer was re-created in Syncope (implies that location does not start with given prefix,
// hence PrefixItemTransformer was applied during pull)
matchingPrinters = anyObjectService.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
fiql(SyncopeClient.getAnyObjectSearchConditionBuilder(PRINTER).
is("location").equalTo("pull*").query()).build());
assertTrue(matchingPrinters.getSize() > 0);
// 6. verify that synctoken was updated
assertNotNull(resourceService.read(RESOURCE_NAME_DBSCRIPTED).
getProvision(anyObjectTO.getType()).get().getSyncToken());
} finally {
resourceService.update(originalResource);
}
}
@Test
public void filteredReconciliation() throws IOException {
String user1OnTestPull = UUID.randomUUID().toString();
String user2OnTestPull = UUID.randomUUID().toString();
JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
PullTaskTO task = null;
UserTO userTO = null;
try {
// 1. create 2 users on testpull
jdbcTemplate.execute("INSERT INTO testpull VALUES ("
+ '\'' + user1OnTestPull + "', 'user1', 'Doe', false, 'mail1@apache.org', NULL)");
jdbcTemplate.execute("INSERT INTO testpull VALUES ("
+ '\'' + user2OnTestPull + "', 'user2', 'Rossi', false, 'mail2@apache.org', NULL)");
// 2. create new pull task for test-db, with reconciliation filter (surname 'Rossi')
ImplementationTO reconFilterBuilder = new ImplementationTO();
reconFilterBuilder.setKey("TestReconFilterBuilder");
reconFilterBuilder.setEngine(ImplementationEngine.GROOVY);
reconFilterBuilder.setType(IdMImplementationType.RECON_FILTER_BUILDER);
reconFilterBuilder.setBody(IOUtils.toString(
getClass().getResourceAsStream("/TestReconFilterBuilder.groovy"), StandardCharsets.UTF_8));
Response response = implementationService.create(reconFilterBuilder);
reconFilterBuilder = implementationService.read(
reconFilterBuilder.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
assertNotNull(reconFilterBuilder);
task = taskService.read(TaskType.PULL, "7c2242f4-14af-4ab5-af31-cdae23783655", true);
task.setPullMode(PullMode.FILTERED_RECONCILIATION);
task.setReconFilterBuilder(reconFilterBuilder.getKey());
response = taskService.create(TaskType.PULL, task);
task = getObject(response.getLocation(), TaskService.class, PullTaskTO.class);
assertNotNull(task);
assertEquals(reconFilterBuilder.getKey(), task.getReconFilterBuilder());
// 3. exec task
ExecTO execution = execProvisioningTask(taskService, TaskType.PULL, task.getKey(), MAX_WAIT_SECONDS, false);
assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(execution.getStatus()));
// 4. verify that only enabled user was pulled
userTO = userService.read("user2");
assertNotNull(userTO);
try {
userService.read("user1");
fail("This should not happen");
} catch (SyncopeClientException e) {
assertEquals(ClientExceptionType.NotFound, e.getType());
}
} finally {
jdbcTemplate.execute("DELETE FROM testpull WHERE id = '" + user1OnTestPull + '\'');
jdbcTemplate.execute("DELETE FROM testpull WHERE id = '" + user2OnTestPull + '\'');
if (task != null && !"7c2242f4-14af-4ab5-af31-cdae23783655".equals(task.getKey())) {
taskService.delete(TaskType.PULL, task.getKey());
}
if (userTO != null) {
userService.delete(userTO.getKey());
}
}
}
@Test
public void syncTokenWithErrors() {
ResourceTO origResource = resourceService.read(RESOURCE_NAME_DBPULL);
ConnInstanceTO origConnector = connectorService.read(origResource.getConnector(), null);
ResourceTO resForTest = SerializationUtils.clone(origResource);
resForTest.setKey("syncTokenWithErrors");
resForTest.setConnector(null);
ConnInstanceTO connForTest = SerializationUtils.clone(origConnector);
connForTest.setKey(null);
connForTest.setDisplayName("For syncTokenWithErrors");
JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
try {
connForTest.getCapabilities().add(ConnectorCapability.SYNC);
ConnConfProperty changeLogColumn = connForTest.getConf("changeLogColumn").get();
assertNotNull(changeLogColumn);
assertTrue(changeLogColumn.getValues().isEmpty());
changeLogColumn.getValues().add("lastModification");
Response response = connectorService.create(connForTest);
if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
throw (RuntimeException) clientFactory.getExceptionMapper().fromResponse(response);
}
connForTest = getObject(response.getLocation(), ConnectorService.class, ConnInstanceTO.class);
assertNotNull(connForTest);
resForTest.setConnector(connForTest.getKey());
resForTest = createResource(resForTest);
assertNotNull(resForTest);
PullTaskTO pullTask = new PullTaskTO();
pullTask.setActive(true);
pullTask.setName("For syncTokenWithErrors");
pullTask.setResource(resForTest.getKey());
pullTask.setDestinationRealm(SyncopeConstants.ROOT_REALM);
pullTask.setPullMode(PullMode.INCREMENTAL);
pullTask.setPerformCreate(true);
pullTask.setPerformUpdate(true);
pullTask.setPerformDelete(true);
response = taskService.create(TaskType.PULL, pullTask);
if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
throw (RuntimeException) clientFactory.getExceptionMapper().fromResponse(response);
}
pullTask = getObject(response.getLocation(), TaskService.class, PullTaskTO.class);
assertNotNull(pullTask);
jdbcTemplate.execute("DELETE FROM testpull");
jdbcTemplate.execute("INSERT INTO testpull VALUES "
+ "(1040, 'syncTokenWithErrors1', 'Surname1', "
+ "false, 'syncTokenWithErrors1@syncope.apache.org', '2014-05-23 13:53:24.293')");
jdbcTemplate.execute("INSERT INTO testpull VALUES "
+ "(1041, 'syncTokenWithErrors2', 'Surname2', "
+ "false, 'syncTokenWithErrors1@syncope.apache.org', '2015-05-23 13:53:24.293')");
ExecTO exec = execProvisioningTask(taskService, TaskType.PULL, pullTask.getKey(), MAX_WAIT_SECONDS, false);
assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(exec.getStatus()));
resForTest = resourceService.read(resForTest.getKey());
assertTrue(resForTest.getProvision(AnyTypeKind.USER.name()).get().getSyncToken().contains("2014-05-23"));
jdbcTemplate.execute("UPDATE testpull "
+ "SET email='syncTokenWithErrors2@syncope.apache.org', lastModification='2016-05-23 13:53:24.293' "
+ "WHERE ID=1041");
exec = execProvisioningTask(taskService, TaskType.PULL, pullTask.getKey(), MAX_WAIT_SECONDS, false);
assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(exec.getStatus()));
resForTest = resourceService.read(resForTest.getKey());
assertTrue(resForTest.getProvision(AnyTypeKind.USER.name()).get().getSyncToken().contains("2016-05-23"));
} finally {
if (resForTest.getConnector() != null) {
resourceService.delete(resForTest.getKey());
connectorService.delete(connForTest.getKey());
}
jdbcTemplate.execute("DELETE FROM testpull WHERE ID=1040");
jdbcTemplate.execute("DELETE FROM testpull WHERE ID=1041");
}
}
@Test
public void remediation() {
// First of all, clear any potential conflict with existing user / group
ldapCleanup();
// 1. create ldap cloned resource, where 'userId' (mandatory on Syncope) is removed from mapping
ResourceTO ldap = resourceService.read(RESOURCE_NAME_LDAP);
ldap.setKey("ldapForRemediation");
ProvisionTO provision = ldap.getProvision(AnyTypeKind.USER.name()).get();
provision.getMapping().getItems().removeIf(item -> "userId".equals(item.getIntAttrName()));
provision.getMapping().getItems().removeIf(item -> "mail".equals(item.getIntAttrName()));
provision.getVirSchemas().clear();
ldap.getProvisions().clear();
ldap.getProvisions().add(provision);
ldap = createResource(ldap);
// 2. create PullTask with remediation enabled, for the new resource
PullTaskTO pullTask = (PullTaskTO) taskService.search(new TaskQuery.Builder(TaskType.PULL).
resource(RESOURCE_NAME_LDAP).build()).getResult().get(0);
assertNotNull(pullTask);
pullTask.setResource(ldap.getKey());
pullTask.setRemediation(true);
pullTask.getActions().clear();
Response response = taskService.create(TaskType.PULL, pullTask);
if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
throw (RuntimeException) clientFactory.getExceptionMapper().fromResponse(response);
}
pullTask = getObject(response.getLocation(), TaskService.class, PullTaskTO.class);
assertNotNull(pullTask);
try {
// 3. execute the pull task and verify that:
ExecTO execution = execProvisioningTask(
taskService, TaskType.PULL, pullTask.getKey(), MAX_WAIT_SECONDS, false);
assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(execution.getStatus()));
// 3a. user was not pulled
try {
userService.read("pullFromLDAP");
fail("This should never happen");
} catch (SyncopeClientException e) {
assertEquals(ClientExceptionType.NotFound, e.getType());
}
// 3b. remediation was created
Optional<RemediationTO> remediation = remediationService.list(
new RemediationQuery.Builder().page(1).size(1000).build()).getResult().stream().
filter(r -> "uid=pullFromLDAP,ou=People,o=isp".equalsIgnoreCase(r.getRemoteName())).
findFirst();
assertTrue(remediation.isPresent());
assertEquals(AnyTypeKind.USER.name(), remediation.get().getAnyType());
assertEquals(ResourceOperation.CREATE, remediation.get().getOperation());
assertNotNull(remediation.get().getAnyCRPayload());
assertNull(remediation.get().getAnyURPayload());
assertNull(remediation.get().getKeyPayload());
assertTrue(remediation.get().getError().contains("RequiredValuesMissing [userId]"));
// 4. remedy by copying the email value to userId
AnyCR userCR = remediation.get().getAnyCRPayload();
userCR.getResources().clear();
String email = userCR.getPlainAttr("email").get().getValues().get(0);
userCR.getPlainAttrs().add(new Attr.Builder("userId").value(email).build());
remediationService.remedy(remediation.get().getKey(), userCR);
// 5. user is now found
UserTO user = userService.read("pullFromLDAP");
assertNotNull(user);
assertEquals(email, user.getPlainAttr("userId").get().getValues().get(0));
// 6. remediation was removed
try {
remediationService.read(remediation.get().getKey());
fail("This should never happen");
} catch (SyncopeClientException e) {
assertEquals(ClientExceptionType.NotFound, e.getType());
}
} finally {
resourceService.delete(ldap.getKey());
}
}
@Test
public void remediationSinglePull() throws IOException {
// First of all, clear any potential conflict with existing user / group
ldapCleanup();
ResourceTO ldap = resourceService.read(RESOURCE_NAME_LDAP);
ldap.setKey("ldapForRemediationSinglePull");
ProvisionTO provision = ldap.getProvision(AnyTypeKind.USER.name()).get();
provision.getMapping().getItems().removeIf(item -> "userId".equals(item.getIntAttrName()));
provision.getMapping().getItems().removeIf(item -> "email".equals(item.getIntAttrName()));
provision.getVirSchemas().clear();
ldap.getProvisions().clear();
ldap.getProvisions().add(provision);
ldap = createResource(ldap);
try {
// 2. pull an user
PullTaskTO pullTask = new PullTaskTO();
pullTask.setResource(ldap.getKey());
pullTask.setDestinationRealm(SyncopeConstants.ROOT_REALM);
pullTask.setRemediation(true);
pullTask.setPerformCreate(true);
pullTask.setPerformUpdate(true);
pullTask.setUnmatchingRule(UnmatchingRule.ASSIGN);
pullTask.setMatchingRule(MatchingRule.UPDATE);
try {
reconciliationService.pull(new ReconQuery.Builder(AnyTypeKind.USER.name(), ldap.getKey()).fiql(
"uid==pullFromLDAP").build(), pullTask);
fail("Should not arrive here");
} catch (SyncopeClientException sce) {
assertEquals(ClientExceptionType.Reconciliation, sce.getType());
}
Optional<RemediationTO> remediation = remediationService.list(
new RemediationQuery.Builder().page(1).size(1000).build()).getResult().stream().
filter(r -> "uid=pullFromLDAP,ou=People,o=isp".equalsIgnoreCase(r.getRemoteName())).
findFirst();
assertTrue(remediation.isPresent());
assertEquals(AnyTypeKind.USER.name(), remediation.get().getAnyType());
assertEquals(ResourceOperation.CREATE, remediation.get().getOperation());
assertNotNull(remediation.get().getAnyCRPayload());
assertNull(remediation.get().getAnyURPayload());
assertNull(remediation.get().getKeyPayload());
assertTrue(remediation.get().getError().contains(
"SyncopeClientCompositeException: {[RequiredValuesMissing [userId]]}"));
} finally {
resourceService.delete(ldap.getKey());
remediationService.list(new RemediationQuery.Builder().page(1).size(10).build()).getResult().forEach(
r -> remediationService.delete(r.getKey()));
}
}
@Test
public void issueSYNCOPE68() {
//-----------------------------
// Create a new user ... it should be updated applying pull policy
//-----------------------------
UserCR userCR = new UserCR();
userCR.setRealm(SyncopeConstants.ROOT_REALM);
userCR.setPassword("password123");
userCR.setUsername("testuser2");
userCR.getPlainAttrs().add(attr("firstname", "testuser2"));
userCR.getPlainAttrs().add(attr("surname", "testuser2"));
userCR.getPlainAttrs().add(attr("ctype", "a type"));
userCR.getPlainAttrs().add(attr("fullname", "a type"));
userCR.getPlainAttrs().add(attr("userId", "testuser2@syncope.apache.org"));
userCR.getPlainAttrs().add(attr("email", "testuser2@syncope.apache.org"));
userCR.getResources().add(RESOURCE_NAME_NOPROPAGATION2);
userCR.getResources().add(RESOURCE_NAME_NOPROPAGATION4);
userCR.getMemberships().add(new MembershipTO.Builder("bf825fe1-7320-4a54-bd64-143b5c18ab97").build());
UserTO userTO = createUser(userCR).getEntity();
assertNotNull(userTO);
assertEquals("testuser2", userTO.getUsername());
assertEquals(1, userTO.getMemberships().size());
assertEquals(3, userTO.getResources().size());
//-----------------------------
try {
//-----------------------------
// add user template
//-----------------------------
UserTO template = new UserTO();
template.getMemberships().add(new MembershipTO.Builder("b8d38784-57e7-4595-859a-076222644b55").build());
template.getResources().add(RESOURCE_NAME_NOPROPAGATION4);
//-----------------------------
// Update pull task
PullTaskTO task = taskService.read(TaskType.PULL, "81d88f73-d474-4450-9031-605daa4e313f", true);
assertNotNull(task);
task.getTemplates().put(AnyTypeKind.USER.name(), template);
taskService.update(TaskType.PULL, task);
PullTaskTO actual = taskService.read(TaskType.PULL, task.getKey(), true);
assertNotNull(actual);
assertEquals(task.getKey(), actual.getKey());
assertFalse(actual.getTemplates().get(AnyTypeKind.USER.name()).getResources().isEmpty());
assertFalse(((UserTO) actual.getTemplates().get(AnyTypeKind.USER.name())).getMemberships().isEmpty());
ExecTO execution = execProvisioningTask(
taskService, TaskType.PULL, actual.getKey(), MAX_WAIT_SECONDS, false);
assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(execution.getStatus()));
userTO = userService.read("testuser2");
assertNotNull(userTO);
assertEquals("testuser2@syncope.apache.org", userTO.getPlainAttr("userId").get().getValues().get(0));
assertEquals(2, userTO.getMemberships().size());
assertEquals(4, userTO.getResources().size());
} finally {
UserTO dUserTO = deleteUser(userTO.getKey()).getEntity();
assertNotNull(dUserTO);
}
}
@Test
public void issueSYNCOPE230() {
JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
String id = "a54b3794-b231-47be-b24a-11e1a42949f6";
// 1. populate the external table
jdbcTemplate.execute("INSERT INTO testpull VALUES"
+ "('" + id + "', 'issuesyncope230', 'Surname230', false, 'syncope230@syncope.apache.org', NULL)");
// 2. execute PullTask for resource-db-pull (table TESTPULL on external H2)
execProvisioningTask(
taskService, TaskType.PULL, "7c2242f4-14af-4ab5-af31-cdae23783655", MAX_WAIT_SECONDS, false);
// 3. read e-mail address for user created by the PullTask first execution
UserTO userTO = userService.read("issuesyncope230");
assertNotNull(userTO);
String email = userTO.getPlainAttr("email").get().getValues().iterator().next();
assertNotNull(email);
// 4. update TESTPULL on external H2 by changing e-mail address
jdbcTemplate.execute("UPDATE TESTPULL SET email='updatedSYNCOPE230@syncope.apache.org' WHERE id='" + id + '\'');
// 5. re-execute the PullTask
execProvisioningTask(
taskService, TaskType.PULL, "7c2242f4-14af-4ab5-af31-cdae23783655", MAX_WAIT_SECONDS, false);
// 6. verify that the e-mail was updated
userTO = userService.read("issuesyncope230");
assertNotNull(userTO);
email = userTO.getPlainAttr("email").get().getValues().iterator().next();
assertNotNull(email);
assertEquals("updatedSYNCOPE230@syncope.apache.org", email);
}
@Test
public void issueSYNCOPE258() throws IOException {
// -----------------------------
// Add a custom correlation rule
// -----------------------------
ImplementationTO corrRule = null;
try {
corrRule = implementationService.read(IdMImplementationType.PULL_CORRELATION_RULE, "TestPullRule");
} catch (SyncopeClientException e) {
if (e.getType().getResponseStatus() == Response.Status.NOT_FOUND) {
corrRule = new ImplementationTO();
corrRule.setKey("TestPullRule");
corrRule.setEngine(ImplementationEngine.GROOVY);
corrRule.setType(IdMImplementationType.PULL_CORRELATION_RULE);
corrRule.setBody(IOUtils.toString(
getClass().getResourceAsStream("/TestPullRule.groovy"), StandardCharsets.UTF_8));
Response response = implementationService.create(corrRule);
corrRule = implementationService.read(
corrRule.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
assertNotNull(corrRule);
}
}
assertNotNull(corrRule);
PullPolicyTO policyTO = policyService.read(PolicyType.PULL, "9454b0d7-2610-400a-be82-fc23cf553dd6");
policyTO.getCorrelationRules().put(AnyTypeKind.USER.name(), corrRule.getKey());
policyService.update(PolicyType.PULL, policyTO);
// -----------------------------
PullTaskTO task = new PullTaskTO();
task.setDestinationRealm(SyncopeConstants.ROOT_REALM);
task.setName("Test Pull Rule");
task.setActive(true);
task.setResource(RESOURCE_NAME_WS2);
task.setPullMode(PullMode.FULL_RECONCILIATION);
task.setPerformCreate(true);
task.setPerformDelete(true);
task.setPerformUpdate(true);
Response response = taskService.create(TaskType.PULL, task);
task = getObject(response.getLocation(), TaskService.class, PullTaskTO.class);
UserCR userCR = UserITCase.getUniqueSample("s258_1@apache.org");
userCR.getResources().clear();
userCR.getResources().add(RESOURCE_NAME_WS2);
createUser(userCR);
userCR = UserITCase.getUniqueSample("s258_2@apache.org");
userCR.getResources().clear();
userCR.getResources().add(RESOURCE_NAME_WS2);
UserTO userTO = createUser(userCR).getEntity();
// change email in order to unmatch the second user
UserUR userUR = new UserUR();
userUR.setKey(userTO.getKey());
userUR.getPlainAttrs().add(attrAddReplacePatch("email", "s258@apache.org"));
userService.update(userUR);
execProvisioningTask(taskService, TaskType.PULL, task.getKey(), MAX_WAIT_SECONDS, false);
PullTaskTO executed = taskService.read(TaskType.PULL, task.getKey(), true);
assertEquals(1, executed.getExecutions().size());
// asser for just one match
assertTrue(executed.getExecutions().get(0).getMessage().contains("[updated/failures]: 1/0"),
() -> executed.getExecutions().get(0).getMessage().substring(0, 55) + "...");
}
@Test
public void issueSYNCOPE272() {
removeTestUsers();
// create user with testdb resource
UserCR userCR = UserITCase.getUniqueSample("syncope272@syncope.apache.org");
userCR.getResources().add(RESOURCE_NAME_TESTDB);
ProvisioningResult<UserTO> result = createUser(userCR);
UserTO userTO = result.getEntity();
try {
assertNotNull(userTO);
assertEquals(1, result.getPropagationStatuses().size());
assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
ExecTO taskExecTO = execProvisioningTask(
taskService, TaskType.PULL, "986867e2-993b-430e-8feb-aa9abb4c1dcd", MAX_WAIT_SECONDS, false);
assertNotNull(taskExecTO.getStatus());
assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(taskExecTO.getStatus()));
userTO = userService.read(userTO.getKey());
assertNotNull(userTO);
assertNotNull(userTO.getPlainAttr("firstname").get().getValues().get(0));
} finally {
removeTestUsers();
}
}
@Test
public void issueSYNCOPE307() {
UserCR userCR = UserITCase.getUniqueSample("s307@apache.org");
userCR.setUsername("test0");
userCR.getPlainAttrs().removeIf(attr -> "firstname".equals(attr.getSchema()));
userCR.getPlainAttrs().add(attr("firstname", "nome0"));
userCR.getAuxClasses().add("csv");
userCR.getResources().clear();
userCR.getResources().add(RESOURCE_NAME_WS2);
UserTO userTO = createUser(userCR).getEntity();
assertNotNull(userTO);
userTO = userService.read(userTO.getKey());
assertTrue(userTO.getVirAttrs().isEmpty());
// Update pull task
PullTaskTO task = taskService.read(TaskType.PULL, "38abbf9e-a1a3-40a1-a15f-7d0ac02f47f1", true);
assertNotNull(task);
UserTO template = new UserTO();
template.setPassword("'password123'");
template.getResources().add(RESOURCE_NAME_DBVIRATTR);
template.getVirAttrs().add(attr("virtualdata", "'virtualvalue'"));
task.getTemplates().put(AnyTypeKind.USER.name(), template);
taskService.update(TaskType.PULL, task);
// exec task: one user from CSV will match the user created above and template will be applied
ExecTO exec = execProvisioningTask(taskService, TaskType.PULL, task.getKey(), MAX_WAIT_SECONDS, false);
// check that template was successfully applied
// 1. propagation to db
assertEquals(ExecStatus.SUCCESS.name(), exec.getStatus());
JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
String value = queryForObject(jdbcTemplate,
MAX_WAIT_SECONDS, "SELECT USERNAME FROM testpull WHERE ID=?", String.class, userTO.getKey());
assertEquals("virtualvalue", value);
// 2. virtual attribute
userTO = userService.read(userTO.getKey());
assertEquals("virtualvalue", userTO.getVirAttr("virtualdata").get().getValues().get(0));
}
@Test
public void issueSYNCOPE313DB() throws Exception {
// 1. create user in DB
UserCR userCR = UserITCase.getUniqueSample("syncope313-db@syncope.apache.org");
userCR.setPassword("security123");
userCR.getResources().add(RESOURCE_NAME_TESTDB);
UserTO user = createUser(userCR).getEntity();
assertNotNull(user);
assertFalse(user.getResources().isEmpty());
// 2. Check that the DB resource has the correct password
JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
String value = queryForObject(jdbcTemplate,
MAX_WAIT_SECONDS, "SELECT PASSWORD FROM test WHERE ID=?", String.class, user.getUsername());
assertEquals(Encryptor.getInstance().encode("security123", CipherAlgorithm.SHA1), value.toUpperCase());
// 3. Update the password in the DB
String newCleanPassword = "new-security";
String newPassword = Encryptor.getInstance().encode(newCleanPassword, CipherAlgorithm.SHA1);
jdbcTemplate.execute("UPDATE test set PASSWORD='" + newPassword + "' where ID='" + user.getUsername() + '\'');
// 4. Pull the user from the resource
ImplementationTO pullActions = new ImplementationTO();
pullActions.setKey(DBPasswordPullActions.class.getSimpleName());
pullActions.setEngine(ImplementationEngine.JAVA);
pullActions.setType(IdMImplementationType.PULL_ACTIONS);
pullActions.setBody(DBPasswordPullActions.class.getName());
Response response = implementationService.create(pullActions);
pullActions = implementationService.read(
pullActions.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
assertNotNull(pullActions);
PullTaskTO pullTask = new PullTaskTO();
pullTask.setDestinationRealm(SyncopeConstants.ROOT_REALM);
pullTask.setName("DB Pull Task");
pullTask.setActive(true);
pullTask.setPerformCreate(true);
pullTask.setPerformUpdate(true);
pullTask.setPullMode(PullMode.FULL_RECONCILIATION);
pullTask.setResource(RESOURCE_NAME_TESTDB);
pullTask.getActions().add(pullActions.getKey());
Response taskResponse = taskService.create(TaskType.PULL, pullTask);
PullTaskTO actual = getObject(taskResponse.getLocation(), TaskService.class, PullTaskTO.class);
assertNotNull(actual);
pullTask = taskService.read(TaskType.PULL, actual.getKey(), true);
assertNotNull(pullTask);
assertEquals(actual.getKey(), pullTask.getKey());
assertEquals(actual.getJobDelegate(), pullTask.getJobDelegate());
ExecTO execution = execProvisioningTask(taskService, TaskType.PULL, pullTask.getKey(), MAX_WAIT_SECONDS, false);
assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(execution.getStatus()));
// 5. Test the pulled user
Triple<Map<String, Set<String>>, List<String>, UserTO> self =
clientFactory.create(user.getUsername(), newCleanPassword).self();
assertNotNull(self);
// 6. Delete PullTask + user
taskService.delete(TaskType.PULL, pullTask.getKey());
deleteUser(user.getKey());
}
@Test
public void issueSYNCOPE313LDAP() throws Exception {
// First of all, clear any potential conflict with existing user / group
ldapCleanup();
UserTO user = null;
PullTaskTO pullTask = null;
ConnInstanceTO resourceConnector = null;
ConnConfProperty property = null;
try {
// 1. create user in LDAP
String oldCleanPassword = "security123";
UserCR userCR = UserITCase.getUniqueSample("syncope313-ldap@syncope.apache.org");
userCR.setPassword(oldCleanPassword);
userCR.getResources().add(RESOURCE_NAME_LDAP);
user = createUser(userCR).getEntity();
assertNotNull(user);
assertFalse(user.getResources().isEmpty());
// 2. request to change password only on Syncope and not on LDAP
String newCleanPassword = "new-security123";
UserUR userUR = new UserUR();
userUR.setKey(user.getKey());
userUR.setPassword(new PasswordPatch.Builder().value(newCleanPassword).build());
user = updateUser(userUR).getEntity();
// 3. Check that the Syncope user now has the changed password
Triple<Map<String, Set<String>>, List<String>, UserTO> self =
clientFactory.create(user.getUsername(), newCleanPassword).self();
assertNotNull(self);
// 4. Check that the LDAP resource has the old password
ConnObjectTO connObject =
resourceService.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), user.getKey());
assertNotNull(getLdapRemoteObject(
connObject.getAttr(Name.NAME).get().getValues().get(0),
oldCleanPassword,
connObject.getAttr(Name.NAME).get().getValues().get(0)));
// 5. Update the LDAP Connector to retrieve passwords
ResourceTO ldapResource = resourceService.read(RESOURCE_NAME_LDAP);
resourceConnector = connectorService.read(
ldapResource.getConnector(), Locale.ENGLISH.getLanguage());
property = resourceConnector.getConf("retrievePasswordsWithSearch").get();
property.getValues().clear();
property.getValues().add(Boolean.TRUE);
connectorService.update(resourceConnector);
// 6. Pull the user from the resource
ImplementationTO pullActions = new ImplementationTO();
pullActions.setKey(LDAPPasswordPullActions.class.getSimpleName());
pullActions.setEngine(ImplementationEngine.JAVA);
pullActions.setType(IdMImplementationType.PULL_ACTIONS);
pullActions.setBody(LDAPPasswordPullActions.class.getName());
Response response = implementationService.create(pullActions);
pullActions = implementationService.read(
pullActions.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
assertNotNull(pullActions);
pullTask = new PullTaskTO();
pullTask.setDestinationRealm(SyncopeConstants.ROOT_REALM);
pullTask.setName("LDAP Pull Task");
pullTask.setActive(true);
pullTask.setPerformCreate(true);
pullTask.setPerformUpdate(true);
pullTask.setPullMode(PullMode.FULL_RECONCILIATION);
pullTask.setResource(RESOURCE_NAME_LDAP);
pullTask.getActions().add(pullActions.getKey());
Response taskResponse = taskService.create(TaskType.PULL, pullTask);
pullTask = getObject(taskResponse.getLocation(), TaskService.class, PullTaskTO.class);
assertNotNull(pullTask);
ExecTO execution = execProvisioningTask(
taskService, TaskType.PULL, pullTask.getKey(), MAX_WAIT_SECONDS, false);
assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(execution.getStatus()));
// 7. Test the pulled user
self = clientFactory.create(user.getUsername(), oldCleanPassword).self();
assertNotNull(self);
} catch (Exception e) {
fail(e::getMessage);
} finally {
// Delete PullTask + user + reset the connector
if (pullTask != null) {
taskService.delete(TaskType.PULL, pullTask.getKey());
}
if (resourceConnector != null && property != null) {
property.getValues().clear();
property.getValues().add(Boolean.FALSE);
connectorService.update(resourceConnector);
}
if (user != null) {
deleteUser(user.getKey());
}
}
}
@Test
public void issueSYNCOPE1062() {
GroupTO propagationGroup = null;
PullTaskTO pullTask = null;
UserTO user = null;
GroupTO group = null;
try {
// 1. create group with resource for propagation
GroupCR propagationGroupCR = GroupITCase.getBasicSample("SYNCOPE1062");
propagationGroupCR.getResources().add(RESOURCE_NAME_DBPULL);
propagationGroup = createGroup(propagationGroupCR).getEntity();
// 2. create pull task for another resource, with user template assigning the group above
pullTask = new PullTaskTO();
pullTask.setDestinationRealm(SyncopeConstants.ROOT_REALM);
pullTask.setName("SYNCOPE1062");
pullTask.setActive(true);
pullTask.setPerformCreate(true);
pullTask.setPerformUpdate(true);
pullTask.setPullMode(PullMode.FULL_RECONCILIATION);
pullTask.setResource(RESOURCE_NAME_LDAP);
UserTO template = new UserTO();
template.getAuxClasses().add("minimal group");
template.getMemberships().add(new MembershipTO.Builder(propagationGroup.getKey()).build());
template.getPlainAttrs().add(attr("firstname", "'fixed'"));
pullTask.getTemplates().put(AnyTypeKind.USER.name(), template);
Response taskResponse = taskService.create(TaskType.PULL, pullTask);
pullTask = getObject(taskResponse.getLocation(), TaskService.class, PullTaskTO.class);
assertNotNull(pullTask);
assertFalse(pullTask.getTemplates().isEmpty());
// 3. exec the pull task
ExecTO execution = execProvisioningTask(
taskService, TaskType.PULL, pullTask.getKey(), MAX_WAIT_SECONDS, false);
assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(execution.getStatus()));
// the user is successfully pulled...
user = userService.read("pullFromLDAP");
assertNotNull(user);
assertEquals("pullFromLDAP@syncope.apache.org", user.getPlainAttr("email").get().getValues().get(0));
group = groupService.read("testLDAPGroup");
assertNotNull(group);
ConnObjectTO connObject =
resourceService.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), user.getKey());
assertNotNull(connObject);
assertEquals("pullFromLDAP@syncope.apache.org", connObject.getAttr("mail").get().getValues().get(0));
Attr userDn = connObject.getAttr(Name.NAME).get();
assertNotNull(userDn);
assertEquals(1, userDn.getValues().size());
assertNotNull(
getLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, userDn.getValues().get(0)));
// ...and propagated
PagedResult<TaskTO> propagationTasks = taskService.search(new TaskQuery.Builder(TaskType.PROPAGATION).
resource(RESOURCE_NAME_DBPULL).
anyTypeKind(AnyTypeKind.USER).entityKey(user.getKey()).build());
assertEquals(1, propagationTasks.getSize());
// 4. update the user on the external resource
updateLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD,
userDn.getValues().get(0), Map.of("mail", "pullFromLDAP2@syncope.apache.org"));
connObject = resourceService.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), user.getKey());
assertNotNull(connObject);
assertEquals("pullFromLDAP2@syncope.apache.org", connObject.getAttr("mail").get().getValues().get(0));
// 5. exec the pull task again
execution = execProvisioningTask(taskService, TaskType.PULL, pullTask.getKey(), MAX_WAIT_SECONDS, false);
assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(execution.getStatus()));
// the internal is updated...
user = userService.read("pullFromLDAP");
assertNotNull(user);
assertEquals("pullFromLDAP2@syncope.apache.org", user.getPlainAttr("email").get().getValues().get(0));
// ...and propagated
propagationTasks = taskService.search(new TaskQuery.Builder(TaskType.PROPAGATION).
resource(RESOURCE_NAME_DBPULL).
anyTypeKind(AnyTypeKind.USER).entityKey(user.getKey()).build());
assertEquals(2, propagationTasks.getSize());
} catch (Exception e) {
LOG.error("Unexpected during issueSYNCOPE1062()", e);
fail(e::getMessage);
} finally {
if (pullTask != null) {
taskService.delete(TaskType.PULL, pullTask.getKey());
}
if (propagationGroup != null) {
groupService.delete(propagationGroup.getKey());
}
if (group != null) {
groupService.delete(group.getKey());
}
if (user != null) {
userService.delete(user.getKey());
}
}
}
}