/*
* 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.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import javax.ws.rs.core.Response;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.io.IOUtils;
import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.to.AbstractTaskTO;
import org.apache.syncope.common.lib.to.AnyTypeClassTO;
import org.apache.syncope.common.lib.to.AttrTO;
import org.apache.syncope.common.lib.to.BulkAction;
import org.apache.syncope.common.lib.to.ConnInstanceTO;
import org.apache.syncope.common.lib.to.GroupTO;
import org.apache.syncope.common.lib.to.MappingItemTO;
import org.apache.syncope.common.lib.to.MappingTO;
import org.apache.syncope.common.lib.to.MembershipTO;
import org.apache.syncope.common.lib.to.PlainSchemaTO;
import org.apache.syncope.common.lib.to.ProvisionTO;
import org.apache.syncope.common.lib.to.PullTaskTO;
import org.apache.syncope.common.lib.to.RealmTO;
import org.apache.syncope.common.lib.to.ResourceTO;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.AttrSchemaType;
import org.apache.syncope.common.lib.types.ConnConfPropSchema;
import org.apache.syncope.common.lib.types.ConnConfProperty;
import org.apache.syncope.common.lib.types.ConnectorCapability;
import org.apache.syncope.common.lib.types.MappingPurpose;
import org.apache.syncope.common.lib.types.PullMode;
import org.apache.syncope.common.lib.types.SchemaType;
import org.apache.syncope.common.lib.types.TaskType;
import org.apache.syncope.common.rest.api.beans.AnyQuery;
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.migration.MigrationPullActions;
import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
import org.identityconnectors.common.security.GuardedString;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:migrationEnv.xml" })
public class MigrationITCase extends AbstractTaskITCase {
private static final String CONNINSTANCE_DISPLAY_NAME = "syncope12DB";
private static final String RESOURCE_KEY = "Syncope 1.2";
private static final String MIGRATION_CIPHER_ALGORITHM = "migrationCipherAlgorithm";
private static final String MIGRATION_RESOURCES_SCHEMA = "migrationResources";
private static final String MIGRATION_MEMBERSHIPS_SCHEMA = "migrationMemberships";
private static final String MIGRATION_ANYTYPE_CLASS = "migration";
private static final String MIGRATION_REALM = "migration";
private static final String PULL_TASK_NAME = "Syncope 1.2 migration";
private static String basedir;
private static String connectorServerLocation;
private static String connIdDbVersion;
@BeforeClass
public static void setup() throws IOException {
InputStream propStream = null;
try {
Properties props = new Properties();
propStream = MigrationITCase.class.getResourceAsStream("/test.properties");
props.load(propStream);
IOUtils.closeQuietly(propStream);
propStream = MigrationITCase.class.getResourceAsStream("/connid.properties");
props.load(propStream);
basedir = props.getProperty("basedir");
for (String location : props.getProperty("connid.locations").split(",")) {
if (!location.startsWith("file")) {
connectorServerLocation = location;
}
}
connIdDbVersion = props.getProperty("connid.database.version");
} catch (Exception e) {
LOG.error("Could not load /connid.properties", e);
} finally {
IOUtils.closeQuietly(propStream);
}
assertNotNull(basedir);
assertNotNull(connectorServerLocation);
assertNotNull(connIdDbVersion);
}
@Autowired
private DriverManagerDataSource syncope12DataSource;
private String setupAnyTypeClass() {
PlainSchemaTO cipherAlgorithm = new PlainSchemaTO();
cipherAlgorithm.setKey(MIGRATION_CIPHER_ALGORITHM);
cipherAlgorithm.setType(AttrSchemaType.String);
cipherAlgorithm.setReadonly(true);
cipherAlgorithm = createSchema(SchemaType.PLAIN, cipherAlgorithm);
PlainSchemaTO migrationResources = new PlainSchemaTO();
migrationResources.setKey(MIGRATION_RESOURCES_SCHEMA);
migrationResources.setType(AttrSchemaType.String);
migrationResources.setMultivalue(true);
migrationResources.setReadonly(true);
migrationResources = createSchema(SchemaType.PLAIN, migrationResources);
PlainSchemaTO migrationMemberships = new PlainSchemaTO();
migrationMemberships.setKey(MIGRATION_MEMBERSHIPS_SCHEMA);
migrationMemberships.setType(AttrSchemaType.String);
migrationMemberships.setMultivalue(true);
migrationMemberships.setReadonly(true);
migrationMemberships = createSchema(SchemaType.PLAIN, migrationMemberships);
AnyTypeClassTO migration = new AnyTypeClassTO();
migration.setKey(MIGRATION_ANYTYPE_CLASS);
migration.getPlainSchemas().add(cipherAlgorithm.getKey());
migration.getPlainSchemas().add(migrationResources.getKey());
migration.getPlainSchemas().add(migrationMemberships.getKey());
Response response = anyTypeClassService.create(migration);
assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatusInfo().getStatusCode());
return MIGRATION_ANYTYPE_CLASS;
}
private String setupConnector() {
ConnInstanceTO connInstanceTO = new ConnInstanceTO();
connInstanceTO.setLocation(connectorServerLocation);
connInstanceTO.setConnectorName("net.tirasa.connid.bundles.db.scriptedsql.ScriptedSQLConnector");
connInstanceTO.setBundleName("net.tirasa.connid.bundles.db.scriptedsql");
connInstanceTO.setVersion(connIdDbVersion);
connInstanceTO.setDisplayName(CONNINSTANCE_DISPLAY_NAME);
ConnConfPropSchema schema = new ConnConfPropSchema();
schema.setName("user");
schema.setType(String.class.getName());
ConnConfProperty property = new ConnConfProperty();
property.setSchema(schema);
property.getValues().add(syncope12DataSource.getUsername());
connInstanceTO.getConf().add(property);
schema = new ConnConfPropSchema();
schema.setName("password");
schema.setType(GuardedString.class.getName());
property = new ConnConfProperty();
property.setSchema(schema);
property.getValues().add(syncope12DataSource.getPassword());
connInstanceTO.getConf().add(property);
schema = new ConnConfPropSchema();
schema.setName("jdbcDriver");
property = new ConnConfProperty();
property.setSchema(schema);
property.getValues().add("org.h2.Driver");
connInstanceTO.getConf().add(property);
schema = new ConnConfPropSchema();
schema.setName("jdbcUrlTemplate");
property = new ConnConfProperty();
property.setSchema(schema);
property.getValues().add(syncope12DataSource.getUrl());
connInstanceTO.getConf().add(property);
schema = new ConnConfPropSchema();
schema.setName("testScriptFileName");
property = new ConnConfProperty();
property.setSchema(schema);
property.getValues().add(basedir + "/../../core/migration/src/main/resources/scripted/TestScript.groovy");
connInstanceTO.getConf().add(property);
schema = new ConnConfPropSchema();
schema.setName("schemaScriptFileName");
property = new ConnConfProperty();
property.setSchema(schema);
property.getValues().add(basedir + "/../../core/migration/src/main/resources/scripted/SchemaScript.groovy");
connInstanceTO.getConf().add(property);
schema = new ConnConfPropSchema();
schema.setName("searchScriptFileName");
property = new ConnConfProperty();
property.setSchema(schema);
property.getValues().add(basedir + "/../../core/migration/src/main/resources/scripted/SearchScript.groovy");
connInstanceTO.getConf().add(property);
schema = new ConnConfPropSchema();
schema.setName("syncScriptFileName");
property = new ConnConfProperty();
property.setSchema(schema);
property.getValues().add(basedir + "/../../core/migration/src/main/resources/scripted/SyncScript.groovy");
connInstanceTO.getConf().add(property);
connInstanceTO.getCapabilities().add(ConnectorCapability.SEARCH);
connInstanceTO.getCapabilities().add(ConnectorCapability.SYNC);
Response response = connectorService.create(connInstanceTO);
connInstanceTO = getObject(response.getLocation(), ConnectorService.class, ConnInstanceTO.class);
try {
connectorService.check(connInstanceTO);
} catch (Exception e) {
fail("Unexpected exception:\n" + ExceptionUtils2.getFullStackTrace(e));
}
return connInstanceTO.getKey();
}
private void setupResource(final String connectorKey, final String anyTypeClass) {
ResourceTO resourceTO = new ResourceTO();
resourceTO.setKey(RESOURCE_KEY);
resourceTO.setConnector(connectorKey);
// USER
ProvisionTO provisionTO = new ProvisionTO();
provisionTO.setAnyType(AnyTypeKind.USER.name());
provisionTO.setObjectClass(ObjectClass.ACCOUNT_NAME);
provisionTO.getAuxClasses().add(anyTypeClass);
resourceTO.getProvisions().add(provisionTO);
MappingTO mapping = new MappingTO();
provisionTO.setMapping(mapping);
MappingItemTO item = new MappingItemTO();
item.setIntAttrName("username");
item.setExtAttrName("username");
item.setMandatoryCondition("true");
item.setPurpose(MappingPurpose.PULL);
mapping.setConnObjectKeyItem(item);
item = new MappingItemTO();
item.setPassword(true);
item.setIntAttrName("password");
item.setExtAttrName("__PASSWORD__");
item.setMandatoryCondition("true");
item.setPurpose(MappingPurpose.PULL);
mapping.add(item);
item = new MappingItemTO();
item.setIntAttrName(MIGRATION_CIPHER_ALGORITHM);
item.setExtAttrName("cipherAlgorithm");
item.setMandatoryCondition("true");
item.setPurpose(MappingPurpose.PULL);
mapping.add(item);
item = new MappingItemTO();
item.setIntAttrName("surname");
item.setExtAttrName("surname");
item.setMandatoryCondition("false");
item.setPurpose(MappingPurpose.PULL);
mapping.add(item);
item = new MappingItemTO();
item.setIntAttrName("email");
item.setExtAttrName("email");
item.setMandatoryCondition("false");
item.setPurpose(MappingPurpose.PULL);
mapping.add(item);
item = new MappingItemTO();
item.setIntAttrName("firstname");
item.setExtAttrName("firstname");
item.setMandatoryCondition("false");
item.setPurpose(MappingPurpose.PULL);
mapping.add(item);
item = new MappingItemTO();
item.setIntAttrName("ctype");
item.setExtAttrName("type");
item.setMandatoryCondition("false");
item.setPurpose(MappingPurpose.PULL);
mapping.add(item);
item = new MappingItemTO();
item.setIntAttrName("gender");
item.setExtAttrName("gender");
item.setMandatoryCondition("false");
item.setPurpose(MappingPurpose.PULL);
mapping.add(item);
item = new MappingItemTO();
item.setIntAttrName("loginDate");
item.setExtAttrName("loginDate");
item.setMandatoryCondition("false");
item.setPurpose(MappingPurpose.PULL);
mapping.add(item);
item = new MappingItemTO();
item.setIntAttrName(MIGRATION_RESOURCES_SCHEMA);
item.setExtAttrName("__RESOURCES__");
item.setMandatoryCondition("false");
item.setPurpose(MappingPurpose.PULL);
mapping.add(item);
// GROUP
provisionTO = new ProvisionTO();
provisionTO.setAnyType(AnyTypeKind.GROUP.name());
provisionTO.setObjectClass(ObjectClass.GROUP_NAME);
provisionTO.getAuxClasses().add(anyTypeClass);
resourceTO.getProvisions().add(provisionTO);
mapping = new MappingTO();
provisionTO.setMapping(mapping);
item = new MappingItemTO();
item.setIntAttrName("name");
item.setExtAttrName("name");
item.setMandatoryCondition("true");
item.setPurpose(MappingPurpose.PULL);
mapping.setConnObjectKeyItem(item);
item = new MappingItemTO();
item.setIntAttrName("show");
item.setExtAttrName("show");
item.setMandatoryCondition("false");
item.setPurpose(MappingPurpose.PULL);
mapping.add(item);
item = new MappingItemTO();
item.setIntAttrName("title");
item.setExtAttrName("title");
item.setMandatoryCondition("false");
item.setPurpose(MappingPurpose.PULL);
mapping.add(item);
item = new MappingItemTO();
item.setIntAttrName("icon");
item.setExtAttrName("icon");
item.setMandatoryCondition("false");
item.setPurpose(MappingPurpose.PULL);
mapping.add(item);
item = new MappingItemTO();
item.setIntAttrName(MIGRATION_RESOURCES_SCHEMA);
item.setExtAttrName("__RESOURCES__");
item.setMandatoryCondition("false");
item.setPurpose(MappingPurpose.PULL);
mapping.add(item);
item = new MappingItemTO();
item.setIntAttrName(MIGRATION_MEMBERSHIPS_SCHEMA);
item.setExtAttrName("__MEMBERSHIPS__");
item.setMandatoryCondition("false");
item.setPurpose(MappingPurpose.PULL);
mapping.add(item);
resourceService.create(resourceTO);
}
private void setupRealm() {
try {
realmService.list("/" + MIGRATION_REALM);
} catch (SyncopeClientException e) {
LOG.error("{} not found? Let's attempt to re-create...", MIGRATION_REALM, e);
RealmTO realm = new RealmTO();
realm.setName(MIGRATION_REALM);
realmService.create("/", realm);
}
}
private String setupPullTask() {
PullTaskTO task = new PullTaskTO();
task.setActive(true);
task.setName(PULL_TASK_NAME);
task.setResource(RESOURCE_KEY);
task.setPerformCreate(true);
task.setSyncStatus(true);
task.setPullMode(PullMode.FULL_RECONCILIATION);
task.setDestinationRealm("/" + MIGRATION_REALM);
task.getActionsClassNames().add(MigrationPullActions.class.getName());
UserTO user = new UserTO();
user.getPlainAttrs().add(new AttrTO.Builder().schema("userId").value("'12' + username + '@syncope.apache.org'").
build());
user.getPlainAttrs().add(new AttrTO.Builder().schema("fullname").value("username").build());
task.getTemplates().put(AnyTypeKind.USER.name(), user);
Response response = taskService.create(task);
task = getObject(response.getLocation(), TaskService.class, PullTaskTO.class);
return task.getKey();
}
@Test
public void migrateFromSyncope12() throws InterruptedException {
// 1. cleanup
try {
for (AbstractTaskTO task : taskService.list(
new TaskQuery.Builder(TaskType.PULL).resource(RESOURCE_KEY).build()).getResult()) {
if (PULL_TASK_NAME.equals(PullTaskTO.class.cast(task).getName())) {
taskService.delete(task.getKey());
}
}
} catch (Exception e) {
// ignore
}
try {
resourceService.delete(RESOURCE_KEY);
} catch (Exception e) {
// ignore
}
try {
for (ConnInstanceTO connInstance : connectorService.list(null)) {
if (CONNINSTANCE_DISPLAY_NAME.equals(connInstance.getDisplayName())) {
connectorService.delete(connInstance.getKey());
}
}
} catch (Exception e) {
// ignore
}
try {
schemaService.delete(SchemaType.PLAIN, MIGRATION_CIPHER_ALGORITHM);
schemaService.delete(SchemaType.PLAIN, MIGRATION_MEMBERSHIPS_SCHEMA);
schemaService.delete(SchemaType.PLAIN, MIGRATION_RESOURCES_SCHEMA);
anyTypeClassService.delete(MIGRATION_ANYTYPE_CLASS);
} catch (Exception e) {
// ignore
}
BulkAction bulkAction = new BulkAction();
bulkAction.setType(BulkAction.Type.DELETE);
for (UserTO user : userService.search(new AnyQuery.Builder().fiql("username==*12").build()).getResult()) {
bulkAction.getTargets().add(user.getKey());
}
userService.bulk(bulkAction);
bulkAction.getTargets().clear();
for (GroupTO group : groupService.search(new AnyQuery.Builder().fiql("name==*12").build()).getResult()) {
bulkAction.getTargets().add(group.getKey());
}
groupService.bulk(bulkAction);
// 2. setup
setupResource(setupConnector(), setupAnyTypeClass());
setupRealm();
String pullTaskKey = setupPullTask();
// 3. execute pull task
execProvisioningTask(taskService, pullTaskKey, 50, false);
// 4. verify
UserTO user = null;
int i = 0;
boolean membershipFound = false;
do {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
try {
user = userService.read("rossini12");
assertNotNull(user);
membershipFound = IterableUtils.matchesAny(user.getMemberships(), new Predicate<MembershipTO>() {
@Override
public boolean evaluate(final MembershipTO object) {
return "1 root12".equals(object.getGroupName());
}
});
} catch (Exception e) {
// ignore
}
i++;
} while (!membershipFound && i < 50);
assertNotNull(user);
assertTrue(membershipFound);
assertEquals("/" + MIGRATION_REALM, user.getRealm());
GroupTO group = groupService.read("12 aRoleForPropagation12");
assertNotNull(group);
assertEquals("/" + MIGRATION_REALM, group.getRealm());
// 4a. user plain attrs
assertEquals("Gioacchino", user.getPlainAttrMap().get("firstname").getValues().get(0));
assertEquals("Rossini", user.getPlainAttrMap().get("surname").getValues().get(0));
// 4b. user resources
assertTrue(user.getResources().contains(RESOURCE_NAME_TESTDB2));
// 4c. user password
assertNotNull(clientFactory.create("bellini12", ADMIN_PWD).self());
// 4d. group plain attrs
assertEquals("r12", group.getPlainAttrMap().get("title").getValues().get(0));
// 4e. group resources
assertTrue(group.getResources().contains(RESOURCE_NAME_CSV));
}
}