From 8c324a022d519687e22cecda2b56927de09fdcb9 Mon Sep 17 00:00:00 2001 From: Sylinsic <38617929+Sylinsic@users.noreply.github.com> Date: Thu, 1 Feb 2024 07:24:48 +0000 Subject: [PATCH] [LDAP-41] Add generic sync strategy (#27) --- .../bundles/ldap/LdapConfiguration.java | 15 + .../{sync/sunds => commons}/LdifParser.java | 22 +- .../sunds => commons}/PasswordDecryptor.java | 2 +- .../sync/GenericChangeLogSyncStrategy.java | 686 ++++++++++++++++++ .../ldap/sync/sunds/ChangeLogAttributes.java | 18 +- .../sunds/SunDSChangeLogSyncStrategy.java | 10 +- .../connid/bundles/ldap/Messages.properties | 3 + .../bundles/ldap/Messages_de.properties | 3 + .../bundles/ldap/Messages_es.properties | 3 + .../bundles/ldap/Messages_fr.properties | 3 + .../bundles/ldap/Messages_it.properties | 3 + .../bundles/ldap/Messages_ja.properties | 3 + .../bundles/ldap/Messages_ko.properties | 3 + .../bundles/ldap/Messages_pt.properties | 3 + .../bundles/ldap/Messages_zh.properties | 3 + .../bundles/ldap/Messages_zh_TW.properties | 6 +- .../sunds => commons}/LdifParserTests.java | 10 +- .../ldap/sync/sunds/LdapModifyUtils.java | 19 +- 18 files changed, 778 insertions(+), 37 deletions(-) rename src/main/java/net/tirasa/connid/bundles/ldap/{sync/sunds => commons}/LdifParser.java (96%) rename src/main/java/net/tirasa/connid/bundles/ldap/{sync/sunds => commons}/PasswordDecryptor.java (99%) create mode 100644 src/main/java/net/tirasa/connid/bundles/ldap/sync/GenericChangeLogSyncStrategy.java rename src/test/java/net/tirasa/connid/bundles/ldap/{sync/sunds => commons}/LdifParserTests.java (90%) diff --git a/src/main/java/net/tirasa/connid/bundles/ldap/LdapConfiguration.java b/src/main/java/net/tirasa/connid/bundles/ldap/LdapConfiguration.java index 29d705a..58b8f6f 100644 --- a/src/main/java/net/tirasa/connid/bundles/ldap/LdapConfiguration.java +++ b/src/main/java/net/tirasa/connid/bundles/ldap/LdapConfiguration.java @@ -205,6 +205,8 @@ public enum SearchScope { private String changeNumberAttribute = "changeNumber"; + private String changeLogContext = "cn=changelog"; + private boolean filterWithOrInsteadOfAnd; private boolean removeLogEntryObjectClassFromFilter = true; @@ -359,6 +361,8 @@ public void validate() { failValidation("changeLogBlockSize.legalValue"); } + checkNotBlank(changeLogContext, "changeLogContext.notBlank"); + if (synchronizePasswords) { checkNotBlank(passwordAttributeToSynchronize, "passwordAttributeToSynchronize.notBlank"); checkNotBlank(passwordDecryptionKey, "decryptionKey.notBlank"); @@ -888,6 +892,17 @@ public void setChangeNumberAttribute(String changeNumberAttribute) { this.changeNumberAttribute = changeNumberAttribute; } + @ConfigurationProperty(order = 39, operations = { SyncOp.class }, + displayMessageKey = "changeLogContext.display", + helpMessageKey = "changeNumberAttribute.help") + public String getChangeLogContext() { + return changeLogContext; + } + + public void setChangeLogContext(String changeLogContext) { + this.changeLogContext = changeLogContext; + } + @ConfigurationProperty(order = 39, operations = { SyncOp.class }, displayMessageKey = "filterWithOrInsteadOfAnd.display", helpMessageKey = "filterWithOrInsteadOfAnd.help") diff --git a/src/main/java/net/tirasa/connid/bundles/ldap/sync/sunds/LdifParser.java b/src/main/java/net/tirasa/connid/bundles/ldap/commons/LdifParser.java similarity index 96% rename from src/main/java/net/tirasa/connid/bundles/ldap/sync/sunds/LdifParser.java rename to src/main/java/net/tirasa/connid/bundles/ldap/commons/LdifParser.java index b3ea61a..a927a57 100644 --- a/src/main/java/net/tirasa/connid/bundles/ldap/sync/sunds/LdifParser.java +++ b/src/main/java/net/tirasa/connid/bundles/ldap/commons/LdifParser.java @@ -1,18 +1,18 @@ -/* +/* * ==================== * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * + * * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. - * + * * The contents of this file are subject to the terms of the Common Development * and Distribution License("CDDL") (the "License"). You may not use this file * except in compliance with the License. - * + * * You can obtain a copy of the License at * http://opensource.org/licenses/cddl1.php * See the License for the specific language governing permissions and limitations * under the License. - * + * * When distributing the Covered Code, include this CDDL Header Notice in each file * and include the License file at http://opensource.org/licenses/cddl1.php. * If applicable, add the following below this CDDL Header, with the fields @@ -21,7 +21,7 @@ * ==================== * Portions Copyrighted 2011 ConnId. */ -package net.tirasa.connid.bundles.ldap.sync.sunds; +package net.tirasa.connid.bundles.ldap.commons; import java.util.ArrayList; import java.util.Iterator; @@ -36,13 +36,14 @@ public LdifParser(String ldif) { this.ldif = ldif; } + @Override public Iterator iterator() { return new LineIterator(getUnfoldedLines()); } private List getUnfoldedLines() { String[] lines = ldif.split("\n", -1); - ArrayList result = new ArrayList(lines.length); + ArrayList result = new ArrayList<>(lines.length); StringBuilder builder = null; for (String line : lines) { if (line.startsWith(" ")) { @@ -68,13 +69,16 @@ private List getUnfoldedLines() { private static final class LineIterator implements Iterator { private final Iterator rawLines; + private Line lastLine; + private Line next; public LineIterator(List rawLines) { this.rawLines = rawLines.iterator(); } + @Override public boolean hasNext() { if (next == null) { next = getNext(); @@ -82,6 +86,7 @@ public boolean hasNext() { return next != null; } + @Override public Line next() { if (next == null) { next = getNext(); @@ -119,18 +124,19 @@ private Line getNext() { return result; } + @Override public void remove() { throw new UnsupportedOperationException(); } } public abstract static class Line { - } public final static class NameValue extends Line { private final String name; + private final String value; public NameValue(String name, String value) { diff --git a/src/main/java/net/tirasa/connid/bundles/ldap/sync/sunds/PasswordDecryptor.java b/src/main/java/net/tirasa/connid/bundles/ldap/commons/PasswordDecryptor.java similarity index 99% rename from src/main/java/net/tirasa/connid/bundles/ldap/sync/sunds/PasswordDecryptor.java rename to src/main/java/net/tirasa/connid/bundles/ldap/commons/PasswordDecryptor.java index ad21bb7..82e949b 100644 --- a/src/main/java/net/tirasa/connid/bundles/ldap/sync/sunds/PasswordDecryptor.java +++ b/src/main/java/net/tirasa/connid/bundles/ldap/commons/PasswordDecryptor.java @@ -21,7 +21,7 @@ * ==================== * Portions Copyrighted 2011 ConnId. */ -package net.tirasa.connid.bundles.ldap.sync.sunds; +package net.tirasa.connid.bundles.ldap.commons; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; diff --git a/src/main/java/net/tirasa/connid/bundles/ldap/sync/GenericChangeLogSyncStrategy.java b/src/main/java/net/tirasa/connid/bundles/ldap/sync/GenericChangeLogSyncStrategy.java new file mode 100644 index 0000000..61d98a8 --- /dev/null +++ b/src/main/java/net/tirasa/connid/bundles/ldap/sync/GenericChangeLogSyncStrategy.java @@ -0,0 +1,686 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2011 ConnId. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + */ +package net.tirasa.connid.bundles.ldap.sync; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.naming.InvalidNameException; +import javax.naming.directory.SearchControls; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; +import net.tirasa.connid.bundles.ldap.LdapConnection; +import net.tirasa.connid.bundles.ldap.commons.LdapEntry; +import net.tirasa.connid.bundles.ldap.commons.LdapUtil; +import net.tirasa.connid.bundles.ldap.commons.LdifParser; +import net.tirasa.connid.bundles.ldap.commons.LdifParser.ChangeSeparator; +import net.tirasa.connid.bundles.ldap.commons.LdifParser.Line; +import net.tirasa.connid.bundles.ldap.commons.LdifParser.NameValue; +import net.tirasa.connid.bundles.ldap.commons.LdifParser.Separator; +import net.tirasa.connid.bundles.ldap.commons.PasswordDecryptor; +import net.tirasa.connid.bundles.ldap.search.LdapFilter; +import net.tirasa.connid.bundles.ldap.search.LdapInternalSearch; +import net.tirasa.connid.bundles.ldap.search.LdapSearch; +import net.tirasa.connid.bundles.ldap.search.LdapSearches; +import org.identityconnectors.common.CollectionUtil; +import org.identityconnectors.common.StringUtil; +import org.identityconnectors.common.logging.Log; +import org.identityconnectors.common.security.GuardedString; +import org.identityconnectors.framework.common.exceptions.ConnectorException; +import org.identityconnectors.framework.common.objects.Attribute; +import org.identityconnectors.framework.common.objects.AttributeBuilder; +import org.identityconnectors.framework.common.objects.ConnectorObject; +import org.identityconnectors.framework.common.objects.ConnectorObjectBuilder; +import org.identityconnectors.framework.common.objects.ObjectClass; +import org.identityconnectors.framework.common.objects.OperationOptions; +import org.identityconnectors.framework.common.objects.OperationalAttributes; +import org.identityconnectors.framework.common.objects.SyncDelta; +import org.identityconnectors.framework.common.objects.SyncDeltaBuilder; +import org.identityconnectors.framework.common.objects.SyncDeltaType; +import org.identityconnectors.framework.common.objects.SyncResultsHandler; +import org.identityconnectors.framework.common.objects.SyncToken; +import org.identityconnectors.framework.common.objects.Uid; + +/** + * An implementation of the sync operation based on the retro change log + * plugin of Sun Directory Server. + */ +public class GenericChangeLogSyncStrategy implements LdapSyncStrategy { + + // TODO detect that the change log has been trimmed. + private static final Log LOG = Log.getLog(GenericChangeLogSyncStrategy.class); + + /** + * The list of attribute operations supported by the "modify" LDIF change type. + */ + private static final Set LDIF_MODIFY_OPS; + + private static final Set LDAP_DN_ATTRIBUTES; + + private final LdapConnection conn; + + private Set oclassesToSync; + + private Set attrsToSync; + + private PasswordDecryptor passwordDecryptor; + + static { + LDIF_MODIFY_OPS = CollectionUtil.newCaseInsensitiveSet(); + LDIF_MODIFY_OPS.add("add"); + LDIF_MODIFY_OPS.add("delete"); + LDIF_MODIFY_OPS.add("replace"); + + LDAP_DN_ATTRIBUTES = CollectionUtil.newCaseInsensitiveSet(); + LDAP_DN_ATTRIBUTES.add("uid"); + LDAP_DN_ATTRIBUTES.add("cn"); + } + + public GenericChangeLogSyncStrategy(LdapConnection conn) { + this.conn = conn; + } + + @Override + public SyncToken getLatestSyncToken(ObjectClass oclass) { + String changeNumberAttr = getChangeNumberAttribute(); + SearchControls controls = LdapInternalSearch.createDefaultSearchControls(); + controls.setSearchScope(SearchControls.ONELEVEL_SCOPE); + controls.setReturningAttributes(new String[] { + changeNumberAttr }); + + LdapInternalSearch search = new LdapInternalSearch( + conn, + "(objectClass=changelogEntry)", + Collections.singletonList(conn.getConfiguration().getChangeLogContext()), + conn.getConfiguration().newDefaultSearchStrategy(false), + controls); + + final int[] maxChangeNumber = { 0 }; + + search.execute((baseDN, result) -> { + final LdapEntry entry = LdapEntry.create(baseDN, result); + int changeNumber = convertToInt(LdapUtil.getStringAttrValue(entry.getAttributes(), changeNumberAttr), -1); + if (changeNumber > maxChangeNumber[0]) { + maxChangeNumber[0] = changeNumber; + } + + return true; + }); + + return new SyncToken(maxChangeNumber); + } + + @Override + public void sync( + final SyncToken token, + final SyncResultsHandler handler, + final OperationOptions options, + final ObjectClass oclass) { + final String changeNumberAttr = getChangeNumberAttribute(); + SearchControls controls = LdapInternalSearch.createDefaultSearchControls(); + controls.setSearchScope(SearchControls.ONELEVEL_SCOPE); + + controls.setReturningAttributes(new String[] { + changeNumberAttr, + "targetDN", + "targetEntryUUID", + "changeType", + "changes", + "newRdn", + "deleteOldRdn", + "newSuperior" }); + + final int[] currentChangeNumber = { getStartChangeNumber(token) }; + + final boolean[] results = new boolean[1]; + do { + results[0] = false; + + String filter = getChangeLogSearchFilter(changeNumberAttr, currentChangeNumber[0]); + + LdapInternalSearch search = new LdapInternalSearch( + conn, + filter, + Collections.singletonList(conn.getConfiguration().getChangeLogContext()), + conn.getConfiguration().newDefaultSearchStrategy(false), + controls); + + search.execute((baseDN, result) -> { + results[0] = true; + final LdapEntry entry = LdapEntry.create(baseDN, result); + + int changeNumber = convertToInt( + LdapUtil.getStringAttrValue(entry.getAttributes(), changeNumberAttr), -1); + + if (changeNumber > currentChangeNumber[0]) { + currentChangeNumber[0] = changeNumber; + } + + final SyncDelta delta = createSyncDelta(entry, changeNumber, options.getAttributesToGet(), oclass); + + if (delta != null) { + return handler.handle(delta); + } + return true; + }); + + // We have already processed the current change. + // In the next cycle we want to start with the next change. + if (results[0]) { + currentChangeNumber[0]++; + } + + } while (results[0]); + } + + private SyncDelta createSyncDelta( + final LdapEntry changeLogEntry, + final int changeNumber, + final String[] attrsToGetOption, + ObjectClass oclass) throws InvalidNameException { + + LOG.ok("Attempting to create sync delta for log entry {0}", changeNumber); + + final String targetDN = LdapUtil.getStringAttrValue(changeLogEntry.getAttributes(), "targetDN"); + + if (targetDN == null) { + LOG.error("Skipping log entry because it does not have a targetDN attribute"); + return null; + } + + final LdapName targetName = LdapUtil.quietCreateLdapName(targetDN); + + if (filterOutByBaseContexts(targetName)) { + LOG.ok("Skipping log entry because it does not match any of the " + + "base contexts to synchronize"); + return null; + } + + final String changeType = LdapUtil.getStringAttrValue(changeLogEntry.getAttributes(), "changeType"); + + SyncDeltaType deltaType = getSyncDeltaType(changeType); + + SyncDeltaBuilder syncDeltaBuilder = new SyncDeltaBuilder(); + syncDeltaBuilder.setToken(new SyncToken(changeNumber)); + syncDeltaBuilder.setDeltaType(deltaType); + + if (deltaType.equals(SyncDeltaType.DELETE)) { + LOG.ok("Creating sync delta for deleted entry {0}", + LdapUtil.getStringAttrValue(changeLogEntry.getAttributes(), "targetEntryUUID")); + + String uidAttr = conn.getSchema().getLdapUidAttribute(oclass); + + Uid deletedUid; + if (LDAP_DN_ATTRIBUTES.contains(uidAttr)) { + deletedUid = createUid(uidAttr, targetDN); + } else if ("entryUUID".equalsIgnoreCase(uidAttr)) { + deletedUid = new Uid(LdapUtil.getStringAttrValue(changeLogEntry.getAttributes(), "targetEntryUUID")); + } else { + // ever fallback to dn without throwing any exception more reliable + deletedUid = new Uid(targetDN); + } + // Build an empty connector object, with minimal information - LDAP-8 + ConnectorObjectBuilder objectBuilder = new ConnectorObjectBuilder(); + objectBuilder.setObjectClass(oclass); + objectBuilder.setUid(deletedUid); + objectBuilder.setName("fake-dn"); + objectBuilder.addAttributes(Collections.emptySet()); + + syncDeltaBuilder.setUid(deletedUid); + syncDeltaBuilder.setObject(objectBuilder.build()); + + return syncDeltaBuilder.build(); + } + + final String changes = LdapUtil.getStringAttrValue(changeLogEntry.getAttributes(), "changes"); + + final Map> attrChanges = getAttributeChanges(changeType, changes); + + if (filterOutByModifiersNames(attrChanges)) { + LOG.ok("Skipping entry because modifiersName is in the list of " + + "modifiersName's to filter out"); + return null; + } + + if (filterOutByAttributes(attrChanges)) { + LOG.ok("Skipping entry because no changed attributes in the list " + + "of attributes to synchronize"); + return null; + } + + // If the change type was modrdn, we need to compute the DN that the entry + // was modified to. + String newTargetDN = targetDN; + + if ("modrdn".equalsIgnoreCase(changeType)) { + final String newRdn = LdapUtil.getStringAttrValue(changeLogEntry.getAttributes(), "newRdn"); + + if (StringUtil.isBlank(newRdn)) { + LOG.error("Skipping log entry because it does not have a newRdn attribute"); + return null; + } + + final String newSuperior = LdapUtil.getStringAttrValue(changeLogEntry.getAttributes(), "newSuperior"); + + newTargetDN = getNewTargetDN(targetName, newSuperior, newRdn); + } + + // Always specify the attributes to get. This will return attributes with + // empty values when the attribute is not present, allowing the client to + // detect that the attribute has been removed. + Set attrsToGet; + + if (attrsToGetOption != null) { + attrsToGet = CollectionUtil.newSet(attrsToGetOption); + // Do not retrieve the password attribute from the entry (usually it is an unusable + // hashed value anyway). We will use the one from the change log below. + attrsToGet.remove(OperationalAttributes.PASSWORD_NAME); + } else { + attrsToGet = CollectionUtil.newSet(LdapSearch.getAttributesReturnedByDefault(conn, oclass)); + } + // If objectClass is not in the list of attributes to get, prepare to remove it later. + boolean removeObjectClass = attrsToGet.add("objectClass"); + + LdapFilter filter = LdapFilter.forEntryDN(newTargetDN).withNativeFilter(getModifiedEntrySearchFilter(oclass)); + + ConnectorObject object = LdapSearches.findObject(conn, oclass, filter, attrsToGet.toArray(new String[0])); + + if (object == null) { + LOG.ok("Skipping entry because the modified entry is missing, " + + "not of the right object class, or not matching the search filter"); + return null; + } + + Attribute oclassAttr = object.getAttributeByName("objectClass"); + + final List objectClasses = LdapUtil.checkedListByFilter( + CollectionUtil.nullAsEmpty(oclassAttr.getValue()), String.class); + + if (filterOutByObjectClasses(objectClasses)) { + LOG.ok("Skipping entry because no object class in the list of " + + "object classes to synchronize"); + return null; + } + + Attribute passwordAttr = null; + + if (conn.getConfiguration().isSynchronizePasswords()) { + final List passwordValues = attrChanges.get( + conn.getConfiguration().getPasswordAttributeToSynchronize()); + + if (!passwordValues.isEmpty()) { + byte[] encryptedPwd = (byte[]) passwordValues.get(0); + + final String decryptedPwd = getPasswordDecryptor().decryptPassword(encryptedPwd); + + passwordAttr = AttributeBuilder.buildPassword(new GuardedString(decryptedPwd.toCharArray())); + } + } + + if (removeObjectClass || passwordAttr != null) { + ConnectorObjectBuilder objectBuilder = new ConnectorObjectBuilder(); + objectBuilder.setObjectClass(object.getObjectClass()); + objectBuilder.setUid(object.getUid()); + objectBuilder.setName(object.getName()); + + if (removeObjectClass) { + for (Attribute attr : object.getAttributes()) { + if (attr != oclassAttr) { + objectBuilder.addAttribute(attr); + } + } + } else { + objectBuilder.addAttributes(object.getAttributes()); + } + + if (passwordAttr != null) { + objectBuilder.addAttribute(passwordAttr); + } + + object = objectBuilder.build(); + } + + LOG.ok("Creating sync delta for created or updated entry"); + + if ("modrdn".equalsIgnoreCase(changeType)) { + String uidAttr = conn.getSchema().getLdapUidAttribute(oclass); + + // We can only set the previous Uid if it is the entry DN, + // which is readily available. + if (LdapEntry.isDNAttribute(uidAttr)) { + syncDeltaBuilder.setPreviousUid(conn.getSchema().createUid(oclass, targetDN)); + } + } + + syncDeltaBuilder.setUid(object.getUid()); + syncDeltaBuilder.setObject(object); + + return syncDeltaBuilder.build(); + } + + private String getNewTargetDN( + final LdapName targetName, + final String newSuperior, + final String newRdn) { + + try { + LdapName newTargetName; + if (newSuperior == null) { + newTargetName = new LdapName(targetName.getRdns()); + if (newTargetName.size() > 0) { + newTargetName.remove(targetName.size() - 1); + } + } else { + newTargetName = LdapUtil.quietCreateLdapName(newSuperior); + } + newTargetName.add(newRdn); + return newTargetName.toString(); + } catch (InvalidNameException e) { + throw new ConnectorException(e); + } + } + + private boolean filterOutByBaseContexts(final LdapName targetName) { + List baseContexts = conn.getConfiguration(). + getBaseContextsToSynchronizeAsLdapNames(); + if (baseContexts.isEmpty()) { + baseContexts = conn.getConfiguration().getBaseContextsAsLdapNames(); + } + return !LdapUtil.isUnderContexts(targetName, baseContexts); + } + + private boolean filterOutByModifiersNames( + final Map> changes) { + Set filter = conn.getConfiguration(). + getModifiersNamesToFilterOutAsLdapNames(); + if (filter.isEmpty()) { + LOG.ok("Filtering by modifiersName disabled"); + return false; + } + List modifiersNameValues = changes.get("modifiersName"); + if (CollectionUtil.isEmpty(modifiersNameValues)) { + LOG.ok("Not filtering by modifiersName because not set for this entry"); + return false; + } + LdapName modifiersName = LdapUtil.quietCreateLdapName(modifiersNameValues.get(0).toString()); + return filter.contains(modifiersName); + } + + private boolean filterOutByAttributes( + final Map> attrChanges) { + Set filter = getAttributesToSynchronize(); + if (filter.isEmpty()) { + LOG.ok("Filtering by attributes disabled"); + return false; + } + Set changedAttrs = attrChanges.keySet(); + return !containsAny(filter, changedAttrs); + } + + private boolean filterOutByObjectClasses( + final List objectClasses) { + Set filter = getObjectClassesToSynchronize(); + if (filter.isEmpty()) { + LOG.ok("Filtering by object class disabled"); + return false; + } + return !containsAny(filter, objectClasses); + } + + private SyncDeltaType getSyncDeltaType(final String changeType) { + if ("delete".equalsIgnoreCase(changeType)) { + return SyncDeltaType.DELETE; + } + return SyncDeltaType.CREATE_OR_UPDATE; + } + + private String getModifiedEntrySearchFilter(ObjectClass oclass) { + if (oclass.equals(ObjectClass.ACCOUNT)) { + return conn.getConfiguration().getAccountSynchronizationFilter(); + } + return null; + } + + private int getStartChangeNumber(final SyncToken lastToken) { + return lastToken != null ? (Integer) lastToken. + getValue() + 1 : 0; + } + + private Map> getAttributeChanges(final String changeType, final String ldif) { + final Map> result = CollectionUtil.newCaseInsensitiveMap(); + + if ("modify".equalsIgnoreCase(changeType)) { + LdifParser parser = new LdifParser(ldif); + Iterator lines = parser.iterator(); + while (lines.hasNext()) { + Line line = lines.next(); + // We only expect one change, so ignore any change separators. + if (line instanceof Separator || line instanceof ChangeSeparator) { + continue; + } + NameValue nameValue = (NameValue) line; + String operation = nameValue.getName(); + String attrName = nameValue.getValue(); + if (LDIF_MODIFY_OPS.contains(operation)) { + List values = new ArrayList<>(); + while (lines.hasNext()) { + line = lines.next(); + if (line instanceof Separator) { + break; + } + nameValue = (NameValue) line; + Object value = decodeAttributeValue(nameValue); + if (value != null) { + values.add(value); + } + } + if (!values.isEmpty()) { + result.put(attrName, values); + } + } + } + } else if ("add".equalsIgnoreCase(changeType)) { + LdifParser parser = new LdifParser(ldif); + for (Line line : parser) { + // We only expect one change, so ignore any change separators. + if (line instanceof Separator || line instanceof ChangeSeparator) { + continue; + } + NameValue nameValue = (NameValue) line; + Object value = decodeAttributeValue(nameValue); + if (value != null) { + List values = result.get(nameValue.getName()); + if (values == null) { + values = new ArrayList<>(); + result.put(nameValue.getName(), values); + } + values.add(value); + } + } + } + + // Returning an empty map when changeType is "delete" or "modrdn". + return result; + } + + private Object decodeAttributeValue(final NameValue nameValue) { + String value = nameValue.getValue(); + if (value.startsWith(":")) { + // This is a Base64 encoded value... + String base64 = value.substring(1).trim(); + try { + return Base64.getDecoder().decode(base64); + // TODO the adapter had code here to convert the byte array + // to a string if the attribute was of a string type. Since + // that information is in the schema and we don't have access + // to the resource schema, leaving that functionality out for now. + } catch (Exception e) { + LOG.error("Could not decode attribute {0} with Base64 value {1}", + nameValue.getName(), nameValue.getValue()); + return null; + } + } else { + return value; + } + } + + private String getChangeLogSearchFilter( + final String changeNumberAttr, final int startChangeNumber) { + int blockSize = conn.getConfiguration().getChangeLogBlockSize(); + boolean filterWithOrInsteadOfAnd = conn.getConfiguration(). + isFilterWithOrInsteadOfAnd(); + boolean filterByLogEntryOClass = !conn.getConfiguration(). + isRemoveLogEntryObjectClassFromFilter(); + + StringBuilder result = new StringBuilder(); + if (filterWithOrInsteadOfAnd) { + if (filterByLogEntryOClass) { + result.append("(&(objectClass=changeLogEntry)"); + } + result.append("(|("); + result.append(changeNumberAttr); + result.append('='); + result.append(startChangeNumber); + result.append(')'); + + int endChangeNumber = startChangeNumber + blockSize; + for (int i = startChangeNumber + 1; i <= endChangeNumber; i++) { + result.append("("); + result.append(changeNumberAttr); + result.append('='); + result.append(i); + result.append(')'); + } + + result.append(')'); + if (filterByLogEntryOClass) { + result.append(')'); + } + } else { + result.append("(&"); + if (filterByLogEntryOClass) { + result.append("(objectClass=changeLogEntry)"); + } + result.append("("); + result.append(changeNumberAttr); + result.append(">="); + result.append(startChangeNumber); + result.append(')'); + + int endChangeNumber = startChangeNumber + blockSize; + result.append("("); + result.append(changeNumberAttr); + result.append("<="); + result.append(endChangeNumber); + result.append(')'); + + result.append(')'); + } + + return result.toString(); + } + + private String getChangeNumberAttribute() { + String result = conn.getConfiguration().getChangeNumberAttribute(); + if (StringUtil.isBlank(result)) { + result = "changeNumber"; + } + return result; + } + + private Set getAttributesToSynchronize() { + if (attrsToSync == null) { + Set result = CollectionUtil.newCaseInsensitiveSet(); + result.addAll(Arrays.asList(LdapUtil.nullAsEmpty(conn.getConfiguration().getAttributesToSynchronize()))); + if (conn.getConfiguration().isSynchronizePasswords()) { + result.add(conn.getConfiguration(). + getPasswordAttributeToSynchronize()); + } + attrsToSync = result; + } + return attrsToSync; + } + + private Set getObjectClassesToSynchronize() { + if (oclassesToSync == null) { + Set result = CollectionUtil.newCaseInsensitiveSet(); + result.addAll(Arrays.asList(LdapUtil.nullAsEmpty(conn.getConfiguration().getObjectClassesToSynchronize()))); + oclassesToSync = result; + } + return oclassesToSync; + } + + private PasswordDecryptor getPasswordDecryptor() { + if (passwordDecryptor == null) { + conn.getConfiguration().getPasswordDecryptionKey().access(decryptionKey -> { + conn.getConfiguration().getPasswordDecryptionInitializationVector().access(decryptionIV -> { + passwordDecryptor = new PasswordDecryptor(decryptionKey, decryptionIV); + }); + }); + } + assert passwordDecryptor != null; + return passwordDecryptor; + } + + private boolean containsAny( + final Set haystack, final Collection needles) { + for (String needle : needles) { + if (haystack.contains(needle)) { + return true; + } + } + return false; + } + + private Uid createUid(final String uidAttr, final String targetDN) throws InvalidNameException { + Rdn matchingRnd = null; + for (Rdn rdn : new LdapName(targetDN).getRdns()) { + if (uidAttr.equalsIgnoreCase(rdn.getType())) { + matchingRnd = rdn; + } + } + return matchingRnd == null ? null : new Uid(matchingRnd.getValue().toString()); + } + + public static int convertToInt(String number, final int def) { + int result = def; + if (number != null && number.length() > 0) { + int decimal = number.indexOf('.'); + if (decimal > 0) { + number = number.substring(0, decimal); + } + try { + result = Integer.parseInt(number); + } catch (NumberFormatException e) { + // Ignore. + } + } + return result; + } +} diff --git a/src/main/java/net/tirasa/connid/bundles/ldap/sync/sunds/ChangeLogAttributes.java b/src/main/java/net/tirasa/connid/bundles/ldap/sync/sunds/ChangeLogAttributes.java index 6b2a844..9ef724e 100644 --- a/src/main/java/net/tirasa/connid/bundles/ldap/sync/sunds/ChangeLogAttributes.java +++ b/src/main/java/net/tirasa/connid/bundles/ldap/sync/sunds/ChangeLogAttributes.java @@ -1,18 +1,18 @@ -/* +/* * ==================== * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * + * * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. - * + * * The contents of this file are subject to the terms of the Common Development * and Distribution License("CDDL") (the "License"). You may not use this file * except in compliance with the License. - * + * * You can obtain a copy of the License at * http://opensource.org/licenses/cddl1.php * See the License for the specific language governing permissions and limitations * under the License. - * + * * When distributing the Covered Code, include this CDDL Header Notice in each file * and include the License file at http://opensource.org/licenses/cddl1.php. * If applicable, add the following below this CDDL Header, with the fields @@ -25,9 +25,11 @@ public final class ChangeLogAttributes { - private String changeLogContext; - private int firstChangeNumber; - private int lastChangeNumber; + private final String changeLogContext; + + private final int firstChangeNumber; + + private final int lastChangeNumber; public ChangeLogAttributes(String changeLogContext, int firstChangeNumber, int lastChangeNumber) { this.changeLogContext = changeLogContext; diff --git a/src/main/java/net/tirasa/connid/bundles/ldap/sync/sunds/SunDSChangeLogSyncStrategy.java b/src/main/java/net/tirasa/connid/bundles/ldap/sync/sunds/SunDSChangeLogSyncStrategy.java index 1c3ae6a..4bae0c2 100644 --- a/src/main/java/net/tirasa/connid/bundles/ldap/sync/sunds/SunDSChangeLogSyncStrategy.java +++ b/src/main/java/net/tirasa/connid/bundles/ldap/sync/sunds/SunDSChangeLogSyncStrategy.java @@ -53,15 +53,17 @@ import javax.naming.ldap.Rdn; import net.tirasa.connid.bundles.ldap.LdapConnection; import net.tirasa.connid.bundles.ldap.commons.LdapEntry; +import net.tirasa.connid.bundles.ldap.commons.LdifParser; +import net.tirasa.connid.bundles.ldap.commons.PasswordDecryptor; +import net.tirasa.connid.bundles.ldap.commons.LdifParser.ChangeSeparator; +import net.tirasa.connid.bundles.ldap.commons.LdifParser.Line; +import net.tirasa.connid.bundles.ldap.commons.LdifParser.NameValue; +import net.tirasa.connid.bundles.ldap.commons.LdifParser.Separator; import net.tirasa.connid.bundles.ldap.search.LdapFilter; import net.tirasa.connid.bundles.ldap.search.LdapInternalSearch; import net.tirasa.connid.bundles.ldap.search.LdapSearch; import net.tirasa.connid.bundles.ldap.search.LdapSearches; import net.tirasa.connid.bundles.ldap.sync.LdapSyncStrategy; -import net.tirasa.connid.bundles.ldap.sync.sunds.LdifParser.ChangeSeparator; -import net.tirasa.connid.bundles.ldap.sync.sunds.LdifParser.Line; -import net.tirasa.connid.bundles.ldap.sync.sunds.LdifParser.NameValue; -import net.tirasa.connid.bundles.ldap.sync.sunds.LdifParser.Separator; import org.identityconnectors.common.logging.Log; import org.identityconnectors.common.security.GuardedString; import org.identityconnectors.framework.common.exceptions.ConnectorException; diff --git a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages.properties b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages.properties index 8d7e4f7..4c23cb9 100644 --- a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages.properties +++ b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages.properties @@ -74,6 +74,8 @@ changeLogBlockSize.display=Change Log Block Size changeLogBlockSize.help=The number of change log entries to fetch per query. Default is "100". changeNumberAttribute.display=Change Number Attribute changeNumberAttribute.help=The name of the change number attribute in the change log entry. Default is "changeNumber". +changeLogContext.display=Change Log Context +changeLogContext.help=The DN of the Change Log context. Default is "cn=changelog" filterWithOrInsteadOfAnd.display=Filter with Or Instead of And filterWithOrInsteadOfAnd.help=Normally the the filter used to fetch change log entries is an and-based filter retrieving an interval of change entries. If this property is set, the filter will or together the required change numbers instead. Default is "false". removeLogEntryObjectClassFromFilter.display=Remove Log Entry Object Class from Filter @@ -150,6 +152,7 @@ modifiersNamesToFilterOut.noBlankValues=The list of modifiers'' names to filter modifiersNamesToFilterOut.noInvalidLdapNames=The modifier''s name to filter out {0} cannot be parsed changeNumberAttribute.notBlank=The change number attribute cannot be blank changeLogBlockSize.legalValue=The synchronization block size should be greather than 0 +changeLogContext.notBlank=The change log context cannot be blank passwordAttributeToSynchronize.notBlank=The password attribute to synchronize cannot be blank decryptionKey.notBlank=The decryption key cannot be blank decryptionInitializationVector.notBlank=The decryption initialization vector cannot be blank diff --git a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_de.properties b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_de.properties index f573b84..cd54cf6 100644 --- a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_de.properties +++ b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_de.properties @@ -74,6 +74,8 @@ changeLogBlockSize.display=\u00c4nderungsprotokoll-Blockgr\u00f6\u00dfe changeLogBlockSize.help=Die Anzahl der \u00c4nderungsprotokolleintr\u00e4ge, die pro Abfrage aufgerufen werden. changeNumberAttribute.display=\u00c4nderungsnummerattribut changeNumberAttribute.help=Das \u00c4nderungsnummerattribut im \u00c4nderungsprotokolleintrag. +changeLogContext.display=Change Log Context +changeLogContext.help=The DN of the Change Log context. Default is "cn=changelog" filterWithOrInsteadOfAnd.display=Filtern mit Or anstelle von And filterWithOrInsteadOfAnd.help=Normalerweise ist der Filter, der zum Abrufen von \u00c4nderungsprotokolleintr\u00e4gen verwendet wird, ein And-basierter Filter, der ein Intervall von \u00c4nderungseintr\u00e4gen abruft. Wenn diese Eigenschaft gesetzt ist, verwendet der Filter stattdessen eine Or-Bedingung, um die erforderlichen \u00c4nderungsnummern abzurufen. removeLogEntryObjectClassFromFilter.display=\u00c4nderungseintrag-Objektklasse aus dem Filter entfernen @@ -133,6 +135,7 @@ modifiersNamesToFilterOut.noBlankValues=Die Liste der herauszufilternden Modifiz modifiersNamesToFilterOut.noInvalidLdapNames=Der herauszufilternde Modifizierername {0} kann nicht analysiert werden changeNumberAttribute.notBlank=Das \u00c4nderungsnummerattribut darf nicht leer sein changeLogBlockSize.legalValue=Die Gr\u00f6\u00dfe des Synchronisationsblocks muss gr\u00f6\u00dfer als 0 sein +changeLogContext.notBlank=The change log context cannot be blank passwordAttributeToSynchronize.notBlank=Das zu synchronisierende Passwortattribut darf nicht leer sein decryptionKey.notBlank=Der Entschl\u00fcsselungsschl\u00fcssel darf nicht leer sein decryptionInitializationVector.notBlank=Der Initialisierungsvektor zur Entschl\u00fcsselung darf nicht leer sein diff --git a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_es.properties b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_es.properties index f245235..92a0bca 100644 --- a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_es.properties +++ b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_es.properties @@ -74,6 +74,8 @@ changeLogBlockSize.display=Tama\u00f1o de bloque de registro de cambios changeLogBlockSize.help=N\u00famero de entradas del registro de cambios que se recopilan por consulta. changeNumberAttribute.display=Atributo de cambio de n\u00famero changeNumberAttribute.help=El nombre del atributo de cambio de n\u00famero en la entrada del registro de cambios. +changeLogContext.display=Change Log Context +changeLogContext.help=The DN of the Change Log context. Default is "cn=changelog" filterWithOrInsteadOfAnd.display=Filtrar con Or en vez de And filterWithOrInsteadOfAnd.help=Para recopilar las entradas del registro de cambios se usa normalmente un filtro basado en And que recupera un intervalo de entradas de cambios. Si se configura esta propiedad, el filtro aplicar\u00e1 Or conjuntamente a los n\u00fameros de cambios necesarios. removeLogEntryObjectClassFromFilter.display=Suprimir del filtro la clase de objeto de entrada de registro @@ -130,6 +132,7 @@ modifiersNamesToFilterOut.noBlankValues=La lista de nombres de modificadores par modifiersNamesToFilterOut.noInvalidLdapNames=El nombre del modificador para filtrar {0} no se puede analizar changeNumberAttribute.notBlank=El atributo de cambio de n\u00famero no puede quedar en blanco changeLogBlockSize.legalValue=El tama\u00f1o de bloque de sincronizaci\u00f3n debe ser mayor que 0 +changeLogContext.notBlank=The change log context cannot be blank passwordAttributeToSynchronize.notBlank=El atributo de contrase\u00f1a para sincronizar no puede quedar en blanco decryptionKey.notBlank=La clave de descifrado no puede quedar en blanco decryptionInitializationVector.notBlank=El vector de inicializaci\u00f3n de descifrado no puede quedar en blanco diff --git a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_fr.properties b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_fr.properties index 27f2c0e..e463e6e 100644 --- a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_fr.properties +++ b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_fr.properties @@ -74,6 +74,8 @@ changeLogBlockSize.display=Taille de bloc du journal des modifications changeLogBlockSize.help=Nombre d\u2019entr\u00e9es du journal des modifications \u00e0 extraire par requ\u00eate. changeNumberAttribute.display=Attribut de num\u00e9ro\u00b7de modification changeNumberAttribute.help=Nom de l\u2019attribut du num\u00e9ro de modification dans l\u2019entr\u00e9e du journal des modifications. +changeLogContext.display=Change Log Context +changeLogContext.help=The DN of the Change Log context. Default is "cn=changelog" filterWithOrInsteadOfAnd.display=Filtrer avec Ou au lieu de Et filterWithOrInsteadOfAnd.help=En g\u00e9n\u00e9ral, le filtre utilis\u00e9 pour extraire les entr\u00e9es du journal des modifications est un filtre de type Et r\u00e9cup\u00e9rant un intervalle d\u2019entr\u00e9es de modifications. Si cette propri\u00e9t\u00e9 est d\u00e9finie, le filtre applique \u00e0 la place la r\u00e8gle Ou pour recueillir les num\u00e9ros de modifications requises. removeLogEntryObjectClassFromFilter.display=Supprimer la classe d\u2019objet des entr\u00e9es de journal du filtre @@ -130,6 +132,7 @@ modifiersNamesToFilterOut.noBlankValues=La liste des noms de modificateurs \u00e modifiersNamesToFilterOut.noInvalidLdapNames=Impossible d\u2019analyser le nom de modificateur \u00e0 \u00e9liminer par filtrage {0}. changeNumberAttribute.notBlank=L\u2019attribut de num\u00e9ro de modification doit \u00eatre sp\u00e9cifi\u00e9. changeLogBlockSize.legalValue=La taille de bloc de synchronisation doit \u00eatre sup\u00e9rieure \u00e0 0. +changeLogContext.notBlank=The change log context cannot be blank passwordAttributeToSynchronize.notBlank=L\u2019attribut de mot de passe \u00e0 synchroniser doit \u00eatre sp\u00e9cifi\u00e9. decryptionKey.notBlank=La cl\u00e9 de d\u00e9chiffrement doit \u00eatre sp\u00e9cifi\u00e9e. decryptionInitializationVector.notBlank=Le vecteur d\u2019initialisation de d\u00e9chiffrement doit \u00eatre sp\u00e9cifi\u00e9. diff --git a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_it.properties b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_it.properties index d21beda..79c256e 100644 --- a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_it.properties +++ b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_it.properties @@ -74,6 +74,8 @@ changeLogBlockSize.display=Dimensione blocco log delle modifiche changeLogBlockSize.help=Il numero di voci del log delle modifiche da richiamare in ogni query. changeNumberAttribute.display=Attributo numero modifiche changeNumberAttribute.help=Il nome dell\u2019attributo del numero modifiche nella voce del log delle modifiche. +changeLogContext.display=Change Log Context +changeLogContext.help=The DN of the Change Log context. Default is "cn=changelog" filterWithOrInsteadOfAnd.display=Esegui filtro con Or al posto di And filterWithOrInsteadOfAnd.help=In genere, il filtro usato per richiamare le voci del log utilizza l\u2019operatore And quando richiama un intervallo di modifiche. Se si imposta questa propriet\u00e0, il filtro utilizzer\u00e0 l\u2019operatore Or sui numeri delle modifiche richieste. removeLogEntryObjectClassFromFilter.display=Rimuovi classe oggetto changeLogEntry dal filtro @@ -130,6 +132,7 @@ modifiersNamesToFilterOut.noBlankValues=L\u2019elenco dei nomi dei modificatori modifiersNamesToFilterOut.noInvalidLdapNames=Impossibile analizzare il nome del modificatore da escludere {0} changeNumberAttribute.notBlank=L\u2019attributo del numero di modifiche non pu\u00f2 essere vuoto changeLogBlockSize.legalValue=La dimensione del blocco di sincronizzazione deve essere superiore a 0 +changeLogContext.notBlank=The change log context cannot be blank passwordAttributeToSynchronize.notBlank=L\u2019attributo password da sincronizzare non pu\u00f2 essere vuoto decryptionKey.notBlank=La chiave di decrittazione non pu\u00f2 essere vuota decryptionInitializationVector.notBlank=Il vettore di inizializzazione di decrittazione non pu\u00f2 essere vuoto diff --git a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_ja.properties b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_ja.properties index fd654db..1031d0c 100644 --- a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_ja.properties +++ b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_ja.properties @@ -74,6 +74,8 @@ changeLogBlockSize.display=\u5909\u66f4\u30ed\u30b0\u306e\u30d6\u30ed\u30c3\u30a changeLogBlockSize.help=1 \u3064\u306e\u30af\u30a8\u30ea\u30fc\u3067\u30d5\u30a7\u30c3\u30c1\u3059\u308b\u5909\u66f4\u30ed\u30b0\u306e\u30a8\u30f3\u30c8\u30ea\u6570\u3002 changeNumberAttribute.display=\u5909\u66f4\u756a\u53f7\u5c5e\u6027 changeNumberAttribute.help=\u5909\u66f4\u30ed\u30b0\u30a8\u30f3\u30c8\u30ea\u306e\u5909\u66f4\u756a\u53f7\u5c5e\u6027\u306e\u540d\u524d\u3002 +changeLogContext.display=Change Log Context +changeLogContext.help=The DN of the Change Log context. Default is "cn=changelog" filterWithOrInsteadOfAnd.display=AND \u306e\u4ee3\u308f\u308a\u306b OR \u3067\u30d5\u30a3\u30eb\u30bf\u3092\u9069\u7528 filterWithOrInsteadOfAnd.help=\u901a\u5e38\u3001\u5909\u66f4\u30ed\u30b0\u30a8\u30f3\u30c8\u30ea\u306e\u30d5\u30a7\u30c3\u30c1\u306b\u4f7f\u7528\u3055\u308c\u308b\u30d5\u30a3\u30eb\u30bf\u306f AND \u30d9\u30fc\u30b9\u306e\u30d5\u30a3\u30eb\u30bf\u3067\u3001\u5909\u66f4\u30a8\u30f3\u30c8\u30ea\u306e\u9593\u9694\u3092\u53d6\u5f97\u3057\u307e\u3059\u3002\u3053\u306e\u30d7\u30ed\u30d1\u30c6\u30a3\u30fc\u3092\u8a2d\u5b9a\u3059\u308b\u3068\u3001\u30d5\u30a3\u30eb\u30bf\u306f\u5fc5\u8981\u306a\u5909\u66f4\u756a\u53f7\u3092 OR \u3067\u53ce\u96c6\u3057\u307e\u3059\u3002 removeLogEntryObjectClassFromFilter.display=\u30d5\u30a3\u30eb\u30bf\u304b\u3089\u30ed\u30b0\u30a8\u30f3\u30c8\u30ea\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u30af\u30e9\u30b9\u3092\u524a\u9664 @@ -130,6 +132,7 @@ modifiersNamesToFilterOut.noBlankValues=\u30d5\u30a3\u30eb\u30bf\u3067\u9664\u59 modifiersNamesToFilterOut.noInvalidLdapNames=\u30d5\u30a3\u30eb\u30bf\u3067\u9664\u5916\u3059\u308b\u5909\u66f4\u8005\u306e\u540d\u524d {0} \u3092\u89e3\u6790\u3067\u304d\u307e\u305b\u3093 changeNumberAttribute.notBlank=\u5909\u66f4\u756a\u53f7\u5c5e\u6027\u306f\u7a7a\u306b\u3067\u304d\u307e\u305b\u3093 changeLogBlockSize.legalValue=\u540c\u671f\u30d6\u30ed\u30c3\u30af\u30b5\u30a4\u30ba\u306f 0 \u3088\u308a\u5927\u304d\u3044\u5024\u306b\u3057\u3066\u304f\u3060\u3055\u3044 +changeLogContext.notBlank=The change log context cannot be blank passwordAttributeToSynchronize.notBlank=\u540c\u671f\u3059\u308b\u30d1\u30b9\u30ef\u30fc\u30c9\u5c5e\u6027\u306f\u7a7a\u306b\u3067\u304d\u307e\u305b\u3093 decryptionKey.notBlank=\u5fa9\u53f7\u5316\u30ad\u30fc\u306f\u7a7a\u306b\u3067\u304d\u307e\u305b\u3093 decryptionInitializationVector.notBlank=\u5fa9\u53f7\u5316\u306e\u521d\u671f\u5316\u30d9\u30af\u30c8\u30eb\u306f\u7a7a\u306b\u3067\u304d\u307e\u305b\u3093 diff --git a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_ko.properties b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_ko.properties index 15812fc..4b1b56c 100644 --- a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_ko.properties +++ b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_ko.properties @@ -74,6 +74,8 @@ changeLogBlockSize.display=\ubcc0\uacbd \ub85c\uadf8 \ube14\ub85d \ud06c\uae30 changeLogBlockSize.help=\ucffc\ub9ac\ub2f9 \ubd88\ub7ec\uc62c \ubcc0\uacbd \ub85c\uadf8 \ud56d\ubaa9\uc758 \uc218\uc785\ub2c8\ub2e4. changeNumberAttribute.display=\uc22b\uc790 \ubcc0\uacbd \uc18d\uc131 changeNumberAttribute.help=\ubcc0\uacbd \ub85c\uadf8 \ud56d\ubaa9\uc758 \uc22b\uc790 \ubcc0\uacbd \uc18d\uc131 \uc774\ub984\uc785\ub2c8\ub2e4. +changeLogContext.display=Change Log Context +changeLogContext.help=The DN of the Change Log context. Default is "cn=changelog" filterWithOrInsteadOfAnd.display=And \ub300\uc2e0 Or\ub85c \ud544\ud130 filterWithOrInsteadOfAnd.help=\uc77c\ubc18\uc801\uc73c\ub85c \ubcc0\uacbd \ub85c\uadf8 \ud56d\ubaa9\uc744 \ubd88\ub7ec\uc62c \ub54c \uc0ac\uc6a9\ub418\ub294 \ud544\ud130\ub294 \ubcc0\uacbd \ud56d\ubaa9\uc758 \uac04\uaca9\uc744 \uac80\uc0c9\ud558\ub294 And \uae30\ubc18 \ud544\ud130\uc785\ub2c8\ub2e4. \uc774 \ub4f1\ub85d \uc815\ubcf4\ub97c \uc124\uc815\ud558\uba74 \ud544\ud130\uac00 \ub300\uc2e0 \ud544\uc218 \ubcc0\uacbd \uc22b\uc790\uac00 \ud568\uaed8 \uc9c0\uc815\ub41c Or\uac00 \ub429\ub2c8\ub2e4. removeLogEntryObjectClassFromFilter.display=\ud544\ud130\uc5d0\uc11c \ub85c\uadf8 \ud56d\ubaa9 \uac1d\uccb4 \ud074\ub798\uc2a4 \uc81c\uac70 @@ -130,6 +132,7 @@ modifiersNamesToFilterOut.noBlankValues=\ud544\ud130\ub9c1\ud560 \uc218\uc815\uc modifiersNamesToFilterOut.noInvalidLdapNames={0}\uc744(\ub97c) \ud544\ud130\ub9c1\ud560 \uc218\uc815\uc790 \uc774\ub984\uc740 \uad6c\ubb38 \ubd84\uc11d\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. changeNumberAttribute.notBlank=\uc22b\uc790 \ubcc0\uacbd \uc18d\uc131\uc740 \ube44\uc6cc \ub458 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. changeLogBlockSize.legalValue=\ub3d9\uae30\ud654 \ube14\ub85d \ud06c\uae30\ub294 0\ubcf4\ub2e4 \ucee4\uc57c \ud569\ub2c8\ub2e4. +changeLogContext.notBlank=The change log context cannot be blank passwordAttributeToSynchronize.notBlank=\ub3d9\uae30\ud654\ud560 \ube44\ubc00\ubc88\ud638 \uc18d\uc131\uc740 \ube44\uc6cc \ub458 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. decryptionKey.notBlank=\ud574\ub3c5 \ud0a4\ub294 \ube44\uc6cc \ub458 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. decryptionInitializationVector.notBlank=\ud574\ub3c5 \ucd08\uae30\ud654 \ubca1\ud130\ub294 \ube44\uc6cc \ub458 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. diff --git a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_pt.properties b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_pt.properties index 229b47c..4743894 100644 --- a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_pt.properties +++ b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_pt.properties @@ -74,6 +74,8 @@ changeLogBlockSize.display=Tamanho do bloco de registro de altera\u00e7\u00e3o changeLogBlockSize.help=O n\u00famero de entradas de registro de altera\u00e7\u00e3o a obter por consulta. changeNumberAttribute.display= Atributo do n\u00famero de altera\u00e7\u00e3o changeNumberAttribute.help=O nome do atributo do n\u00famero de altera\u00e7\u00e3o na entrada do registro de altera\u00e7\u00e3o. +changeLogContext.display=Change Log Context +changeLogContext.help=The DN of the Change Log context. Default is "cn=changelog" filterWithOrInsteadOfAnd.display=Filtrar com OR em vez de AND filterWithOrInsteadOfAnd.help=Normalmente, o filtro usado para obter entradas de registro de altera\u00e7\u00e3o \u00e9 um filtro baseado em AND que recupera um intervalo de entradas de altera\u00e7\u00e3o. Se essa propriedade estiver definida, o filtro ser\u00e1 aplicado ou, em vez disso, conjuntamente aos n\u00fameros de altera\u00e7\u00f5es necess\u00e1rios. removeLogEntryObjectClassFromFilter.display=Remover classe de objeto de entrada de registro do filtro @@ -130,6 +132,7 @@ modifiersNamesToFilterOut.noBlankValues=A lista de nomes dos modificadores para modifiersNamesToFilterOut.noInvalidLdapNames=O nome do modificador para filtragem {0} n\u00e3o pode ser analisado changeNumberAttribute.notBlank=O atributo do n\u00famero de altera\u00e7\u00e3o n\u00e3o pode ficar em branco changeLogBlockSize.legalValue=O tamanho do bloco de sincroniza\u00e7\u00e3o deve ser maior que 0 +changeLogContext.notBlank=The change log context cannot be blank passwordAttributeToSynchronize.notBlank=O atributo de senha para sincroniza\u00e7\u00e3o n\u00e3o pode ficar em branco decryptionKey.notBlank=A chave de descriptografia n\u00e3o pode ficar em branco decryptionInitializationVector.notBlank=O vetor de inicializa\u00e7\u00e3o da descriptografia n\u00e3o pode ficar em branco diff --git a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_zh.properties b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_zh.properties index a72702d..164ed93 100644 --- a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_zh.properties +++ b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_zh.properties @@ -74,6 +74,8 @@ changeLogBlockSize.display=\u66f4\u6539\u65e5\u5fd7\u5757\u5927\u5c0f changeLogBlockSize.help=\u6bcf\u4e2a\u67e5\u8be2\u83b7\u53d6\u7684\u66f4\u6539\u65e5\u5fd7\u6761\u76ee\u6570\u3002 changeNumberAttribute.display=\u66f4\u6539\u7f16\u53f7\u5c5e\u6027 changeNumberAttribute.help=\u66f4\u6539\u65e5\u5fd7\u6761\u76ee\u4e2d\u7684\u66f4\u6539\u7f16\u53f7\u5c5e\u6027\u7684\u540d\u79f0\u3002 +changeLogContext.display=Change Log Context +changeLogContext.help=The DN of the Change Log context. Default is "cn=changelog" filterWithOrInsteadOfAnd.display=\u4f7f\u7528 OR \u800c\u4e0d\u662f AND \u8fdb\u884c\u8fc7\u6ee4 filterWithOrInsteadOfAnd.help=\u901a\u5e38\uff0c\u7528\u4e8e\u83b7\u53d6\u66f4\u6539\u65e5\u5fd7\u6761\u76ee\u7684\u8fc7\u6ee4\u5668\u662f\u57fa\u4e8e AND \u6761\u4ef6\u68c0\u7d22\u4e00\u6bb5\u65f6\u95f4\u95f4\u9694\u5185\u7684\u66f4\u6539\u6761\u76ee\u3002\u5982\u679c\u8bbe\u7f6e\u4e86\u6b64\u5c5e\u6027\uff0c\u5219\u8fc7\u6ee4\u5668\u5c06\u6539\u7528 OR \u6761\u4ef6\u914d\u5408\u6240\u9700\u7684\u66f4\u6539\u6570\u91cf\u8fdb\u884c\u8fc7\u6ee4\u3002 removeLogEntryObjectClassFromFilter.display=\u4ece\u8fc7\u6ee4\u5668\u4e2d\u5220\u9664\u65e5\u5fd7\u6761\u76ee\u5bf9\u8c61\u7c7b @@ -130,6 +132,7 @@ modifiersNamesToFilterOut.noBlankValues=\u8981\u8fc7\u6ee4\u6389\u7684\u4fee\u65 modifiersNamesToFilterOut.noInvalidLdapNames=\u65e0\u6cd5\u89e3\u6790\u8981\u8fc7\u6ee4\u6389\u7684\u4fee\u6539\u8005\u540d\u79f0 {0} changeNumberAttribute.notBlank=\u66f4\u6539\u7f16\u53f7\u5c5e\u6027\u4e0d\u80fd\u4e3a\u7a7a changeLogBlockSize.legalValue=\u540c\u6b65\u5757\u5927\u5c0f\u5e94\u5927\u4e8e 0 +changeLogContext.notBlank=The change log context cannot be blank passwordAttributeToSynchronize.notBlank=\u8981\u540c\u6b65\u7684\u5bc6\u7801\u5c5e\u6027\u4e0d\u80fd\u4e3a\u7a7a decryptionKey.notBlank=\u89e3\u5bc6\u5bc6\u94a5\u4e0d\u80fd\u4e3a\u7a7a decryptionInitializationVector.notBlank=\u89e3\u5bc6\u521d\u59cb\u5316\u5411\u91cf\u4e0d\u80fd\u4e3a\u7a7a diff --git a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_zh_TW.properties b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_zh_TW.properties index dd8f677..52cca91 100644 --- a/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_zh_TW.properties +++ b/src/main/resources/net/tirasa/connid/bundles/ldap/Messages_zh_TW.properties @@ -72,7 +72,10 @@ accountSynchronizationFilter.display=\u8981\u540c\u6b65\u4e4b\u5e33\u865f\u7684 accountSynchronizationFilter.help=\u8981\u540c\u6b65\u4e4b\u7269\u4ef6\u7684\u9078\u64c7\u6027 LDAP \u7be9\u9078\u5668\u3002\u56e0\u70ba\u8b8a\u66f4\u8a18\u9304\u662f\u91dd\u5c0d\u6240\u6709\u7269\u4ef6\uff0c\u6240\u4ee5\u6b64\u7be9\u9078\u5668\u53ea\u6703\u66f4\u65b0\u7b26\u5408\u6240\u6307\u5b9a\u7be9\u9078\u5668\u7684\u7269\u4ef6\u3002\u82e5\u6307\u5b9a\u4e86\u7be9\u9078\u5668\uff0c\u5247\u53ea\u6709\u5728\u7b26\u5408\u7be9\u9078\u5668\u4e14\u5305\u62ec\u5df2\u540c\u6b65\u7684\u7269\u4ef6\u985e\u5225\u6642\uff0c\u624d\u6703\u540c\u6b65\u7269\u4ef6\u3002 changeLogBlockSize.display=\u8b8a\u66f4\u8a18\u9304\u5340\u6bb5\u5927\u5c0f changeLogBlockSize.help=\u6bcf\u6b21\u67e5\u8a62\u6240\u64f7\u53d6\u7684\u8b8a\u66f4\u8a18\u9304\u9805\u76ee\u6578\u3002 -changeNumberAttribute.display=\u8b8a\u66f4\u6578\u5b57\u5c6c\u6027groupObjectClasses.display=Group Object Classes +changeNumberAttribute.display=\u8b8a\u66f4\u6578\u5b57\u5c6c\u6027 +changeLogContext.display=Change Log Context +changeLogContext.help=The DN of the Change Log context. Default is "cn=changelog" +groupObjectClasses.display=Group Object Classes groupObjectClasses.help=The group class or classes that will be used when creating new group objects in the LDAP tree. When entering more than one object class, each entry should be on its own line; do not use commas or semi-colons to separate multiple group classes. Some group classes may require that you specify all group classes in the class hierarchy. groupNameAttributes.display=Group Name Attributes groupNameAttributes.help=Attribute or attributes which holds the group''s name. @@ -137,6 +140,7 @@ modifiersNamesToFilterOut.noBlankValues=\u8981\u7be9\u9078\u6389\u4e4b\u4fee\u98 modifiersNamesToFilterOut.noInvalidLdapNames=\u7121\u6cd5\u5256\u6790\u8981\u7be9\u9078\u6389\u300c{0}\u300d\u4e4b\u4fee\u98fe\u9375\u540d\u7a31 changeNumberAttribute.notBlank=\u8b8a\u66f4\u6578\u5b57\u5c6c\u6027\u4e0d\u53ef\u70ba\u7a7a\u767d changeLogBlockSize.legalValue=\u540c\u6b65\u5340\u6bb5\u5927\u5c0f\u61c9\u5927\u65bc 0 +changeLogContext.notBlank=The change log context cannot be blank passwordAttributeToSynchronize.notBlank=\u8981\u540c\u6b65\u7684\u5bc6\u78bc\u5c6c\u6027\u4e0d\u53ef\u70ba\u7a7a\u767d decryptionKey.notBlank=\u89e3\u5bc6\u91d1\u9470\u4e0d\u53ef\u70ba\u7a7a\u767d decryptionInitializationVector.notBlank=\u89e3\u5bc6\u521d\u59cb\u5316\u5411\u91cf\u4e0d\u53ef\u70ba\u7a7a\u767d diff --git a/src/test/java/net/tirasa/connid/bundles/ldap/sync/sunds/LdifParserTests.java b/src/test/java/net/tirasa/connid/bundles/ldap/commons/LdifParserTests.java similarity index 90% rename from src/test/java/net/tirasa/connid/bundles/ldap/sync/sunds/LdifParserTests.java rename to src/test/java/net/tirasa/connid/bundles/ldap/commons/LdifParserTests.java index a0b9dcf..eebc41b 100644 --- a/src/test/java/net/tirasa/connid/bundles/ldap/sync/sunds/LdifParserTests.java +++ b/src/test/java/net/tirasa/connid/bundles/ldap/commons/LdifParserTests.java @@ -21,16 +21,16 @@ * ==================== * Portions Copyrighted 2011 ConnId. */ -package net.tirasa.connid.bundles.ldap.sync.sunds; +package net.tirasa.connid.bundles.ldap.commons; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Iterator; -import net.tirasa.connid.bundles.ldap.sync.sunds.LdifParser.ChangeSeparator; -import net.tirasa.connid.bundles.ldap.sync.sunds.LdifParser.Line; -import net.tirasa.connid.bundles.ldap.sync.sunds.LdifParser.NameValue; -import net.tirasa.connid.bundles.ldap.sync.sunds.LdifParser.Separator; +import net.tirasa.connid.bundles.ldap.commons.LdifParser.ChangeSeparator; +import net.tirasa.connid.bundles.ldap.commons.LdifParser.Line; +import net.tirasa.connid.bundles.ldap.commons.LdifParser.NameValue; +import net.tirasa.connid.bundles.ldap.commons.LdifParser.Separator; import org.junit.jupiter.api.Test; public class LdifParserTests { diff --git a/src/test/java/net/tirasa/connid/bundles/ldap/sync/sunds/LdapModifyUtils.java b/src/test/java/net/tirasa/connid/bundles/ldap/sync/sunds/LdapModifyUtils.java index 9526073..1e84c69 100644 --- a/src/test/java/net/tirasa/connid/bundles/ldap/sync/sunds/LdapModifyUtils.java +++ b/src/test/java/net/tirasa/connid/bundles/ldap/sync/sunds/LdapModifyUtils.java @@ -23,8 +23,6 @@ */ package net.tirasa.connid.bundles.ldap.sync.sunds; -import static net.tirasa.connid.bundles.ldap.commons.LdapUtil.quietCreateLdapName; - import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -42,10 +40,12 @@ import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; import net.tirasa.connid.bundles.ldap.LdapConnection; -import net.tirasa.connid.bundles.ldap.sync.sunds.LdifParser.ChangeSeparator; -import net.tirasa.connid.bundles.ldap.sync.sunds.LdifParser.Line; -import net.tirasa.connid.bundles.ldap.sync.sunds.LdifParser.NameValue; -import net.tirasa.connid.bundles.ldap.sync.sunds.LdifParser.Separator; +import net.tirasa.connid.bundles.ldap.commons.LdapUtil; +import net.tirasa.connid.bundles.ldap.commons.LdifParser; +import net.tirasa.connid.bundles.ldap.commons.LdifParser.ChangeSeparator; +import net.tirasa.connid.bundles.ldap.commons.LdifParser.Line; +import net.tirasa.connid.bundles.ldap.commons.LdifParser.NameValue; +import net.tirasa.connid.bundles.ldap.commons.LdifParser.Separator; import org.identityconnectors.common.logging.Log; /** @@ -157,7 +157,7 @@ private static void performChange(LdapConnection conn, String dn, String changeT } attrs.put(attr); } - LdapName newName = quietCreateLdapName(dn); + LdapName newName = LdapUtil.quietCreateLdapName(dn); LOG.ok("Creating context {0} with attributes {1}", newName, attrs); String container = newName.getPrefix(newName.size() - 1).toString(); Rdn rdn = newName.getRdn(newName.size() - 1); @@ -169,13 +169,12 @@ private static void performChange(LdapConnection conn, String dn, String changeT addModificationItems(DirContext.ADD_ATTRIBUTE, added, modItems); addModificationItems(DirContext.REMOVE_ATTRIBUTE, deleted, modItems); LOG.ok("Modifying context {0} with attributes {1}", dn, modItems); - conn.getInitialContext().modifyAttributes(dn, - modItems.toArray(new ModificationItem[modItems.size()])); + conn.getInitialContext().modifyAttributes(dn, modItems.toArray(new ModificationItem[modItems.size()])); } else if ("delete".equalsIgnoreCase(changeType)) { LOG.ok("Deleting context {0}"); conn.getInitialContext().destroySubcontext(dn); } else if ("modrdn".equalsIgnoreCase(changeType)) { - LdapName oldName = quietCreateLdapName(dn); + LdapName oldName = LdapUtil.quietCreateLdapName(dn); LdapName newName = (LdapName) oldName.getPrefix(oldName.size() - 1); newName.add(newRdn); LOG.ok("Renaming context {0} to {1}", oldName, newName);