diff --git a/pom.xml b/pom.xml index a7b330f6..02b3b0f5 100644 --- a/pom.xml +++ b/pom.xml @@ -72,6 +72,7 @@ ${project.build.directory}/staging 1.5 22.3.1 + 2.9.6 ${project.build.directory}/compiler @@ -872,6 +873,11 @@ xml-apis 1.4.01 + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + diff --git a/src/main/java/org/lsc/Hooks.java b/src/main/java/org/lsc/Hooks.java new file mode 100644 index 00000000..69a557fc --- /dev/null +++ b/src/main/java/org/lsc/Hooks.java @@ -0,0 +1,148 @@ +/* + **************************************************************************** + * Ldap Synchronization Connector provides tools to synchronize + * electronic identities from a list of data sources including + * any database with a JDBC connector, another LDAP directory, + * flat files... + * + * ==LICENSE NOTICE== + * + * Copyright (c) 2008 - 2011 LSC Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + + *    * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + *     * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + *     * Neither the name of the LSC Project nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ==LICENSE NOTICE== + * + * (c) 2008 - 2011 LSC Project + **************************************************************************** + */ +package org.lsc; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.lang.ProcessBuilder; +import java.io.OutputStream; +import java.io.IOException; +import java.util.Optional; +import org.lsc.utils.output.LdifLayout; +import org.lsc.beans.syncoptions.ISyncOptions.OutputFormat; +import com.fasterxml.jackson.databind.ObjectMapper; // For encoding object to JSON +import com.fasterxml.jackson.databind.ObjectWriter; + + +/** + * This object is managing posthook scripts + */ +public class Hooks { + + static final Logger LOGGER = LoggerFactory.getLogger(Hooks.class); + + private static final ObjectMapper Mapper = new ObjectMapper(); + /** + * Method calling a postSyncHook if necessary + * + * return nothing + */ + public final void postSyncHook(final Optional hook, final OutputFormat outputFormat, final LscModifications lm) { + + hook.ifPresent( h -> callHook(lm.getOperation(), h, lm.getMainIdentifier(), outputFormat, lm)); + } + + public final static void callHook( LscModificationType operationType, + String hook, + String identifier, + OutputFormat outputFormat, + LscModifications lm) { + + LOGGER.info("Calling {} posthook {} with format {} for {}", + operationType.getDescription(), + hook, + outputFormat.toString(), + identifier); + + String modifications = null; + // Compute modifications only in a create / update / changeid operation + if( operationType != LscModificationType.DELETE_OBJECT) + { + if( outputFormat == OutputFormat.JSON ) { + modifications = getJsonModifications(lm); + } + else { + modifications = LdifLayout.format(lm); + } + } + + try { + if( modifications != null ) { + Process p = new ProcessBuilder( + hook, + identifier, + operationType.getDescription()) + .start(); + + // sends ldif modifications to stdin of hook script + OutputStream stdin = p.getOutputStream(); + stdin.write(modifications.getBytes()); + stdin.write("\n".getBytes()); + stdin.flush(); + stdin.close(); + } + else { + Process p = new ProcessBuilder( + hook, + identifier, + operationType.getDescription()) + .start(); + } + } + catch(IOException e) { + LOGGER.error("Error while calling {} posthook {} with format {} for {}", + operationType.getDescription(), + hook, + outputFormat.toString(), + identifier, + e); + } + } + + /** + * Method computing modifications as json + * + * @return modifications in a json String + */ + public final static String getJsonModifications(final LscModifications lm) { + ObjectWriter ow = Mapper.writer().withDefaultPrettyPrinter(); + String json = ""; + try { + json = ow.writeValueAsString(lm.getLscAttributeModifications()); + } + catch(Exception e) { + LOGGER.error("Error while encoding LSC modifications to json", e); + } + return json; + } + +} diff --git a/src/main/java/org/lsc/beans/syncoptions/ForceSyncOptions.java b/src/main/java/org/lsc/beans/syncoptions/ForceSyncOptions.java index f5f6c1ba..1bb474e0 100644 --- a/src/main/java/org/lsc/beans/syncoptions/ForceSyncOptions.java +++ b/src/main/java/org/lsc/beans/syncoptions/ForceSyncOptions.java @@ -47,6 +47,7 @@ import java.util.List; import java.util.Set; +import java.util.Optional; import org.lsc.LscModificationType; import org.lsc.configuration.PolicyType; @@ -128,6 +129,30 @@ public String getCondition(LscModificationType operation) { return DEFAULT_CONDITION; } + public OutputFormat getPostHookOutputFormat() { + return OutputFormat.LDIF; + } + + public Optional getCreatePostHook() { + return Optional.empty(); + } + + public Optional getDeletePostHook() { + return Optional.empty(); + } + + public Optional getUpdatePostHook() { + return Optional.empty(); + } + + public Optional getChangeIdPostHook() { + return Optional.empty(); + } + + public Optional getPostHook(LscModificationType operation) { + return Optional.empty(); + } + public String getDn() { return null;//((Ldap)task.getDestinationService()).getDn(); } diff --git a/src/main/java/org/lsc/beans/syncoptions/ISyncOptions.java b/src/main/java/org/lsc/beans/syncoptions/ISyncOptions.java index 9df18858..69616755 100644 --- a/src/main/java/org/lsc/beans/syncoptions/ISyncOptions.java +++ b/src/main/java/org/lsc/beans/syncoptions/ISyncOptions.java @@ -47,6 +47,7 @@ import java.util.List; import java.util.Set; +import java.util.Optional; import org.lsc.LscModificationType; import org.lsc.configuration.PolicyType; @@ -60,6 +61,9 @@ public interface ISyncOptions { /** default condition if none is given */ public static final String DEFAULT_CONDITION = "true"; + + /** list of output formats */ + public enum OutputFormat { LDIF, JSON }; /** * Initialize the synchronization options policy. @@ -153,6 +157,44 @@ public interface ISyncOptions { String getCondition(LscModificationType operation); + /** + * Returns the posthook output format + * + * @return the posthook output format (default = OutputFormat.LDIF) + */ + OutputFormat getPostHookOutputFormat(); + + /** + * Returns the posthook for a creation + * + * @return the posthook or "" if none is specified (default) + */ + Optional getCreatePostHook(); + + /** + * Returns the posthook for an update + * + * @return the posthook or "" if none is specified (default) + */ + Optional getUpdatePostHook(); + + /** + * Returns the posthook for a delete + * + * @return the posthook or "" if none is specified (default) + */ + Optional getDeletePostHook(); + + /** + * Returns the posthook for a id change + * + * @return the posthook or "" if none is specified (default) + */ + Optional getChangeIdPostHook(); + + + Optional getPostHook(LscModificationType operation); + /** * Return the expression used to infer the new object DN * @return the new object dn pattern diff --git a/src/main/java/org/lsc/beans/syncoptions/PropertiesBasedSyncOptions.java b/src/main/java/org/lsc/beans/syncoptions/PropertiesBasedSyncOptions.java index aa04944d..42ff763f 100644 --- a/src/main/java/org/lsc/beans/syncoptions/PropertiesBasedSyncOptions.java +++ b/src/main/java/org/lsc/beans/syncoptions/PropertiesBasedSyncOptions.java @@ -49,6 +49,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.Optional; import org.lsc.LscModificationType; import org.lsc.configuration.DatasetType; @@ -196,6 +197,53 @@ public String getCondition(LscModificationType operation) { } return result; } + + public OutputFormat getPostHookOutputFormat() { + // default: returns LDIF format + if (conf.getHooks() == null || conf.getHooks().getOutputFormat() == null) { + return OutputFormat.LDIF; + } + switch(conf.getHooks().getOutputFormat()){ + case "json": + return OutputFormat.JSON; + default: + return OutputFormat.LDIF; + } + } + + public Optional getCreatePostHook() { + Optional hook = Optional.ofNullable(conf.getHooks().getCreatePostHook()).filter(s -> !s.isEmpty()); + return hook; + } + + public Optional getDeletePostHook() { + Optional hook = Optional.ofNullable(conf.getHooks().getDeletePostHook()).filter(s -> !s.isEmpty()); + return hook; + } + + public Optional getUpdatePostHook() { + Optional hook = Optional.ofNullable(conf.getHooks().getUpdatePostHook()).filter(s -> !s.isEmpty()); + return hook; + } + + public Optional getChangeIdPostHook() { + Optional hook = Optional.ofNullable(conf.getHooks().getChangeIdPostHook()).filter(s -> !s.isEmpty()); + return hook; + } + + public Optional getPostHook(LscModificationType operation) { + switch (operation) { + case CREATE_OBJECT: + return this.getCreatePostHook(); + case UPDATE_OBJECT: + return this.getUpdatePostHook(); + case DELETE_OBJECT: + return this.getDeletePostHook(); + case CHANGE_ID: + return this.getChangeIdPostHook(); + } + return Optional.empty(); + } public String getDelimiter(String name) { DatasetType dataset = LscConfiguration.getDataset(conf, name); diff --git a/src/main/java/org/lsc/configuration/.gitignore b/src/main/java/org/lsc/configuration/.gitignore index faf60484..e83453ba 100644 --- a/src/main/java/org/lsc/configuration/.gitignore +++ b/src/main/java/org/lsc/configuration/.gitignore @@ -4,6 +4,7 @@ /AuditType.java /AuditsType.java /ConditionsType.java +/HooksType.java /ConnectionType.java /ConnectionsType.java /CsvAuditType.java diff --git a/src/main/java/org/lsc/runnable/CleanEntryRunner.java b/src/main/java/org/lsc/runnable/CleanEntryRunner.java index 3d481a16..51d753d3 100644 --- a/src/main/java/org/lsc/runnable/CleanEntryRunner.java +++ b/src/main/java/org/lsc/runnable/CleanEntryRunner.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import javax.naming.CommunicationException; @@ -23,6 +24,8 @@ import org.lsc.utils.ScriptingEvaluator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.lsc.Hooks; +import org.lsc.beans.syncoptions.ISyncOptions.OutputFormat; /** * @author sfroger @@ -31,11 +34,13 @@ public class CleanEntryRunner extends AbstractEntryRunner { static final Logger LOGGER = LoggerFactory.getLogger(CleanEntryRunner.class); + private Hooks hooks; public CleanEntryRunner(final Task task, InfoCounter counter, AbstractSynchronize abstractSynchronize, Entry id) { super(task, counter, abstractSynchronize, id); + this.hooks = new Hooks(); } @Override @@ -108,6 +113,10 @@ public void run() { // if we got here, we have a modification to apply - let's // do it! if (task.getDestinationService().apply(lm)) { + // Retrieve posthook for the current operation + hooks.postSyncHook( syncOptions.getDeletePostHook(), + syncOptions.getPostHookOutputFormat(), + lm); counter.incrementCountCompleted(); abstractSynchronize.logAction(lm, id, task.getName()); } else { diff --git a/src/main/java/org/lsc/runnable/SynchronizeEntryRunner.java b/src/main/java/org/lsc/runnable/SynchronizeEntryRunner.java index dec1fb56..139a0f09 100644 --- a/src/main/java/org/lsc/runnable/SynchronizeEntryRunner.java +++ b/src/main/java/org/lsc/runnable/SynchronizeEntryRunner.java @@ -3,6 +3,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import org.lsc.AbstractSynchronize; import org.lsc.beans.InfoCounter; @@ -16,6 +17,8 @@ import org.lsc.utils.ScriptingEvaluator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.lsc.Hooks; +import org.lsc.beans.syncoptions.ISyncOptions.OutputFormat; /** * @author sbahloul @@ -24,6 +27,7 @@ public class SynchronizeEntryRunner extends AbstractEntryRunner { static final Logger LOGGER = LoggerFactory.getLogger(SynchronizeEntryRunner.class); private boolean fromSource; + private Hooks hooks; public SynchronizeEntryRunner(final Task task, InfoCounter counter, AbstractSynchronize abstractSynchronize, @@ -31,6 +35,7 @@ public SynchronizeEntryRunner(final Task task, InfoCounter counter, boolean fromSource) { super(task, counter, abstractSynchronize, id); this.fromSource = fromSource; + this.hooks = new Hooks(); } @Override @@ -137,6 +142,10 @@ public boolean run(IBean entry) { // if we got here, we have a modification to apply - let's do it! if (task.getDestinationService().apply(lm)) { + // Retrieve posthook for the current operation + hooks.postSyncHook( task.getSyncOptions().getPostHook(modificationType), + task.getSyncOptions().getPostHookOutputFormat(), + lm); counter.incrementCountCompleted(); abstractSynchronize.logAction(lm, id, syncName); return true; @@ -160,4 +169,5 @@ public boolean run(IBean entry) { } } -} \ No newline at end of file +} + diff --git a/src/main/resources/schemas/lsc-core-2.2.xsd b/src/main/resources/schemas/lsc-core-2.2.xsd index 968c0227..5aaf4e55 100644 --- a/src/main/resources/schemas/lsc-core-2.2.xsd +++ b/src/main/resources/schemas/lsc-core-2.2.xsd @@ -323,7 +323,17 @@ - + + + + + + + + + + + @@ -334,6 +344,7 @@ default="FORCE" /> + diff --git a/src/test/java/org/lsc/Ldap2LdapHookSyncTest.java b/src/test/java/org/lsc/Ldap2LdapHookSyncTest.java new file mode 100644 index 00000000..85cbce91 --- /dev/null +++ b/src/test/java/org/lsc/Ldap2LdapHookSyncTest.java @@ -0,0 +1,261 @@ +/* + **************************************************************************** + * Ldap Synchronization Connector provides tools to synchronize + * electronic identities from a list of data sources including + * any database with a JDBC connector, another LDAP directory, + * flat files... + * + * ==LICENSE NOTICE== + * + * Copyright (c) 2008 - 2011 LSC Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + + *    * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + *     * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + *     * Neither the name of the LSC Project nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ==LICENSE NOTICE== + * + * (c) 2008 - 2011 LSC Project + * Sebastien Bahloul <seb@lsc-project.org> + * Thomas Chemineau <thomas@lsc-project.org> + * Jonathan Clarke <jon@lsc-project.org> + * Remy-Christophe Schermesser <rcs@lsc-project.org> + **************************************************************************** + */ +package org.lsc; + +import com.fasterxml.jackson.databind.ObjectMapper; // For encoding object to JSON +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import java.io.StringWriter; +import java.io.PrintWriter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.lsc.configuration.LscConfiguration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.io.File; +import java.util.Scanner; + + +/** + * This test case attempts to reproduce a ldap2ldap setup via SimpleSynchronize. + * It attempts to launch the tasks defined in src/test/resources/etc/lsc.xml: + * ldap2ldapHookTestCreate ldap2ldapHookTestUpdate ldap2ldapHookTestDelete + */ +public class Ldap2LdapHookSyncTest extends CommonLdapSyncTest { + + + @Before + public void setup() { + LscConfiguration.reset(); + LscConfiguration.getInstance(); + Assert.assertNotNull(LscConfiguration.getConnection("src-ldap")); + Assert.assertNotNull(LscConfiguration.getConnection("dst-ldap")); + reloadJndiConnections(); + } + + @Test + public final void testLdap2LdapJSONHookSyncTest() throws Exception { + + // Declare the tasks to launch in the correct order + List sync_tasks = Arrays.asList("ldap2ldapJSONHookTestCreate", "ldap2ldapJSONHookTestUpdate"); + List clean_tasks = Arrays.asList("ldap2ldapJSONHookTestDelete"); + + // perform the sync + launchSyncCleanTask(sync_tasks, false, true, false); + launchSyncCleanTask(clean_tasks, false, false, true); + + // check the results of the synchronization + reloadJndiConnections(); + + ObjectMapper mapperCreatedEntry = new ObjectMapper(); + JsonNode expectedCreatedEntry = mapperCreatedEntry.readTree("[ { \"attributeName\" : \"objectClass\", \"values\" : [ \"inetOrgPerson\", \"person\", \"top\" ], \"operation\" : \"ADD_VALUES\" }, { \"attributeName\" : \"cn\", \"values\" : [ \"CN0001-hook\" ], \"operation\" : \"ADD_VALUES\" }, { \"attributeName\" : \"sn\", \"values\" : [ \"CN0001-hook\" ], \"operation\" : \"ADD_VALUES\" } ]"); + + checkJSONSyncResults("create", expectedCreatedEntry); + + ObjectMapper mapperUpdatedEntry = new ObjectMapper(); + JsonNode expectedUpdatedEntry = mapperUpdatedEntry.readTree("[ { \"attributeName\" : \"description\", \"values\" : [ \"CN0001-hook\" ], \"operation\" : \"REPLACE_VALUES\" }, {\"attributeName\":\"userCertificate;binary\",\"values\":[\"MIIDkTCCAnmgAwIBAgIUDhx/9qofTrT+yNFFvihdDn7rjOQwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCRlIxDTALBgNVBAgMBHRlc3QxDTALBgNVBAcMBHRlc3QxDTALBgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxDTALBgNVBAMMBHRlc3QwHhcNMjMxMDI3MTQzMDQxWhcNMzMxMDI0MTQzMDQxWjBYMQswCQYDVQQGEwJGUjENMAsGA1UECAwEdGVzdDENMAsGA1UEBwwEdGVzdDENMAsGA1UECgwEdGVzdDENMAsGA1UECwwEdGVzdDENMAsGA1UEAwwEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKdWt0QgFnEi7a1hIJQv4ZdOM5y0GGLHQYNrUNSReArvpkYUY5zasFNVzVHCApRuj0t1NMDrn1gNzKkxTIbYGaWRSn+21J0ow+Nxh2TAQW8dkJnWTksCfyGGGItI5q3ST3EUKnepaAzUYYENcSHRyx7UY/3XuzcW0aGhy4PrVTIHBpyLq0Uzv8nH5nbWM+LYt6YbQMmlAz/psTXIC2dfEZhUb4plLGSo7rZxM5geC6Z+os+I8+uw+mGjps1VP7eGq0jCGHNs2rUHMqBNgLvwMH2WlMXo/iNarAb8fUEPdp59FwiTygBlWAn6GoKHJ1HWPpqMxdtjL2Y5+ZMcp70eJqcCAwEAAaNTMFEwHQYDVR0OBBYEFLwffjUBL/Rp4a6MgeCJiFnCZFu8MB8GA1UdIwQYMBaAFLwffjUBL/Rp4a6MgeCJiFnCZFu8MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACjwsg1Z9PyauoKAhkIfyPTEnlEOCo1nN37c2vnH4fyY6fuBdV6GWtk/u9FCuDmYT/4KDRxe33ZUChwSUX0INgamOarWRES3UoPC1GeOvuMf7uustEMLcHAYZVKXSZUrsOjw+VIZ5XrD6GDE64QtvW5Ve3jf43aGgLf27NF0vhF9+gHOZjjBT33S977HUutMUKfRu9PdHAn8Yb1FmSbAvqqK+SAjn6cJC8l5yS5t0BSNQGbKSA8bPzvWI9HXYVvb+ym6GDrsr+Zad3NrqUSZGzS2JFEDVD9aAikldXu6g02fA5A7nufVePmaG7iTyylO/ZU2lTiJ0SHc2DnO0pg2i+0=\"],\"operation\":\"REPLACE_VALUES\"} ]"); + + checkJSONSyncResults("update", expectedUpdatedEntry); + + checkJSONSyncResults("delete", null); + } + + @Test + public final void testLdap2LdapLDIFHookSyncTest() throws Exception { + + // Declare the tasks to launch in the correct order + List sync_tasks = Arrays.asList("ldap2ldapLDIFHookTestCreate", "ldap2ldapLDIFHookTestUpdate"); + List clean_tasks = Arrays.asList("ldap2ldapLDIFHookTestDelete"); + + // perform the sync + launchSyncCleanTask(sync_tasks, false, true, false); + launchSyncCleanTask(clean_tasks, false, false, true); + + // check the results of the synchronization + reloadJndiConnections(); + + List expectedCreatedEntry = Arrays.asList( + "dn: cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org", + "changetype: add", + "objectClass: inetOrgPerson", + "objectClass: person", + "objectClass: top", + "cn: CN0001-hook", + "sn: CN0001-hook" + ); + + checkLDIFSyncResults("create", expectedCreatedEntry); + + List expectedUpdatedEntry = Arrays.asList( + "dn: cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org", + "changetype: modify", + "replace: description", + "description: CN0001-hook" + ); + + checkLDIFSyncResults("update", expectedUpdatedEntry); + + checkLDIFSyncResults("delete", new ArrayList()); + } + + /* + * Read hook log file to check passed arguments and modification passed as input + */ + private final void checkLDIFSyncResults(String operation, List expectedEntry) throws Exception { + + List hookResults = new ArrayList(); + try { + File hookFile = new File("hook-ldif-" + operation + ".log"); + Scanner hookReader = new Scanner(hookFile); + + while (hookReader.hasNextLine()) { + String data = hookReader.nextLine(); + hookResults.add(data); + } + hookReader.close(); + hookFile.delete(); // remove hook log + } catch (Exception e) { + fail("Error while reading hook-ldif-" + operation + ".log"); + } + assertEquals(hookResults.get(0), "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org"); + assertEquals(hookResults.get(1), operation); + + if(operation != "delete") { + // Make sure all attributes in expectedEntry are present in the hook file + List entry = new ArrayList(hookResults.subList(3, (hookResults.size()-1))); + for (String attr : expectedEntry) { + assertTrue("Attribute " + attr + " not found in " + operation + " entry " + entry.toString(), entry.contains(attr)); + } + } + + } + + /* + * Read hook log file to check passed arguments and modification passed as input + */ + private final void checkJSONSyncResults(String operation, JsonNode expectedEntry) throws Exception { + + List hookResults = new ArrayList(); + try { + File hookFile = new File("hook-json-" + operation + ".log"); + Scanner hookReader = new Scanner(hookFile); + + while (hookReader.hasNextLine()) { + String data = hookReader.nextLine(); + hookResults.add(data); + } + hookReader.close(); + hookFile.delete(); // remove hook log + } catch (Exception e) { + fail("Error while reading hook-json-" + operation + ".log"); + } + assertEquals(hookResults.get(0), "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org"); + assertEquals(hookResults.get(1), operation); + + if(operation != "delete") { + // Make sure all attributes in expectedEntry are present in the hook file + String entry = String.join("", new ArrayList(hookResults.subList(2, hookResults.size()))); + + ObjectMapper mapper = new ObjectMapper(); + JsonFactory factory = mapper.getJsonFactory(); + JsonParser jp = factory.createJsonParser(entry); + try { + JsonNode hookOperation = mapper.readTree(jp); + assertEquals(hookOperation, expectedEntry); + } + catch (Exception e) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + fail("Error while decoding operation in hook-json-" + operation + ".log as JSON:" + sw.toString()); + } + } + + } + + public static void launchSyncCleanTask(List tasks, boolean doAsync, boolean doSync, + boolean doClean) throws Exception { + // initialize required stuff + SimpleSynchronize sync = new SimpleSynchronize(); + List asyncType = new ArrayList(); + List syncType = new ArrayList(); + List cleanType = new ArrayList(); + + + if (doAsync) { + for(String taskName: tasks) { + asyncType.add(taskName); + } + } + + if (doSync) { + for(String taskName: tasks) { + syncType.add(taskName); + } + } + + if (doClean) { + for(String taskName: tasks) { + cleanType.add(taskName); + } + } + + boolean ret = sync.launch(asyncType, syncType, cleanType); + assertTrue(ret); + } +} diff --git a/src/test/resources/bin/hook-json.sh b/src/test/resources/bin/hook-json.sh new file mode 100755 index 00000000..42f73ee8 --- /dev/null +++ b/src/test/resources/bin/hook-json.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Parse arguments +for (( i=1; i <= $#; i++ )); do + eval arg='$'$i + echo $arg >> hook-json-$2.log +done + +# Parse input +while IFS= read line; do +echo "$line" >> hook-json-$2.log +done + diff --git a/src/test/resources/bin/hook-ldif.sh b/src/test/resources/bin/hook-ldif.sh new file mode 100755 index 00000000..b57873d5 --- /dev/null +++ b/src/test/resources/bin/hook-ldif.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Parse arguments +for (( i=1; i <= $#; i++ )); do + eval arg='$'$i + echo $arg >> hook-ldif-$2.log +done + +# Parse input +while IFS= read line; do +echo "$line" >> hook-ldif-$2.log +done + diff --git a/src/test/resources/etc/lsc.xml b/src/test/resources/etc/lsc.xml index 43230b9e..8073fd36 100644 --- a/src/test/resources/etc/lsc.xml +++ b/src/test/resources/etc/lsc.xml @@ -855,6 +855,375 @@ + + ldap2ldapLDIFHookTestCreate + org.lsc.beans.SimpleBean + + ldap2ldapLDIFHookTestCreate-src + + ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org + + cn + + + cn + + (cn=CN0001) + (cn=CN0001) + (cn=CN0001) + yyyyMMddHHmmss'Z' + 5 + + + ldap2ldapLDIFHookTestCreate-dst + + ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org + + cn + + + cn + sn + objectClass + + (cn=CN0001-hook) + (cn=CN0001-hook) + + + "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org" + , + KEEP + + ldif + bin/hook-ldif.sh + + + objectclass + FORCE + + "inetOrgPerson" + "person" + "top" + + + + cn + FORCE + + "CN0001-hook" + + + + sn + FORCE + + "CN0001-hook" + + + + + + ldap2ldapLDIFHookTestUpdate + org.lsc.beans.SimpleBean + + ldap2ldapLDIFHookTestUpdate-src + + ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org + + cn + + + cn + + (cn=CN0001) + (cn=CN0001) + (cn=CN0001) + yyyyMMddHHmmss'Z' + 5 + + + ldap2ldapLDIFHookTestUpdate-dst + + ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org + + cn + + + description + + (cn=CN0001-hook) + (cn=CN0001-hook) + + + "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org" + , + KEEP + + ldif + bin/hook-ldif.sh + + + description + FORCE + + "CN0001-hook" + + + + + + ldap2ldapLDIFHookTestDelete + org.lsc.beans.SimpleBean + + ldap2ldapLDIFHookTestDelete-src + + ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org + + cn + + + cn + + (cn=7SlBBCtxp0ILsbBEp_DeUjS69rpgpWIbOfhXLGvP2HvGY_g_XecLW9J8ymR5b7qN) + (cn=7SlBBCtxp0ILsbBEp_DeUjS69rpgpWIbOfhXLGvP2HvGY_g_XecLW9J8ymR5b7qN) + (cn=7SlBBCtxp0ILsbBEp_DeUjS69rpgpWIbOfhXLGvP2HvGY_g_XecLW9J8ymR5b7qN) + yyyyMMddHHmmss'Z' + 5 + + + ldap2ldapLDIFHookTestDelete-dst + + ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org + + cn + + + cn + sn + description + objectClass + + (cn=CN0001-hook) + (cn=CN0001-hook) + + + "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org" + , + KEEP + + ldif + bin/hook-ldif.sh + + + objectclass + FORCE + + "inetOrgPerson" + "person" + "top" + + + + cn + FORCE + + "CN0001-hook" + + + + + + ldap2ldapJSONHookTestCreate + org.lsc.beans.SimpleBean + + ldap2ldapJSONHookTestCreate-src + + ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org + + cn + + + cn + + (cn=CN0001) + (cn=CN0001) + (cn=CN0001) + yyyyMMddHHmmss'Z' + 5 + + + ldap2ldapJSONHookTestCreate-dst + + ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org + + cn + + + cn + sn + objectClass + + (cn=CN0001-hook) + (cn=CN0001-hook) + + + "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org" + , + KEEP + + json + bin/hook-json.sh + + + objectclass + FORCE + + "inetOrgPerson" + "person" + "top" + + + + cn + FORCE + + "CN0001-hook" + + + + sn + FORCE + + "CN0001-hook" + + + + + + ldap2ldapJSONHookTestUpdate + org.lsc.beans.SimpleBean + + ldap2ldapJSONHookTestUpdate-src + + ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org + + cn + + + cn + + (cn=CN0001) + (cn=CN0001) + (cn=CN0001) + yyyyMMddHHmmss'Z' + 5 + + + ldap2ldapJSONHookTestUpdate-dst + + ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org + + cn + + + description + userCertificate;binary + + (cn=CN0001-hook) + (cn=CN0001-hook) + + + "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org" + , + KEEP + + json + bin/hook-json.sh + + + description + FORCE + + "CN0001-hook" + + + + userCertificate;binary + FORCE + + + + + + + + + + ldap2ldapJSONHookTestDelete + org.lsc.beans.SimpleBean + + ldap2ldapJSONHookTestDelete-src + + ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org + + cn + + + cn + + (cn=7SlBBCtxp0ILsbBEp_DeUjS69rpgpWIbOfhXLGvP2HvGY_g_XecLW9J8ymR5b7qN) + (cn=7SlBBCtxp0ILsbBEp_DeUjS69rpgpWIbOfhXLGvP2HvGY_g_XecLW9J8ymR5b7qN) + (cn=7SlBBCtxp0ILsbBEp_DeUjS69rpgpWIbOfhXLGvP2HvGY_g_XecLW9J8ymR5b7qN) + yyyyMMddHHmmss'Z' + 5 + + + ldap2ldapJSONHookTestDelete-dst + + ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org + + cn + + + cn + sn + description + objectClass + + (cn=CN0001-hook) + (cn=CN0001-hook) + + + "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org" + , + KEEP + + json + bin/hook-json.sh + + + objectclass + FORCE + + "inetOrgPerson" + "person" + "top" + + + + cn + FORCE + + "CN0001-hook" + + + +