/* * ==================== * 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://IdentityConnectors.dev.java.net/legal/license.txt * 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 identityconnectors/legal/license.txt. * 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]" * ==================== * * Portions Copyrighted 2013-2014 ForgeRock AS * Portions Copyrighted 2011-2014 Radovan Semancik (Evolveum) */ package org.identityconnectors.ldap; import static org.identityconnectors.common.CollectionUtil.newList; import static org.identityconnectors.common.StringUtil.isBlank; import static org.identityconnectors.ldap.LdapUtil.nullAsEmpty; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.naming.InvalidNameException; import javax.naming.ldap.LdapName; import org.identityconnectors.common.EqualsHashCodeBuilder; import org.identityconnectors.common.security.GuardedByteArray; import org.identityconnectors.common.security.GuardedString; import org.identityconnectors.common.security.GuardedByteArray.Accessor; import org.identityconnectors.framework.common.exceptions.ConfigurationException; import org.identityconnectors.framework.common.objects.AttributeInfo; import org.identityconnectors.framework.common.objects.AttributeInfoBuilder; import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.spi.AbstractConfiguration; import org.identityconnectors.framework.spi.ConfigurationProperty; import org.identityconnectors.framework.spi.operations.SyncOp; /** * Encapsulates the LDAP connector's configuration. * * @author Andrei Badea */ public class LdapConfiguration extends AbstractConfiguration { // XXX should try to connect to the resource. // XXX add @ConfigurationProperty. static final int DEFAULT_PORT = 389; // Exposed configuration properties. /** * The LDAP host server to connect to. */ private String host; /** * The port the server is listening on. */ private int port = DEFAULT_PORT; /** * Whether the port is a secure SSL port. */ private boolean ssl; /** * Whether the use STARTTLS LDAP extension right after the connection. */ private boolean startTls; /** * LDAP URL's to connect to if the main server specified through the host and port * properties is not available. */ private String[] failover = { }; /** * The bind DN for performing operations on the server. */ private String principal; /** * The bind password associated with the bind DN. */ private GuardedString credentials; /** * The base DNs for operations on the server. */ private String[] baseContexts = { }; /** * Referral policy. Defaults to 'follow' * Values can be: 'follow', 'ignore' or 'throw' */ private String referralsHandling = "follow"; /** * The name of the attribute which the predefined PASSWORD attribute * will be written to. */ private String passwordAttribute = "userPassword"; /** * The authentication mechanism to use. Either "simple" or "SASL-GSSAPI". * Defaults to "simple" */ private String authType = "simple"; /** * A search filter that any account needs to match in order to be returned. */ private String accountSearchFilter = null; /** * A search filter that any group needs to match in order to be returned. */ private String groupSearchFilter = null; /** * The LDAP attribute holding the member for non-POSIX static groups. */ private String groupMemberAttribute = "uniqueMember"; /** * If true, add an extra _memberId attribute to get the group members __UID__ */ private boolean getGroupMemberId = false; /** * If true, will modify group membership of renamed/deleted entries. */ private boolean maintainLdapGroupMembership = false; /** * If true, will modify POSIX group membership of renamed/deleted entries. */ private boolean maintainPosixGroupMembership = false; /** * If the server stores passwords in clear text, we will hash them with * the algorithm specified here. */ private String passwordHashAlgorithm; /** * If true, when binding check for the Password Expired control (and also Password Policy control) * and throw exceptions (PasswordExpiredException, etc.) appropriately. */ private boolean respectResourcePasswordPolicyChangeAfterReset; /** * Specifies strategy of using paging mechanisms such as VLV or Simple Paged Results. * Possible values: "none", "auto", "spr", "vlv" * Default value: "auto" */ private String pagingStrategy = null; public static final String PAGING_STRATEGY_NONE = "none"; public static final String PAGING_STRATEGY_AUTO = "auto"; public static final String PAGING_STRATEGY_SPR = "spr"; public static final String PAGING_STRATEGY_VLV = "vlv"; /** * Whether to use block-based LDAP controls like simple paged results or VLV control. * DEPRECATED. Use pagingStrategy instead. */ @Deprecated private Boolean useBlocks = null; /** * The block size for simple paged results and VLV searches. */ private int blockSize = 100; /** * If true, simple paged search will be preferred over VLV index search * when both are available. * DEPRECATED. Use pagingStrategy instead. Setting this to true is equivalent * to setting pagingStrategy to "spr". If pagingStrategy is already set this value is ignored. */ @Deprecated private Boolean usePagedResultControl = null; /** * The attribute used as the sort key for the VLV searches. */ private String vlvSortAttribute = "uid"; /** * The ordering rule to use for the VLV searches. */ private String vlvSortOrderingRule = null; /** * The LDAP attribute to map Uid to. */ private String uidAttribute = "entryUUID"; /** * Whether to read the schema from the server. */ private boolean readSchema = true; // Sync configuration properties. private String[] baseContextsToSynchronize = { }; private String[] objectClassesToSynchronize = { "inetOrgPerson" }; private String[] attributesToSynchronize = { }; private String[] modifiersNamesToFilterOut = { }; private String accountSynchronizationFilter; private String groupSynchronizationFilter; private int changeLogBlockSize = 100; private String changeNumberAttribute = "changeNumber"; private boolean filterWithOrInsteadOfAnd; private boolean removeLogEntryObjectClassFromFilter = true; private boolean synchronizePasswords; private String passwordAttributeToSynchronize; private GuardedByteArray passwordDecryptionKey; private GuardedByteArray passwordDecryptionInitializationVector; private boolean useTimestampsForSync = false; // Other state. private final ObjectClassMappingConfig accountConfig = new ObjectClassMappingConfig(ObjectClass.ACCOUNT, newList("top", "person", "organizationalPerson", "inetOrgPerson"), false, newList("uid", "cn"), LdapConstants.PASSWORD); private final ObjectClassMappingConfig groupConfig = new ObjectClassMappingConfig(ObjectClass.GROUP, newList("top", "groupOfUniqueNames"), false, Collections.<String>emptyList()); // Other state not to be included in hashCode/equals. private List<LdapName> baseContextsAsLdapNames; private List<LdapName> baseContextsToSynchronizeAsLdapNames; private Set<LdapName> modifiersNamesToFilterOutAsLdapNames; public LdapConfiguration() { } /** * {@inheritDoc} */ public void validate() { checkNotBlank(host, "host.notBlank"); if (port < 0 || port > 0xffff) { failValidation("port.legalValue"); } checkNotEmpty(baseContexts, "baseContexts.notEmpty"); checkNoBlankValues(baseContexts, "baseContexts.noBlankValues"); checkNoInvalidLdapNames(baseContexts, "baseContexts.noInvalidLdapNames"); checkReferralsHandling(referralsHandling, "referralsHandling.invalidPolicy"); checkPasswordHashAlgorithm(passwordHashAlgorithm, "passwordHashAlgorithm.invalidName"); checkNotBlank(passwordAttribute, "passwordAttribute.notBlank"); checkNotEmpty(accountConfig.getLdapClasses(), "accountObjectClasses.notEmpty"); checkNoBlankValues(accountConfig.getLdapClasses(), "accountObjectClasses.noBlankValues"); checkNotEmpty(accountConfig.getShortNameLdapAttributes(), "accountUserNameAttributes.notEmpty"); checkNoBlankValues(accountConfig.getShortNameLdapAttributes(), "accountUserNameAttributes.noBlankValues"); checkNotBlank(groupMemberAttribute, "groupMemberAttribute.notBlank"); if (blockSize <= 0) { failValidation("blockSize.legalValue"); } checkNotBlank(vlvSortAttribute, "vlvSortAttribute.notBlank"); checkNotBlank(uidAttribute, "uidAttribute.notBlank"); if (baseContextsToSynchronize != null) { checkNoBlankValues(baseContextsToSynchronize, "baseContextsToSynchronize.noBlankValues"); checkNoInvalidLdapNames(baseContextsToSynchronize, "baseContextsToSynchronize.noInvalidLdapNames"); } checkNotEmpty(objectClassesToSynchronize, "objectClassesToSynchronize.notEmpty"); checkNoBlankValues(objectClassesToSynchronize, "objectClassesToSynchronize.noBlankValues"); if (attributesToSynchronize != null) { checkNoBlankValues(attributesToSynchronize, "attributesToSynchronize.noBlankValues"); } if (modifiersNamesToFilterOut != null) { checkNoBlankValues(modifiersNamesToFilterOut, "modifiersNamesToFilterOut.noBlankValues"); checkNoInvalidLdapNames(modifiersNamesToFilterOut, "modifiersNamesToFilterOut.noInvalidLdapNames"); } checkNotBlank(changeNumberAttribute, "changeNumberAttribute.notBlank"); if (changeLogBlockSize <= 0) { failValidation("changeLogBlockSize.legalValue"); } if (synchronizePasswords) { checkNotBlank(passwordAttributeToSynchronize, "passwordAttributeToSynchronize.notBlank"); checkNotBlank(passwordDecryptionKey, "decryptionKey.notBlank"); checkNotBlank(passwordDecryptionInitializationVector, "decryptionInitializationVector.notBlank"); } } private void checkNotBlank(String value, String errorMessage) { if (isBlank(value)) { failValidation(errorMessage); } } private void checkNotBlank(GuardedByteArray array, String errorMessage) { final int[] length = { 0 }; if (array != null) { array.access(new Accessor() { public void access(byte[] clearBytes) { length[0] = clearBytes.length; } }); } if (length[0] == 0) { failValidation(errorMessage); } } private void checkNotEmpty(Collection<?> collection, String errorMessage) { if (collection.size() < 1) { failValidation(errorMessage); } } private void checkNotEmpty(String[] array, String errorMessage) { if (array == null || array.length < 1) { failValidation(errorMessage); } } private void checkNoBlankValues(Collection<String> collection, String errorMessage) { for (String each : collection) { if (isBlank(each)) { failValidation(errorMessage); } } } private void checkNoBlankValues(String[] array, String errorMessage) { for (String each : array) { if (isBlank(each)) { failValidation(errorMessage); } } } private void checkNoInvalidLdapNames(String[] array, String errorMessage) { for (String each : array) { try { new LdapName(each); } catch (InvalidNameException e) { failValidation(errorMessage, each); } } } private void checkReferralsHandling(String ref, String errorMessage){ if (!ref.matches("follow|ignore|throw")){ failValidation(errorMessage); } } private void checkPasswordHashAlgorithm(String algo, String errorMessage){ if ((algo != null) && !algo.matches("(?i:SSHA|SHA|SMD5|MD5|WIN-AD)")){ failValidation(errorMessage); } } private void failValidation(String key, Object... args) { String message = getConnectorMessages().format(key, null, args); throw new ConfigurationException(message); } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public boolean isSsl() { return ssl; } public void setSsl(boolean ssl) { this.ssl = ssl; } public boolean isStartTls() { return startTls; } public void setStartTls(boolean startTls) { this.startTls = startTls; } public String[] getFailover() { return failover.clone(); } public void setFailover(String... failover) { this.failover = failover; } public String getPrincipal() { return principal; } public void setPrincipal(String principal) { this.principal = principal; } @ConfigurationProperty(confidential = true) public GuardedString getCredentials() { return credentials; } public void setCredentials(GuardedString credentials) { this.credentials = credentials != null ? credentials.copy() : null; } public String getAuthType(){ return authType; } public void setAuthType(String authType){ this.authType = authType; } public String[] getBaseContexts() { return baseContexts.clone(); } public void setBaseContexts(String... baseContexts) { this.baseContexts = baseContexts.clone(); } public String getReferralsHandling(){ return referralsHandling; } public void setReferralsHandling(String referral){ this.referralsHandling = referral; } public String getPasswordAttribute() { return passwordAttribute; } public void setPasswordAttribute(String passwordAttribute) { this.passwordAttribute = passwordAttribute; } public String[] getAccountObjectClasses() { List<String> ldapClasses = accountConfig.getLdapClasses(); return ldapClasses.toArray(new String[ldapClasses.size()]); } public void setAccountObjectClasses(String... accountObjectClasses) { accountConfig.setLdapClasses(Arrays.asList(accountObjectClasses)); } public String[] getAccountUserNameAttributes() { List<String> shortNameLdapAttributes = accountConfig.getShortNameLdapAttributes(); return shortNameLdapAttributes.toArray(new String[shortNameLdapAttributes.size()]); } public void setAccountUserNameAttributes(String... accountUserNameAttributes) { accountConfig.setShortNameLdapAttributes(Arrays.asList(accountUserNameAttributes)); } public String[] getAccountOperationalAttributes() { Set<AttributeInfo> operationalAttributeInfos = accountConfig.getOperationalAttributes(); String[] operationalAttributeNames = new String[operationalAttributeInfos.size()]; int i = 0; for (AttributeInfo attrInfo : operationalAttributeInfos) { operationalAttributeNames[i] = attrInfo.getName(); i++; } return operationalAttributeNames; } public void setAccountOperationalAttributes(String... accountOperationalAttributes) { Set<AttributeInfo> operationalAttributes = accountConfig.getOperationalAttributes(); operationalAttributes.clear(); operationalAttributes.add(LdapConstants.PASSWORD); for(String attrName : accountOperationalAttributes) { if (LdapConstants.PASSWORD.getName().equals(attrName)) { // Already in the list continue; } operationalAttributes.add(new AttributeInfoBuilder(attrName).build()); } } public String getAccountSearchFilter() { return accountSearchFilter; } public void setAccountSearchFilter(String accountSearchFilter) { this.accountSearchFilter = accountSearchFilter; } public String getGroupSearchFilter() { return groupSearchFilter; } public void setGroupSearchFilter(String groupSearchFilter) { this.groupSearchFilter = groupSearchFilter; } public String[] getGroupObjectClasses() { List<String> ldapClasses = groupConfig.getLdapClasses(); return ldapClasses.toArray(new String[ldapClasses.size()]); } public void setGroupObjectClasses(String... groupObjectClasses) { groupConfig.setLdapClasses(Arrays.asList(groupObjectClasses)); } public String getGroupMemberAttribute() { return groupMemberAttribute; } public void setGroupMemberAttribute(String groupMemberAttribute) { this.groupMemberAttribute = groupMemberAttribute; } public boolean isGetGroupMemberId() { return getGroupMemberId; } public void setGetGroupMemberId(boolean getGroupMemberId) { this.getGroupMemberId = getGroupMemberId; } public boolean isMaintainLdapGroupMembership() { return maintainLdapGroupMembership; } public void setMaintainLdapGroupMembership(boolean maintainLdapGroupMembership) { this.maintainLdapGroupMembership = maintainLdapGroupMembership; } public boolean isMaintainPosixGroupMembership() { return maintainPosixGroupMembership; } public void setMaintainPosixGroupMembership(boolean maintainPosixGroupMembership) { this.maintainPosixGroupMembership = maintainPosixGroupMembership; } public String getPasswordHashAlgorithm() { return passwordHashAlgorithm; } public void setPasswordHashAlgorithm(String passwordHashAlgorithm) { this.passwordHashAlgorithm = passwordHashAlgorithm; } public boolean isRespectResourcePasswordPolicyChangeAfterReset() { return respectResourcePasswordPolicyChangeAfterReset; } public void setRespectResourcePasswordPolicyChangeAfterReset(boolean respectResourcePasswordPolicyChangeAfterReset) { this.respectResourcePasswordPolicyChangeAfterReset = respectResourcePasswordPolicyChangeAfterReset; } public String getPagingStrategy() { return pagingStrategy; } public void setPagingStrategy(String pagingStrategy) { this.pagingStrategy = pagingStrategy; } public Boolean getUseBlocks() { return useBlocks; } public void setUseBlocks(Boolean useBlocks) { this.useBlocks = useBlocks; } public int getBlockSize() { return blockSize; } public void setBlockSize(int blockSize) { this.blockSize = blockSize; } public Boolean getUsePagedResultControl() { return usePagedResultControl; } public void setUsePagedResultControl(Boolean usePagedResultControl) { this.usePagedResultControl = usePagedResultControl; } public String getVlvSortAttribute() { return vlvSortAttribute; } public void setVlvSortAttribute(String vlvSortAttribute) { this.vlvSortAttribute = vlvSortAttribute; } public String getVlvSortOrderingRule() { return vlvSortOrderingRule; } public void setVlvSortOrderingRule(String vlvSortOrderingRule) { this.vlvSortOrderingRule = vlvSortOrderingRule; } public String getUidAttribute() { return uidAttribute; } public void setUidAttribute(String uidAttribute) { this.uidAttribute = uidAttribute; } public boolean isReadSchema() { return readSchema; } public void setReadSchema(boolean readSchema) { this.readSchema = readSchema; } // Sync properties getters and setters. @ConfigurationProperty(operations = { SyncOp.class }) public String[] getBaseContextsToSynchronize() { return baseContextsToSynchronize.clone(); } public void setBaseContextsToSynchronize(String... baseContextsToSynchronize) { this.baseContextsToSynchronize = (String[]) baseContextsToSynchronize.clone(); } @ConfigurationProperty(operations = { SyncOp.class }, required = true) public String[] getObjectClassesToSynchronize() { return objectClassesToSynchronize.clone(); } public void setObjectClassesToSynchronize(String... objectClassesToSynchronize) { this.objectClassesToSynchronize = (String[]) objectClassesToSynchronize.clone(); } @ConfigurationProperty(operations = { SyncOp.class }) public String[] getAttributesToSynchronize() { return attributesToSynchronize.clone(); } public void setAttributesToSynchronize(String... attributesToSynchronize) { this.attributesToSynchronize = (String[]) attributesToSynchronize.clone(); } @ConfigurationProperty(operations = { SyncOp.class }) public String[] getModifiersNamesToFilterOut() { return modifiersNamesToFilterOut.clone(); } public void setModifiersNamesToFilterOut(String... modifiersNamesToFilterOut) { this.modifiersNamesToFilterOut = (String[]) modifiersNamesToFilterOut.clone(); } @ConfigurationProperty(operations = { SyncOp.class }) public String getAccountSynchronizationFilter() { return accountSynchronizationFilter; } public void setAccountSynchronizationFilter(String accountSynchronizationFilter) { this.accountSynchronizationFilter = accountSynchronizationFilter; } @ConfigurationProperty(operations = { SyncOp.class }) public String getGroupSynchronizationFilter() { return groupSynchronizationFilter; } public void setGroupSynchronizationFilter(String groupSynchronizationFilter) { this.groupSynchronizationFilter = groupSynchronizationFilter; } @ConfigurationProperty(operations = { SyncOp.class }, required = true) public int getChangeLogBlockSize() { return changeLogBlockSize; } public void setChangeLogBlockSize(int changeLogBlockSize) { this.changeLogBlockSize = changeLogBlockSize; } @ConfigurationProperty(operations = { SyncOp.class }, required = true) public String getChangeNumberAttribute() { return changeNumberAttribute; } public void setChangeNumberAttribute(String changeNumberAttribute) { this.changeNumberAttribute = changeNumberAttribute; } @ConfigurationProperty(operations = { SyncOp.class }, required = false) public boolean isUseTimestampsForSync() { return useTimestampsForSync; } public void setUseTimestampsForSync(boolean useTimestampsForSync) { this.useTimestampsForSync = useTimestampsForSync; } @ConfigurationProperty(operations = { SyncOp.class }) public boolean isFilterWithOrInsteadOfAnd() { return filterWithOrInsteadOfAnd; } public void setFilterWithOrInsteadOfAnd(boolean filterWithOrInsteadOfAnd) { this.filterWithOrInsteadOfAnd = filterWithOrInsteadOfAnd; } @ConfigurationProperty(operations = { SyncOp.class }) public boolean isRemoveLogEntryObjectClassFromFilter() { return removeLogEntryObjectClassFromFilter; } public void setRemoveLogEntryObjectClassFromFilter(boolean removeLogEntryObjectClassFromFilter) { this.removeLogEntryObjectClassFromFilter = removeLogEntryObjectClassFromFilter; } @ConfigurationProperty(operations = { SyncOp.class }) public boolean isSynchronizePasswords() { return synchronizePasswords; } public void setSynchronizePasswords(boolean synchronizePasswords) { this.synchronizePasswords = synchronizePasswords; } @ConfigurationProperty(operations = { SyncOp.class }) public String getPasswordAttributeToSynchronize() { return passwordAttributeToSynchronize; } public void setPasswordAttributeToSynchronize(String passwordAttributeToSynchronize) { this.passwordAttributeToSynchronize = passwordAttributeToSynchronize; } @ConfigurationProperty(operations = { SyncOp.class }, confidential = true) public GuardedByteArray getPasswordDecryptionKey() { return passwordDecryptionKey; } public void setPasswordDecryptionKey(GuardedByteArray passwordDecryptionKey) { this.passwordDecryptionKey = passwordDecryptionKey != null ? passwordDecryptionKey.copy() : null; } @ConfigurationProperty(operations = { SyncOp.class }, confidential = true) public GuardedByteArray getPasswordDecryptionInitializationVector() { return passwordDecryptionInitializationVector; } public void setPasswordDecryptionInitializationVector(GuardedByteArray passwordDecryptionInitializationVector) { this.passwordDecryptionInitializationVector = passwordDecryptionInitializationVector != null ? passwordDecryptionInitializationVector.copy() : null; } // Getters and setters for configuration properties end here. public List<LdapName> getBaseContextsAsLdapNames() { if (baseContextsAsLdapNames == null) { List<LdapName> result = new ArrayList<LdapName>(baseContexts.length); try { for (String baseContext : baseContexts) { result.add(new LdapName(baseContext)); } } catch (InvalidNameException e) { throw new ConfigurationException(e); } baseContextsAsLdapNames = result; } return baseContextsAsLdapNames; } public List<LdapName> getBaseContextsToSynchronizeAsLdapNames() { if (baseContextsToSynchronizeAsLdapNames == null) { String[] source = nullAsEmpty(baseContextsToSynchronize); List<LdapName> result = new ArrayList<LdapName>(source.length); try { for (String each : source) { result.add(new LdapName(each)); } } catch (InvalidNameException e) { throw new ConfigurationException(e); } baseContextsToSynchronizeAsLdapNames = result; } return baseContextsToSynchronizeAsLdapNames; } public Set<LdapName> getModifiersNamesToFilterOutAsLdapNames() { if (modifiersNamesToFilterOutAsLdapNames == null) { String[] source = nullAsEmpty(modifiersNamesToFilterOut); Set<LdapName> result = new HashSet<LdapName>(source.length); try { for (String each : source) { result.add(new LdapName(each)); } } catch (InvalidNameException e) { throw new ConfigurationException(e); } modifiersNamesToFilterOutAsLdapNames = result; } return modifiersNamesToFilterOutAsLdapNames; } public Map<ObjectClass, ObjectClassMappingConfig> getObjectClassMappingConfigs() { HashMap<ObjectClass, ObjectClassMappingConfig> result = new HashMap<ObjectClass, ObjectClassMappingConfig>(); result.put(accountConfig.getObjectClass(), accountConfig); result.put(groupConfig.getObjectClass(), groupConfig); return result; } private EqualsHashCodeBuilder createHashCodeBuilder() { EqualsHashCodeBuilder builder = new EqualsHashCodeBuilder(); // Exposed configuration properties. builder.append(host); builder.append(port); builder.append(ssl); builder.append(failover); builder.append(principal); builder.append(credentials); for (String baseContext : baseContexts) { builder.append(baseContext); } builder.append(passwordAttribute); builder.append(accountSearchFilter); builder.append(groupMemberAttribute); builder.append(maintainLdapGroupMembership); builder.append(maintainPosixGroupMembership); builder.append(passwordHashAlgorithm); builder.append(respectResourcePasswordPolicyChangeAfterReset); builder.append(useBlocks); builder.append(blockSize); builder.append(usePagedResultControl); builder.append(vlvSortAttribute); builder.append(vlvSortOrderingRule); builder.append(uidAttribute); builder.append(readSchema); // Sync configuration properties. for (String baseContextToSynchronize : baseContextsToSynchronize) { builder.append(baseContextToSynchronize); } for (String objectClassToSynchronize : objectClassesToSynchronize) { builder.append(objectClassToSynchronize); } for (String attributeToSynchronize : attributesToSynchronize) { builder.append(attributeToSynchronize); } for (String modifiersNameToFilterOut : modifiersNamesToFilterOut) { builder.append(modifiersNameToFilterOut); } builder.append(accountSynchronizationFilter); builder.append(changeLogBlockSize); builder.append(changeNumberAttribute); builder.append(filterWithOrInsteadOfAnd); builder.append(removeLogEntryObjectClassFromFilter); builder.append(synchronizePasswords); builder.append(passwordAttributeToSynchronize); builder.append(passwordDecryptionKey); builder.append(passwordDecryptionInitializationVector); // Other state. builder.append(accountConfig); builder.append(groupConfig); return builder; } @Override public int hashCode() { return createHashCodeBuilder().hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof LdapConfiguration) { LdapConfiguration that = (LdapConfiguration) obj; if (this == that) { return true; } return this.createHashCodeBuilder().equals(that.createHashCodeBuilder()); } return false; } }