From 479948e6207d965b3e8aa25c30724d8698d63e34 Mon Sep 17 00:00:00 2001 From: Sylinsic <38617929+Sylinsic@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:42:30 +0000 Subject: [PATCH] [AD-75] Extend the LDAP bundle (#18) --- pom.xml | 2 +- .../connid/bundles/ad/ADConfiguration.java | 238 +++----- .../bundles/ad/ADConfigurationBeanInfo.java | 199 +++++-- .../connid/bundles/ad/ADConnection.java | 157 +----- .../tirasa/connid/bundles/ad/ADConnector.java | 105 +--- .../ad/authentication/ADAuthenticate.java | 53 +- .../connid/bundles/ad/crud/ADCreate.java | 46 +- .../connid/bundles/ad/crud/ADDelete.java | 21 +- .../connid/bundles/ad/crud/ADUpdate.java | 77 ++- .../connid/bundles/ad/schema/ADSchema.java | 43 +- .../bundles/ad/schema/ADSchemaBuilder.java | 40 +- .../ad/search/ADDefaultSearchStrategy.java | 25 - .../ad/search/ADPagedSearchStrategy.java | 174 ------ .../connid/bundles/ad/search/ADSearch.java | 164 ++---- .../ad/search/ADVlvIndexSearchStrategy.java | 208 +------ .../bundles/ad/sync/ADSyncStrategy.java | 114 +++- .../bundles/ad/sync/USNSyncStrategy.java | 38 +- .../ad/util/ADGuardedPasswordAttribute.java | 14 +- .../connid/bundles/ad/util/ADUtilities.java | 42 +- .../connid/bundles/ad/util/DirSyncUtils.java | 36 ++ .../connid/bundles/ad/Messages.properties | 133 ++++- .../connid/bundles/ad/AbstractTest.java | 20 +- .../connid/bundles/ad/AnyObjectTest.java | 37 ++ .../connid/bundles/ad/BasicFeaturesTest.java | 1 + .../tirasa/connid/bundles/ad/TestUtil.java | 48 +- .../ad/crud/AnyObjectCrudTestITCase.java | 508 ++++++++++++++++++ .../ad/sync/SyncAnyObjectTestITCase.java | 232 ++++++++ 27 files changed, 1548 insertions(+), 1227 deletions(-) create mode 100644 src/test/java/net/tirasa/connid/bundles/ad/AnyObjectTest.java create mode 100644 src/test/java/net/tirasa/connid/bundles/ad/crud/AnyObjectCrudTestITCase.java create mode 100644 src/test/java/net/tirasa/connid/bundles/ad/sync/SyncAnyObjectTestITCase.java diff --git a/pom.xml b/pom.xml index 5fd0f06..9d75fa7 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,7 @@ 1.5.2.0 - 1.5.8 + 1.5.9-SNAPSHOT 1.9 UTF-8 diff --git a/src/main/java/net/tirasa/connid/bundles/ad/ADConfiguration.java b/src/main/java/net/tirasa/connid/bundles/ad/ADConfiguration.java index acef10c..98359f5 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/ADConfiguration.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/ADConfiguration.java @@ -16,32 +16,26 @@ package net.tirasa.connid.bundles.ad; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import net.tirasa.connid.bundles.ad.search.ADDefaultSearchStrategy; +import net.tirasa.connid.bundles.ad.sync.ADSyncStrategy; import net.tirasa.connid.bundles.ad.util.ADUtilities; import net.tirasa.connid.bundles.ldap.LdapConfiguration; -import net.tirasa.connid.bundles.ldap.commons.LdapConstants; -import net.tirasa.connid.bundles.ldap.commons.ObjectClassMappingConfig; import net.tirasa.connid.bundles.ldap.search.DefaultSearchStrategy; -import org.identityconnectors.common.CollectionUtil; import org.identityconnectors.common.StringUtil; import org.identityconnectors.common.logging.Log; -import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.spi.ConfigurationProperty; public final class ADConfiguration extends LdapConfiguration { private final Log LOG = Log.getLog(ADConfiguration.class); - private boolean ssl = true; - private boolean retrieveDeletedUser = true; private boolean retrieveDeletedGroup = true; + private boolean retrieveDeletedAnyObject = true; + public static final String PROMPT_USER_FLAG = "pwdLastSet"; public static final String PROMPT_USER_VALUE = "0"; @@ -72,17 +66,13 @@ public final class ADConfiguration extends LdapConfiguration { private String defaultGroupContainer; - private SearchScope userSearchScope; - - private SearchScope groupSearchScope; - - private String groupSearchFilter; + private String defaultAnyObjectContainer; private String[] groupBaseContexts = {}; - + private String[] userBaseContexts = {}; - - private String groupMemberReferenceAttribute = "member"; + + private String[] anyObjectBaseContexts = {}; private String groupOwnerReferenceAttribute = "managedBy"; @@ -92,36 +82,23 @@ public final class ADConfiguration extends LdapConfiguration { private String defaultIdAttribute = "cn"; - private String syncStrategy = "net.tirasa.connid.bundles.ad.sync.ADSyncStrategy"; - private String[] userAuthenticationAttributes = {}; - private final ObjectClassMappingConfig accountConfig = new ObjectClassMappingConfig( - ObjectClass.ACCOUNT, - CollectionUtil.newList("top", "person", "organizationalPerson", "user"), - false, - CollectionUtil.newList("sAMAccountName", "cn", "member", "userPrincipalName"), - LdapConstants.PASSWORD); - - private final ObjectClassMappingConfig groupConfig = new ObjectClassMappingConfig( - ObjectClass.GROUP, - CollectionUtil.newList("top", "group"), - false, - Collections.emptyList()); - - private final ObjectClassMappingConfig allConfig = new ObjectClassMappingConfig( - ObjectClass.ALL, - CollectionUtil.newList("top"), - false, - Collections.emptyList()); - public ADConfiguration() { super(); - setAccountUserNameAttributes("sAMAccountName"); - + + setAccountObjectClasses("top", "person", "organizationalPerson", "user"); + setAccountUserNameAttributes("sAMAccountName", "cn", "member", "userPrincipalName"); setUidAttribute("sAMAccountName"); + + setGroupObjectClasses("top", "group"); + setGroupNameAttributes("sAMAccountName"); setGidAttribute("sAMAccountName"); + + setAnyObjectClasses("top"); + setAnyObjectNameAttributes("cn"); + setAoidAttribute("cn"); setDefaultIdAttribute("cn"); setSynchronizePasswords(false); @@ -130,11 +107,13 @@ public ADConfiguration() { setPasswordAttribute("unicodePwd"); setPort(636); + setSsl(true); - memberships = new ArrayList<>(); + setSyncStrategy("net.tirasa.connid.bundles.ad.sync.ADSyncStrategy"); + setFallbackSyncStrategyClass(ADSyncStrategy.class); + setConnectionClass(ADConnection.class); - userSearchScope = SearchScope.subtree; - groupSearchScope = SearchScope.subtree; + memberships = new ArrayList<>(); } @Override @@ -142,30 +121,6 @@ public DefaultSearchStrategy newDefaultSearchStrategy(boolean ignoreNonExistingB return new ADDefaultSearchStrategy(ignoreNonExistingBaseDN); } - @Override - @ConfigurationProperty(displayMessageKey = "ssl.display", - helpMessageKey = "ssl.help", order = 1) - public boolean isSsl() { - return ssl; - } - - @Override - public void setSsl(final boolean ssl) { - super.setSsl(ssl); - this.ssl = ssl; - } - - @Override - public Map getObjectClassMappingConfigs() { - HashMap result = new HashMap<>(); - result.put(accountConfig.getObjectClass(), accountConfig); - result.put(groupConfig.getObjectClass(), groupConfig); - - allConfig.setShortNameLdapAttributes(CollectionUtil.newList(getDefaultIdAttribute())); - result.put(allConfig.getObjectClass(), allConfig); - return result; - } - @ConfigurationProperty(displayMessageKey = "memberships.display", helpMessageKey = "memberships.help", order = 1) public String[] getMemberships() { @@ -206,8 +161,19 @@ public void setRetrieveDeletedGroup(boolean retrieveDeletedGroup) { this.retrieveDeletedGroup = retrieveDeletedGroup; } + @ConfigurationProperty(displayMessageKey = "retrieveDeletedAnyObject.display", + helpMessageKey = "retrieveDeletedAnyObject.help", order = 4) + public boolean isRetrieveDeletedAnyObject() { + return this.retrieveDeletedAnyObject; + } + + public void setRetrieveDeletedAnyObject(boolean retrieveDeletedAnyObject) { + this.retrieveDeletedAnyObject = retrieveDeletedAnyObject; + } + + @ConfigurationProperty(displayMessageKey = "trustAllCerts.display", - helpMessageKey = "trustAllCerts.help", order = 4) + helpMessageKey = "trustAllCerts.help", order = 5) public boolean isTrustAllCerts() { return trustAllCerts; } @@ -221,30 +187,17 @@ public boolean isMembershipsInOr() { } @ConfigurationProperty(displayMessageKey = "membershipsInOr.display", - helpMessageKey = "membershipsInOr.help", order = 5) + helpMessageKey = "membershipsInOr.help", order = 6) public void setMembershipsInOr(boolean membershipsInOr) { this.membershipsInOr = membershipsInOr; } - @ConfigurationProperty(order = 6, required = true, - displayMessageKey = "baseContextsToSynchronize.display", - helpMessageKey = "baseContextsToSynchronize.help") - @Override - public String[] getBaseContextsToSynchronize() { - return super.getBaseContextsToSynchronize(); - } - - @Override - public void setBaseContextsToSynchronize(String... baseContextsToSynchronize) { - super.setBaseContextsToSynchronize(baseContextsToSynchronize); - } - @ConfigurationProperty(displayMessageKey = "defaultPeopleContainer.display", helpMessageKey = "defaultPeopleContainer.help", order = 7) public String getDefaultPeopleContainer() { if (StringUtil.isBlank(defaultPeopleContainer)) { - return getBaseContextsToSynchronize() == null || getBaseContextsToSynchronize().length < 1 - ? null : getBaseContextsToSynchronize()[0]; + return getBaseContexts() == null || getBaseContexts().length < 1 + ? null : getBaseContexts()[0]; } else { return defaultPeopleContainer; } @@ -258,8 +211,8 @@ public void setDefaultPeopleContainer(String defaultPeopleContainer) { helpMessageKey = "defaultGroupContainer.help", order = 8) public String getDefaultGroupContainer() { if (StringUtil.isBlank(defaultGroupContainer)) { - return getBaseContextsToSynchronize() == null || getBaseContextsToSynchronize().length < 1 - ? null : getBaseContextsToSynchronize()[0]; + return getBaseContexts() == null || getBaseContexts().length < 1 + ? null : getBaseContexts()[0]; } else { return defaultGroupContainer; } @@ -269,47 +222,30 @@ public void setDefaultGroupContainer(String defaultGroupContainer) { this.defaultGroupContainer = defaultGroupContainer; } - @ConfigurationProperty(displayMessageKey = "userSearchScope.display", - helpMessageKey = "userSearchScope.help", order = 9) - public String getUserSearchScope() { - return userSearchScope == null ? SearchScope.subtree.toString() : userSearchScope.toString(); - } - - public void setUserSearchScope(final String userSearchScope) { - this.userSearchScope = SearchScope.valueOf(userSearchScope.toLowerCase()); - } - - @ConfigurationProperty(displayMessageKey = "groupSearchScope.display", - helpMessageKey = "groupSearchScope.help", order = 10) - public String getGroupSearchScope() { - return groupSearchFilter == null ? SearchScope.subtree.toString() : groupSearchScope.toString(); - } - - public void setGroupSearchScope(final String groupSearchScope) { - this.groupSearchScope = SearchScope.valueOf(groupSearchScope.toLowerCase()); - } - - @ConfigurationProperty(displayMessageKey = "groupSearchFilter.display", - helpMessageKey = "groupSearchFilter.help", order = 11) - @Override - public String getGroupSearchFilter() { - return groupSearchFilter; + @ConfigurationProperty(displayMessageKey = "defaultAnyObjectContainer.display", + helpMessageKey = "defaultAnyObjectContainer.help", order = 9) + public String getDefaultAnyObjectContainer() { + if (StringUtil.isBlank(defaultAnyObjectContainer)) { + return getBaseContexts() == null || getBaseContexts().length < 1 + ? null : getBaseContexts()[0]; + } else { + return defaultAnyObjectContainer; + } } - @Override - public void setGroupSearchFilter(String groupSearchFilter) { - this.groupSearchFilter = groupSearchFilter; + public void setDefaultAnyObjectContainer(String defaultAnyObjectContainer) { + this.defaultAnyObjectContainer = defaultAnyObjectContainer; } @ConfigurationProperty(displayMessageKey = "groupBaseContexts.display", - helpMessageKey = "groupBaseContexts.help", order = 12) + helpMessageKey = "groupBaseContexts.help", order = 10) public String[] getGroupBaseContexts() { if (groupBaseContexts != null && groupBaseContexts.length > 0) { // return specified configuration return groupBaseContexts.clone(); } else { // return root suffixes - return getBaseContextsToSynchronize(); + return getBaseContexts(); } } @@ -320,14 +256,14 @@ public void setGroupBaseContexts(String... baseContexts) { } @ConfigurationProperty(displayMessageKey = "userBaseContexts.display", - helpMessageKey = "userBaseContexts.help", order = 13) + helpMessageKey = "userBaseContexts.help", order = 11) public String[] getUserBaseContexts() { if (userBaseContexts != null && userBaseContexts.length > 0) { // return specified configuration return userBaseContexts.clone(); } else { // return root suffixes - return getBaseContextsToSynchronize(); + return getBaseContexts(); } } @@ -337,24 +273,26 @@ public void setUserBaseContexts(final String... baseContexts) { super.setBaseContexts(this.getBaseContexts()); } - @Override - public String[] getBaseContexts() { - // return root suffixes - return this.getBaseContextsToSynchronize(); - } - - @ConfigurationProperty(displayMessageKey = "groupMemberReferenceAttribute.display", - helpMessageKey = "groupMemberReferenceAttribute.help", order = 14) - public String getGroupMemberReferenceAttribute() { - return StringUtil.isBlank(groupMemberReferenceAttribute) ? "member" : groupMemberReferenceAttribute; + @ConfigurationProperty(displayMessageKey = "anyObjectBaseContexts.display", + helpMessageKey = "anyObjectBaseContexts.help", order = 12) + public String[] getAnyObjectBaseContexts() { + if (anyObjectBaseContexts != null && anyObjectBaseContexts.length > 0) { + // return specified configuration + return anyObjectBaseContexts.clone(); + } else { + // return root suffixes + return getBaseContexts(); + } } - public void setGroupMemberReferenceAttribute(String groupMemberReferenceAttribute) { - this.groupMemberReferenceAttribute = groupMemberReferenceAttribute; + public void setAnyObjectBaseContexts(final String... baseContexts) { + this.anyObjectBaseContexts = baseContexts.clone(); + // update base context everytime ... + super.setBaseContexts(this.getBaseContexts()); } @ConfigurationProperty(displayMessageKey = "groupOwnerReferenceAttribute.display", - helpMessageKey = "groupOwnerReferenceAttribute.help", order = 15) + helpMessageKey = "groupOwnerReferenceAttribute.help", order = 13) public String getGroupOwnerReferenceAttribute() { return StringUtil.isBlank(groupOwnerReferenceAttribute) ? "managedBy" : groupOwnerReferenceAttribute; } @@ -368,13 +306,13 @@ public boolean isPwdUpdateOnly() { } @ConfigurationProperty(displayMessageKey = "pwdUpdateOnly.display", - helpMessageKey = "pwdUpdateOnly.help", required = true, order = 17) + helpMessageKey = "pwdUpdateOnly.help", required = true, order = 14) public void setPwdUpdateOnly(boolean pwdUpdateOnly) { this.pwdUpdateOnly = pwdUpdateOnly; } @ConfigurationProperty(displayMessageKey = "membershipConservativePolicy.display", - helpMessageKey = "membershipConservativePolicy.help", order = 18) + helpMessageKey = "membershipConservativePolicy.help", order = 15) public boolean isMembershipConservativePolicy() { return membershipConservativePolicy; } @@ -383,7 +321,7 @@ public void setMembershipConservativePolicy(boolean membershipConservativePolicy this.membershipConservativePolicy = membershipConservativePolicy; } - @ConfigurationProperty(order = 19, + @ConfigurationProperty(order = 16, displayMessageKey = "defaultIdAttribute.display", helpMessageKey = "defaultIdAttribute.help") public String getDefaultIdAttribute() { @@ -395,7 +333,7 @@ public void setDefaultIdAttribute(final String defaultIdAttribute) { } @ConfigurationProperty(displayMessageKey = "excludeAttributeChangesOnUpdate.display", - helpMessageKey = "excludeAttributeChangesOnUpdate.help", order = 20) + helpMessageKey = "excludeAttributeChangesOnUpdate.help", order = 17) public boolean isExcludeAttributeChangesOnUpdate() { return excludeAttributeChangesOnUpdate; } @@ -404,28 +342,8 @@ public void setExcludeAttributeChangesOnUpdate(boolean excludeAttributeChangesOn this.excludeAttributeChangesOnUpdate = excludeAttributeChangesOnUpdate; } - @Override - public final void setUidAttribute(final String uidAttribute) { - super.setUidAttribute(uidAttribute); - } - - @Override - public final void setGidAttribute(final String gidAttribute) { - super.setGidAttribute(gidAttribute); - } - - @ConfigurationProperty(displayMessageKey = "syncStrategy.display", - helpMessageKey = "syncStrategy.help", order = 21) - public String getSyncStrategy() { - return syncStrategy; - } - - public void setSyncStrategy(String syncStrategy) { - this.syncStrategy = syncStrategy; - } - @ConfigurationProperty(displayMessageKey = "userAuthenticationAttributes.display", - helpMessageKey = "userAuthenticationAttributes.help", order = 22) + helpMessageKey = "userAuthenticationAttributes.help", order = 18) public String[] getUserAuthenticationAttributes() { if (userAuthenticationAttributes != null && userAuthenticationAttributes.length > 0) { return userAuthenticationAttributes.clone(); @@ -441,12 +359,4 @@ public void setUserAuthenticationAttributes(final String... userAuthenticationAt this.userAuthenticationAttributes = userAuthenticationAttributes.clone(); } } - - public enum SearchScope { - - object, - onelevel, - subtree - - } } diff --git a/src/main/java/net/tirasa/connid/bundles/ad/ADConfigurationBeanInfo.java b/src/main/java/net/tirasa/connid/bundles/ad/ADConfigurationBeanInfo.java index bd9df3d..a19376f 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/ADConfigurationBeanInfo.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/ADConfigurationBeanInfo.java @@ -32,8 +32,14 @@ public class ADConfigurationBeanInfo extends SimpleBeanInfo { public PropertyDescriptor[] getPropertyDescriptors() { final List props = new ArrayList<>(); try { + /* + * Connectivity + */ // ssl - props.add(new PropertyDescriptor("ssl", ADConfiguration.class)); + props.add(new PropertyDescriptor("ssl", LdapConfiguration.class)); + + // trustAllCerts + props.add(new PropertyDescriptor("trustAllCerts", ADConfiguration.class)); // host props.add(new PropertyDescriptor("host", LdapConfiguration.class)); @@ -41,100 +47,211 @@ public PropertyDescriptor[] getPropertyDescriptors() { // port props.add(new PropertyDescriptor("port", LdapConfiguration.class)); + // failover + props.add(new PropertyDescriptor("failover", LdapConfiguration.class)); + // principal props.add(new PropertyDescriptor("principal", LdapConfiguration.class)); + // credentials + props.add(new PropertyDescriptor("credentials", LdapConfiguration.class)); + + // connectTimeout + props.add(new PropertyDescriptor("connectTimeout", LdapConfiguration.class)); + + // readTimeout + props.add(new PropertyDescriptor("readTimeout", LdapConfiguration.class)); + + /* + * Identifying attributes + */ // uidAttribute props.add(new PropertyDescriptor("uidAttribute", LdapConfiguration.class)); - + // gidAttribute props.add(new PropertyDescriptor("gidAttribute", LdapConfiguration.class)); + + // aoidAttribute + props.add(new PropertyDescriptor("aoidAttribute", LdapConfiguration.class)); // defaultIdAttribute props.add(new PropertyDescriptor("defaultIdAttribute", ADConfiguration.class)); + + // dnAttribute + props.add(new PropertyDescriptor("dnAttribute", LdapConfiguration.class)); - // credentials - props.add(new PropertyDescriptor("credentials", LdapConfiguration.class)); + // userAuthenticationAttributes + props.add(new PropertyDescriptor("userAuthenticationAttributes", ADConfiguration.class)); - // trustAllCerts - props.add(new PropertyDescriptor("trustAllCerts", ADConfiguration.class)); + /* + * Password attributes + */ + // passwordAttribute + props.add(new PropertyDescriptor("passwordAttribute", LdapConfiguration.class)); - // membershipsInOr - props.add(new PropertyDescriptor("membershipsInOr", ADConfiguration.class)); + // passwordAttributeToSynchronize + props.add(new PropertyDescriptor("passwordAttributeToSynchronize", LdapConfiguration.class)); + + // passwordDecryptionInitializationVector + props.add(new PropertyDescriptor("passwordDecryptionInitializationVector", LdapConfiguration.class)); + + // passwordDecryptionKey + props.add(new PropertyDescriptor("passwordDecryptionKey", LdapConfiguration.class)); + + // passwordHashAlgorithm + props.add(new PropertyDescriptor("passwordHashAlgorithm", LdapConfiguration.class)); + + // synchronizePasswords + props.add(new PropertyDescriptor("synchronizePasswords", LdapConfiguration.class)); + + // retrievePasswordsWithSearch + props.add(new PropertyDescriptor("retrievePasswordsWithSearch", LdapConfiguration.class)); // pwdUpdateOnly props.add(new PropertyDescriptor("pwdUpdateOnly", ADConfiguration.class)); - // excludeAttributeChangesOnUpdate - props.add(new PropertyDescriptor("excludeAttributeChangesOnUpdate", ADConfiguration.class)); + /* + * Object classes + */ + // accountObjectClasses + props.add(new PropertyDescriptor("accountObjectClasses", LdapConfiguration.class)); + + // groupObjectClasses + props.add(new PropertyDescriptor("groupObjectClasses", LdapConfiguration.class)); - // failover - props.add(new PropertyDescriptor("failover", LdapConfiguration.class)); + // anyObjectClasses + props.add(new PropertyDescriptor("anyObjectClasses", LdapConfiguration.class)); - // baseContextsToSynchronize - props.add(new PropertyDescriptor("baseContextsToSynchronize", ADConfiguration.class)); + /* + * Base contexts + */ + // baseContexts + props.add(new PropertyDescriptor("baseContexts", LdapConfiguration.class)); // userBaseContexts props.add(new PropertyDescriptor("userBaseContexts", ADConfiguration.class)); - + // groupBaseContexts props.add(new PropertyDescriptor("groupBaseContexts", ADConfiguration.class)); - - // baseContexts + + // anyObjectBaseContexts + props.add(new PropertyDescriptor("anyObjectBaseContexts", ADConfiguration.class)); + + // defaultPeopleContainer props.add(new PropertyDescriptor("defaultPeopleContainer", ADConfiguration.class)); - + // defaultGroupContainer props.add(new PropertyDescriptor("defaultGroupContainer", ADConfiguration.class)); + // defaultAnyObjectContainer + props.add(new PropertyDescriptor("defaultAnyObjectContainer", ADConfiguration.class)); + + /* + * Group memberships & owners + */ // memberships props.add(new PropertyDescriptor("memberships", ADConfiguration.class)); - + // membershipConservativePolicy props.add(new PropertyDescriptor("membershipConservativePolicy", ADConfiguration.class)); + + // membershipsInOr + props.add(new PropertyDescriptor("membershipsInOr", ADConfiguration.class)); + + // groupOwnerReferenceAttribute + props.add(new PropertyDescriptor("groupOwnerReferenceAttribute", ADConfiguration.class)); + + // groupMemberAttribute + props.add(new PropertyDescriptor("groupMemberAttribute", LdapConfiguration.class)); + + // addPrincipalToNewGroups + props.add(new PropertyDescriptor("addPrincipalToNewGroups", LdapConfiguration.class)); + + /* + * Search scopes + */ + // userSearchScope + props.add(new PropertyDescriptor("userSearchScope", LdapConfiguration.class)); + + // groupSearchScope + props.add(new PropertyDescriptor("groupSearchScope", LdapConfiguration.class)); + + // anyObjectSearchScope + props.add(new PropertyDescriptor("anyObjectSearchScope", LdapConfiguration.class)); + /* + * Search filters + */ // accountSearchFilter props.add(new PropertyDescriptor("accountSearchFilter", LdapConfiguration.class)); - + // groupSearchFilter - props.add(new PropertyDescriptor("groupSearchFilter", ADConfiguration.class)); + props.add(new PropertyDescriptor("groupSearchFilter", LdapConfiguration.class)); + + // anyObjectSearchFilter + props.add(new PropertyDescriptor("anyObjectSearchFilter", LdapConfiguration.class)); + /* + * VLV attributes + */ + // useVlvControls + props.add(new PropertyDescriptor("useVlvControls", LdapConfiguration.class)); + + // vlvSortAttribute + props.add(new PropertyDescriptor("vlvSortAttribute", LdapConfiguration.class)); + + /* + * Retrieve deleted + */ // retrieveDeletedUser props.add(new PropertyDescriptor("retrieveDeletedUser", ADConfiguration.class)); - + // retrieveDeletedGroup props.add(new PropertyDescriptor("retrieveDeletedGroup", ADConfiguration.class)); - // accountObjectClasses - props.add(new PropertyDescriptor("accountObjectClasses", LdapConfiguration.class)); + // retrieveDeletedAnyObject + props.add(new PropertyDescriptor("retrieveDeletedAnyObject", ADConfiguration.class)); + /* + * Syncs + */ + // syncStrategy + props.add(new PropertyDescriptor("syncStrategy", LdapConfiguration.class)); + + // baseContextsToSynchronize + props.add(new PropertyDescriptor("baseContextsToSynchronize", LdapConfiguration.class)); + // objectClassesToSynchronize props.add(new PropertyDescriptor("objectClassesToSynchronize", LdapConfiguration.class)); + + // attributesToSynchronize + props.add(new PropertyDescriptor("attributesToSynchronize", LdapConfiguration.class)); - // _connectorMessages - props.add(new PropertyDescriptor("connectorMessages", AbstractConfiguration.class)); + // changeLogBlockSize + props.add(new PropertyDescriptor("changeLogBlockSize", LdapConfiguration.class)); - // userSearchScope - props.add(new PropertyDescriptor("userSearchScope", ADConfiguration.class)); - - // groupSearchScope - props.add(new PropertyDescriptor("groupSearchScope", ADConfiguration.class)); + // changeNumberAttribute + props.add(new PropertyDescriptor("changeNumberAttribute", LdapConfiguration.class)); - // groupOwnerReferenceAttribute - props.add(new PropertyDescriptor("groupOwnerReferenceAttribute", ADConfiguration.class)); + // accountSynchronizationFilter + props.add(new PropertyDescriptor("accountSynchronizationFilter", LdapConfiguration.class)); - // groupMemberReferenceAttribute - props.add(new PropertyDescriptor("groupMemberReferenceAttribute", ADConfiguration.class)); - - // syncStrategy - props.add(new PropertyDescriptor("syncStrategy", ADConfiguration.class)); + /* + * Misc + */ + // _connectorMessages + props.add(new PropertyDescriptor("connectorMessages", AbstractConfiguration.class)); - // userAuthenticationAttributes - props.add(new PropertyDescriptor("userAuthenticationAttributes", ADConfiguration.class)); + // excludeAttributeChangesOnUpdate + props.add(new PropertyDescriptor("excludeAttributeChangesOnUpdate", ADConfiguration.class)); + + // readSchema + props.add(new PropertyDescriptor("readSchema", LdapConfiguration.class)); } catch (IntrospectionException e) { LOG.error(e, "Failure retrieving properties"); props.clear(); } - + return props.toArray(new PropertyDescriptor[props.size()]); } } diff --git a/src/main/java/net/tirasa/connid/bundles/ad/ADConnection.java b/src/main/java/net/tirasa/connid/bundles/ad/ADConnection.java index d22bf68..f36b80a 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/ADConnection.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/ADConnection.java @@ -15,28 +15,25 @@ */ package net.tirasa.connid.bundles.ad; -import static net.tirasa.connid.bundles.ldap.commons.LdapUtil.nullAsEmpty; import static org.identityconnectors.common.StringUtil.isNotBlank; -import com.sun.jndi.ldap.ctl.PasswordExpiredResponseControl; import java.util.ArrayList; import java.util.Hashtable; import java.util.List; -import javax.naming.AuthenticationException; import javax.naming.Context; import javax.naming.NamingException; -import javax.naming.directory.Attributes; import javax.naming.ldap.Control; -import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; import net.tirasa.adsddl.ntsd.controls.SDFlagsControl; import net.tirasa.connid.bundles.ad.schema.ADSchema; import net.tirasa.connid.bundles.ad.util.TrustAllSocketFactory; +import net.tirasa.connid.bundles.ldap.LdapConfiguration; import net.tirasa.connid.bundles.ldap.LdapConnection; +import net.tirasa.connid.bundles.ldap.commons.LdapConstants; + import org.identityconnectors.common.Pair; import org.identityconnectors.common.logging.Log; import org.identityconnectors.common.security.GuardedString; -import org.identityconnectors.framework.common.exceptions.ConnectorException; import org.identityconnectors.framework.common.exceptions.InvalidCredentialException; public class ADConnection extends LdapConnection { @@ -49,45 +46,13 @@ public class ADConnection extends LdapConnection { private static final String LDAP_BINARY_ATTRIBUTE = "java.naming.ldap.attributes.binary"; - private LdapContext initCtx = null; - private LdapContext syncCtx = null; - private final ADSchema schema; - - private final ADConfiguration config; - - public ADConnection(ADConfiguration config) { + public ADConnection(LdapConfiguration config) { super(config); - this.config = config; schema = new ADSchema(this); } - @Override - public AuthenticationResult authenticate(final String entryDN, final GuardedString password) { - assert entryDN != null; - - if (LOG.isOk()) { - LOG.ok("Attempting to authenticate {0}", entryDN); - } - - final Pair pair = createContext(entryDN, password); - - if (pair.second != null) { - quietClose(pair.second); - } - - if (LOG.isOk()) { - LOG.ok("Authentication result: {0}", pair.first); - } - - return pair.first; - } - - public ADSchema getADSchema() { - return schema; - } - public LdapContext getSyncContext(final Control[] control) { return cloneContext(control); } @@ -96,10 +61,8 @@ public LdapContext getSyncContext(final Control[] control) { public void close() { try { super.close(); - quietClose(initCtx); quietClose(syncCtx); } finally { - initCtx = null; syncCtx = null; } } @@ -113,16 +76,6 @@ private LdapContext cloneContext(final Control[] controls) { } } - private static void quietClose(final LdapContext ctx) { - try { - if (ctx != null) { - ctx.close(); - } - } catch (NamingException e) { - LOG.warn(e, "Failure closing context"); - } - } - @Override public LdapContext getInitialContext() { if (this.initCtx != null) { @@ -140,22 +93,8 @@ public LdapContext getInitialContext() { return initCtx; } - private LdapContext connect(String principal, GuardedString credentials) { - final Pair pair = createContext(principal, credentials); - - if (LOG.isOk()) { - LOG.ok("Authentication result {0}", pair.first.getType()); - } - - if (pair.first.getType().equals(AuthenticationResultType.SUCCESS)) { - return pair.second; - } - - pair.first.propagate(); - throw new IllegalStateException("Should never get here"); - } - - private Pair createContext( + @Override + protected Pair createContext( final String principal, final GuardedString credentials) { final List> result = new ArrayList<>(1); @@ -166,11 +105,13 @@ private Pair createContext( env.put(Context.INITIAL_CONTEXT_FACTORY, LDAP_CTX_FACTORY); env.put(Context.PROVIDER_URL, getLdapUrls()); env.put(Context.REFERRAL, "follow"); + env.put(LdapConstants.CONNECT_TIMEOUT_ENV_PROP, Long.toString(config.getConnectTimeout())); + env.put(LdapConstants.READ_TIMEOUT_ENV_PROP, Long.toString(config.getReadTimeout())); if (config.isSsl()) { env.put(Context.SECURITY_PROTOCOL, "ssl"); - if (config.isTrustAllCerts()) { + if (((ADConfiguration) config).isTrustAllCerts()) { env.put(LDAP_CTX_SOCKET_FACTORY, TrustAllSocketFactory.class.getName()); } } @@ -204,84 +145,4 @@ private Pair createContext( return result.get(0); } - - private Pair createContext( - @SuppressWarnings("UseOfObsoleteCollectionType") - final Hashtable env) { - - AuthenticationResult authnResult = null; - InitialLdapContext context = null; - - try { - context = new InitialLdapContext(env, null); - - if (config.isRespectResourcePasswordPolicyChangeAfterReset()) { - if (hasPasswordExpiredControl(context.getResponseControls())) { - authnResult = new AuthenticationResult( - AuthenticationResultType.PASSWORD_EXPIRED); - } - } - } catch (AuthenticationException e) { - // TODO: check AD response - String message = e.getMessage().toLowerCase(); - if (message.contains("password expired")) { // Sun DS. - authnResult = new AuthenticationResult(AuthenticationResultType.PASSWORD_EXPIRED, e); - } else if (message.contains("password has expired")) { // RACF. - authnResult = new AuthenticationResult(AuthenticationResultType.PASSWORD_EXPIRED, e); - } else { - authnResult = new AuthenticationResult(AuthenticationResultType.FAILED, e); - } - - } catch (NamingException e) { - authnResult = new AuthenticationResult(AuthenticationResultType.FAILED, e); - } - - if (authnResult == null) { - assert context != null; - - authnResult = new AuthenticationResult(AuthenticationResultType.SUCCESS); - } - - return new Pair<>(authnResult, context); - } - - private static boolean hasPasswordExpiredControl(final Control[] controls) { - if (controls != null) { - for (Control control : controls) { - if (control instanceof PasswordExpiredResponseControl) { - return true; - } - } - } - return false; - } - - private String getLdapUrls() { - final StringBuilder builder = new StringBuilder(); - - builder.append("ldap://"). - append(config.getHost()).append(':').append(config.getPort()); - - for (String failover : nullAsEmpty(config.getFailover())) { - builder.append(' '); - builder.append(failover); - } - - return builder.toString(); - } - - @Override - public void test() { - checkAlive(); - } - - @Override - public void checkAlive() { - try { - final Attributes attrs = getInitialContext().getAttributes("", new String[] { "subschemaSubentry" }); - attrs.get("subschemaSubentry"); - } catch (NamingException e) { - throw new ConnectorException(e); - } - } } diff --git a/src/main/java/net/tirasa/connid/bundles/ad/ADConnector.java b/src/main/java/net/tirasa/connid/bundles/ad/ADConnector.java index e30e315..fd0bec6 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/ADConnector.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/ADConnector.java @@ -25,12 +25,9 @@ import net.tirasa.connid.bundles.ad.crud.ADDelete; import net.tirasa.connid.bundles.ad.crud.ADUpdate; import net.tirasa.connid.bundles.ad.search.ADSearch; -import net.tirasa.connid.bundles.ad.sync.USNSyncStrategy; -import net.tirasa.connid.bundles.ad.sync.ADSyncStrategy; import net.tirasa.connid.bundles.ldap.LdapConnector; import net.tirasa.connid.bundles.ldap.commons.LdapConstants; import net.tirasa.connid.bundles.ldap.search.LdapFilter; -import org.identityconnectors.common.StringUtil; import org.identityconnectors.common.security.GuardedString; import org.identityconnectors.framework.common.objects.Attribute; import org.identityconnectors.framework.common.objects.AttributeBuilder; @@ -39,11 +36,7 @@ import org.identityconnectors.framework.common.objects.OperationOptions; import org.identityconnectors.framework.common.objects.OperationalAttributes; import org.identityconnectors.framework.common.objects.ResultsHandler; -import org.identityconnectors.framework.common.objects.Schema; -import org.identityconnectors.framework.common.objects.SyncResultsHandler; -import org.identityconnectors.framework.common.objects.SyncToken; import org.identityconnectors.framework.common.objects.Uid; -import org.identityconnectors.framework.spi.Configuration; import org.identityconnectors.framework.spi.ConnectorClass; /** @@ -82,54 +75,6 @@ public class ADConnector extends LdapConnector { public static final int UF_PASSWORD_EXPIRED = 0x800000; - /** - * The configuration for this connector instance. - */ - private transient ADConfiguration config; - - /** - * The relative DirSyncSyncStrategy instance which sync-related operations are delegated to. - */ - private transient ADSyncStrategy syncStrategy; - - /** - * The connection to the AD server. - */ - private transient ADConnection conn; - - @Override - public Configuration getConfiguration() { - return config; - } - - @Override - public void init(final Configuration cfg) { - - config = (ADConfiguration) cfg; - - // TODO: easier and more efficient if conn was protected in superclass - conn = new ADConnection(config); - - String syncStrategyClassName = config.getSyncStrategy(); - if (StringUtil.isNotEmpty(syncStrategyClassName)) { - try { - syncStrategy = (USNSyncStrategy) Class.forName(syncStrategyClassName). - getConstructor(ADConnection.class).newInstance(conn); - } catch (Exception ex) { - syncStrategy = new ADSyncStrategy(conn); - } - } else { - syncStrategy = new ADSyncStrategy(conn); - } - - super.init(cfg); - } - - @Override - public void dispose() { - conn.close(); - super.dispose(); - } @Override public void executeQuery( @@ -137,19 +82,7 @@ public void executeQuery( final LdapFilter query, final ResultsHandler handler, final OperationOptions options) { - new ADSearch(conn, oclass, query, handler, options).executeADQuery(handler); - } - - @Override - public SyncToken getLatestSyncToken(final ObjectClass oclass) { - return syncStrategy.getLatestSyncToken(); - } - - @Override - public void sync(final ObjectClass oclass, final SyncToken token, - final SyncResultsHandler handler, final OperationOptions options) { - - syncStrategy.sync(token, handler, options, oclass); + new ADSearch(conn, oclass, query, handler, options).execute(); } @Override @@ -176,14 +109,14 @@ public Uid create( : Arrays.asList(ldapGroups.getValue().toArray(new String[ldapGroups.getValue().size()]))); } - ldapGroupsToBeAdded.addAll(config.getMemberships() == null - ? Collections.emptyList() : Arrays.asList(config.getMemberships())); + ldapGroupsToBeAdded.addAll(((ADConfiguration) config).getMemberships() == null + ? Collections.emptyList() : Arrays.asList(((ADConfiguration) config).getMemberships())); // add groups attributes.add(AttributeBuilder.build("ldapGroups", ldapGroupsToBeAdded)); } - return new ADCreate(conn, oclass, attributes, options).create(); + return new ADCreate((ADConnection) conn, oclass, attributes, options).execute(); } @Override @@ -211,17 +144,18 @@ public Uid update( ldapGroups.getValue() == null ? Collections.emptyList() : Arrays.asList(ldapGroups.getValue().toArray( - new String[ldapGroups.getValue().size()]))); + new String[ldapGroups.getValue().size()]))); - ldapGroupsToBeAdded.addAll(config.getMemberships() == null - ? Collections.emptyList() : Arrays.asList(config.getMemberships())); + String[] memberships = ((ADConfiguration) config).getMemberships(); + ldapGroupsToBeAdded.addAll(memberships == null + ? Collections.emptyList() : Arrays.asList(memberships)); // add groups attributes.add(AttributeBuilder.build("ldapGroups", ldapGroupsToBeAdded)); } } - return new ADUpdate(conn, oclass, uid).update(attributes); + return new ADUpdate((ADConnection) conn, oclass, uid).update(attributes); } @Override @@ -234,12 +168,7 @@ public void delete( throw new IllegalStateException("Delete operation not permitted"); } - new ADDelete(conn, oclass, uid).delete(); - } - - @Override - public Schema schema() { - return conn.getADSchema().getSchema(); + new ADDelete((ADConnection) conn, oclass, uid).execute(); } @Override @@ -249,7 +178,7 @@ public Uid authenticate( final GuardedString password, final OperationOptions options) { - return new ADAuthenticate(conn, objectClass, username, options).authenticate(password); + return new ADAuthenticate((ADConnection) conn, objectClass, username, options).authenticate(password); } @Override @@ -258,16 +187,6 @@ public Uid resolveUsername( final String username, final OperationOptions options) { - return new ADAuthenticate(conn, objectClass, username, options).resolveUsername(); - } - - @Override - public void test() { - conn.test(); - } - - @Override - public void checkAlive() { - conn.checkAlive(); + return new ADAuthenticate((ADConnection) conn, objectClass, username, options).resolveUsername(); } } diff --git a/src/main/java/net/tirasa/connid/bundles/ad/authentication/ADAuthenticate.java b/src/main/java/net/tirasa/connid/bundles/ad/authentication/ADAuthenticate.java index 2826f15..5f94bea 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/authentication/ADAuthenticate.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/authentication/ADAuthenticate.java @@ -21,9 +21,8 @@ import java.util.Map; import net.tirasa.connid.bundles.ad.ADConfiguration; import net.tirasa.connid.bundles.ad.ADConnection; +import net.tirasa.connid.bundles.ldap.LdapAuthenticate; import net.tirasa.connid.bundles.ldap.LdapConnection.AuthenticationResult; -import net.tirasa.connid.bundles.ldap.LdapConnection.AuthenticationResultType; -import net.tirasa.connid.bundles.ldap.commons.LdapConstants; import net.tirasa.connid.bundles.ldap.search.LdapSearches; import org.identityconnectors.common.logging.Log; import org.identityconnectors.common.security.GuardedString; @@ -37,27 +36,16 @@ import org.identityconnectors.framework.common.objects.OperationOptions; import org.identityconnectors.framework.common.objects.Uid; -public class ADAuthenticate { +public class ADAuthenticate extends LdapAuthenticate { private static final Log LOG = Log.getLog(ADAuthenticate.class); - private final ADConnection conn; - - private final ObjectClass oclass; - - private final String username; - - private final OperationOptions options; - public ADAuthenticate( final ADConnection conn, final ObjectClass oclass, final String username, final OperationOptions options) { - this.conn = conn; - this.oclass = oclass; - this.username = username; - this.options = options; + super(conn, oclass, username, options); } public Uid authenticate(GuardedString password) { @@ -87,23 +75,18 @@ public Uid authenticate(GuardedString password) { return authnObject.getUid(); } - private static boolean isSuccess(final AuthenticationResult authResult) { - // PASSWORD_EXPIRED considered success: credentials were right. - return authResult != null - && (authResult.getType().equals(AuthenticationResultType.SUCCESS) - || authResult.getType().equals(AuthenticationResultType.PASSWORD_EXPIRED)); - } - - private ConnectorObject getObjectToAuthenticate() { + @Override + protected ConnectorObject getObjectToAuthenticate() { List userNameAttrs = getUserNameAttributes(); Map entryDN2Object = new HashMap<>(); - + final String dnAttributeName = conn.getConfiguration().getDnAttribute(); for (String baseContext : ((ADConfiguration) conn.getConfiguration()).getUserBaseContexts()) { for (String userNameAttr : userNameAttrs) { Attribute attr = AttributeBuilder.build(userNameAttr, username); - for (ConnectorObject object : LdapSearches.findObjects(conn, oclass, baseContext, attr, "entryDN")) { - String entryDN = object.getAttributeByName("entryDN").getValue().get(0).toString(); + for (ConnectorObject object : LdapSearches.findObjects(conn, oclass, baseContext, attr, + dnAttributeName)) { + String entryDN = object.getAttributeByName(dnAttributeName).getValue().get(0).toString(); entryDN2Object.put(entryDN, object); } @@ -122,25 +105,13 @@ private ConnectorObject getObjectToAuthenticate() { return null; } - private List getUserNameAttributes() { + @Override + protected List getUserNameAttributes() { String[] result = ADConfiguration.class.cast(conn.getConfiguration()).getUserAuthenticationAttributes(); if (result != null && result.length > 0) { return Arrays.asList(result); } - result = LdapConstants.getLdapUidAttributes(options); - if (result != null && result.length > 0) { - return Arrays.asList(result); - } - return conn.getSchemaMapping().getUserNameLdapAttributes(oclass); - } - - public Uid resolveUsername() { - ConnectorObject authnObject = getObjectToAuthenticate(); - if (authnObject == null) { - throw new InvalidCredentialException(conn.format( - "cannotResolveUsername", null, username)); - } - return authnObject.getUid(); + return super.getUserNameAttributes(); } } diff --git a/src/main/java/net/tirasa/connid/bundles/ad/crud/ADCreate.java b/src/main/java/net/tirasa/connid/bundles/ad/crud/ADCreate.java index 9d93067..e994336 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/crud/ADCreate.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/crud/ADCreate.java @@ -36,14 +36,14 @@ import net.tirasa.connid.bundles.ad.ADConnection; import net.tirasa.connid.bundles.ad.ADConnector; import net.tirasa.connid.bundles.ad.util.ADGuardedPasswordAttribute; -import net.tirasa.connid.bundles.ad.util.ADGuardedPasswordAttribute.Accessor; import net.tirasa.connid.bundles.ad.util.ADUtilities; import net.tirasa.connid.bundles.ldap.commons.LdapConstants; -import net.tirasa.connid.bundles.ldap.commons.LdapModifyOperation; +import net.tirasa.connid.bundles.ldap.modify.LdapCreate; +import net.tirasa.connid.bundles.ldap.schema.LdapSchema; +import net.tirasa.connid.bundles.ldap.schema.GuardedPasswordAttribute.Accessor; import org.identityconnectors.common.CollectionUtil; import org.identityconnectors.common.StringUtil; import org.identityconnectors.common.logging.Log; -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.AttributeUtil; @@ -53,38 +53,20 @@ import org.identityconnectors.framework.common.objects.OperationalAttributes; import org.identityconnectors.framework.common.objects.Uid; -public class ADCreate extends LdapModifyOperation { +public class ADCreate extends LdapCreate { private static final Log LOG = Log.getLog(ADConnection.class); - private final ObjectClass oclass; - - private final Set attrs; - - @SuppressWarnings("FieldNameHidesFieldInSuperclass") - private final ADConnection conn; - public ADCreate( final ADConnection conn, final ObjectClass oclass, final Set attrs, final OperationOptions options) { - super(conn); - - this.oclass = oclass; - this.attrs = attrs; - this.conn = conn; - } - - public Uid create() { - try { - return executeImpl(); - } catch (NamingException e) { - throw new ConnectorException(e); - } + super(conn, oclass, attrs, options); } - private Uid executeImpl() throws NamingException { + @Override + protected Uid executeImpl() throws NamingException { // ------------------------------------------------- // Retrieve DN @@ -100,7 +82,7 @@ private Uid executeImpl() throws NamingException { attrs.remove(cnAttr); } - final ADUtilities utils = new ADUtilities(conn); + final ADUtilities utils = new ADUtilities((ADConnection) conn); Name name; Uid uid = AttributeUtil.getUidAttribute(attrs); @@ -125,6 +107,8 @@ private Uid executeImpl() throws NamingException { idAttrName = conn.getConfiguration().getUidAttribute(); } else if (ObjectClass.GROUP.equals(oclass)) { idAttrName = conn.getConfiguration().getGidAttribute(); + } else if (LdapSchema.ANY_OBJECT_CLASS.equals(oclass)) { + idAttrName = conn.getConfiguration().getAoidAttribute(); } else { idAttrName = ADConfiguration.class.cast(conn.getConfiguration()).getDefaultIdAttribute(); } @@ -198,7 +182,7 @@ private Uid executeImpl() throws NamingException { } else if (attr.is(OBJECTGUID)) { // ignore info } else { - javax.naming.directory.Attribute ldapAttr = conn.getSchemaMapping().encodeAttribute(oclass, attr); + javax.naming.directory.Attribute ldapAttr = conn.getSchema().encodeAttribute(oclass, attr); // Do not send empty attributes. if (ldapAttr != null && ldapAttr.size() > 0) { @@ -234,7 +218,7 @@ private Uid executeImpl() throws NamingException { pwdAttr.access(new Accessor() { @Override - public void access(BasicAttribute attr) { + public void access(javax.naming.directory.Attribute attr) { try { if (attr.get() != null && !attr.get().toString().isEmpty()) { adAttrs.put(attr); @@ -255,7 +239,7 @@ public void access(BasicAttribute attr) { adAttrs.put(UACCONTROL_ATTR, Integer.toString(uacValue)); } - final String entryDN = conn.getSchemaMapping().create(oclass, name, adAttrs); + final String entryDN = conn.getSchema().create(oclass, name, adAttrs); if (uccp != null) { // --------------------------------- @@ -281,11 +265,11 @@ public void access(BasicAttribute attr) { // --------------------------------- } - if (OBJECTGUID.equals(conn.getSchemaMapping().getLdapUidAttribute(oclass))) { + if (OBJECTGUID.equals(conn.getSchema().getLdapUidAttribute(oclass))) { final Attributes profile = conn.getInitialContext().getAttributes(entryDN, new String[] { OBJECTGUID }); return new Uid(GUID.getGuidAsString((byte[]) profile.get(OBJECTGUID).get())); } else { - return conn.getSchemaMapping().createUid(oclass, entryDN); + return conn.getSchema().createUid(oclass, entryDN); } } } diff --git a/src/main/java/net/tirasa/connid/bundles/ad/crud/ADDelete.java b/src/main/java/net/tirasa/connid/bundles/ad/crud/ADDelete.java index 174f629..eb07bd1 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/crud/ADDelete.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/crud/ADDelete.java @@ -21,31 +21,22 @@ import java.util.TreeSet; import javax.naming.NamingException; import net.tirasa.connid.bundles.ad.ADConnection; -import net.tirasa.connid.bundles.ldap.commons.LdapModifyOperation; +import net.tirasa.connid.bundles.ldap.modify.LdapDelete; import net.tirasa.connid.bundles.ldap.search.LdapSearches; import org.identityconnectors.framework.common.exceptions.ConnectorException; import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.common.objects.Uid; -public class ADDelete extends LdapModifyOperation { - - private final ObjectClass oclass; - - private final Uid uid; - - @SuppressWarnings("FieldNameHidesFieldInSuperclass") - private final ADConnection conn; +public class ADDelete extends LdapDelete { public ADDelete(final ADConnection conn, final ObjectClass oclass, final Uid uid) { - super(conn); - this.oclass = oclass; - this.uid = uid; - this.conn = conn; + super(conn, oclass, uid); } - public void delete() { + @Override + public void execute() { final String entryDN; - if (OBJECTGUID.equals(conn.getSchemaMapping().getLdapUidAttribute(oclass))) { + if (OBJECTGUID.equals(conn.getSchema().getLdapUidAttribute(oclass))) { entryDN = String.format("", uid.getUidValue()); } else { entryDN = LdapSearches.getEntryDN(conn, oclass, uid); diff --git a/src/main/java/net/tirasa/connid/bundles/ad/crud/ADUpdate.java b/src/main/java/net/tirasa/connid/bundles/ad/crud/ADUpdate.java index a2f7922..e81fcc2 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/crud/ADUpdate.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/crud/ADUpdate.java @@ -18,14 +18,11 @@ import static net.tirasa.connid.bundles.ad.ADConnector.OBJECTGUID; import static net.tirasa.connid.bundles.ad.ADConnector.OBJECTSID; import static net.tirasa.connid.bundles.ad.ADConnector.PRIMARYGROUPID; -import static net.tirasa.connid.bundles.ldap.commons.LdapUtil.checkedListByFilter; import static net.tirasa.connid.bundles.ad.ADConnector.UACCONTROL_ATTR; import static net.tirasa.connid.bundles.ad.ADConnector.UF_ACCOUNTDISABLE; import static net.tirasa.connid.bundles.ad.util.ADUtilities.getPrimaryGroupSID; import static org.identityconnectors.common.CollectionUtil.isEmpty; import static org.identityconnectors.common.CollectionUtil.newSet; -import static org.identityconnectors.common.CollectionUtil.nullAsEmpty; - import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -48,13 +45,14 @@ import net.tirasa.connid.bundles.ad.ADConnection; import net.tirasa.connid.bundles.ad.ADConnector; import net.tirasa.connid.bundles.ad.util.ADGuardedPasswordAttribute; -import net.tirasa.connid.bundles.ad.util.ADGuardedPasswordAttribute.Accessor; import net.tirasa.connid.bundles.ad.util.ADUtilities; import net.tirasa.connid.bundles.ldap.commons.GroupHelper.GroupMembership; import net.tirasa.connid.bundles.ldap.commons.GroupHelper.Modification; +import net.tirasa.connid.bundles.ldap.modify.LdapUpdate; +import net.tirasa.connid.bundles.ldap.schema.GuardedPasswordAttribute; +import net.tirasa.connid.bundles.ldap.schema.GuardedPasswordAttribute.Accessor; import net.tirasa.connid.bundles.ldap.commons.LdapConstants; import net.tirasa.connid.bundles.ldap.commons.LdapEntry; -import net.tirasa.connid.bundles.ldap.commons.LdapModifyOperation; import org.identityconnectors.common.Pair; import org.identityconnectors.common.StringUtil; import org.identityconnectors.common.logging.Log; @@ -68,19 +66,12 @@ import org.identityconnectors.framework.common.objects.OperationalAttributes; import org.identityconnectors.framework.common.objects.Uid; -public class ADUpdate extends LdapModifyOperation { +public class ADUpdate extends LdapUpdate { private static final Log LOG = Log.getLog(ADUpdate.class); - private final ObjectClass oclass; - - private final Uid uid; - private final ADUtilities utils; - @SuppressWarnings("FieldNameHidesFieldInSuperclass") - private final ADConnection conn; - /** * Retrieve new name if specified. * @@ -103,12 +94,12 @@ private Name getNewName(final String entryDN, final Set attrs) { attrs.remove(name); if (ADUtilities.isDN(name.getNameValue())) { - newName = new Name(conn.getSchemaMapping().getEntryDN(oclass, name)); + newName = new Name(conn.getSchema().getEntryDN(oclass, name)); } } if (newName == null - && !conn.getSchemaMapping().getLdapUidAttribute(oclass).equalsIgnoreCase(ADConfiguration.CN_NAME) + && !conn.getSchema().getLdapUidAttribute(oclass).equalsIgnoreCase(ADConfiguration.CN_NAME) && cnAttr != null) { final String cnValue = cnAttr.getValue() == null || cnAttr.getValue().isEmpty() @@ -134,13 +125,11 @@ private Name getNewName(final String entryDN, final Set attrs) { } public ADUpdate(final ADConnection conn, final ObjectClass oclass, final Uid uid) { - super(conn); + super(conn, oclass, uid); this.utils = new ADUtilities(conn); - this.oclass = oclass; - this.uid = uid; - this.conn = conn; } + @Override public Uid update(final Set attrs) { final ConnectorObject obj = utils.getEntryToBeUpdated(uid, oclass); String entryDN = obj.getName().getNameValue(); @@ -157,7 +146,7 @@ public Uid update(final Set attrs) { LOG.error("Rename operation not permitted: '{0}' to '{1}'", entryDN, newName); throw new ConnectorException("Rename operation not permitted"); } else { - entryDN = conn.getSchemaMapping().rename(oclass, entryDN, newName); + entryDN = conn.getSchema().rename(oclass, entryDN, newName); } } @@ -167,7 +156,7 @@ public Uid update(final Set attrs) { // --------------------------------- // Perform modify/rename // --------------------------------- - final Pair attrToModify = getAttributesToModify(obj, + final Pair attrToModify = getAttributesToModify(obj, attrsToBeUpdated); // Update the attributes. modifyAttributes(entryDN, attrToModify, DirContext.REPLACE_ATTRIBUTE); @@ -177,14 +166,15 @@ public Uid update(final Set attrs) { modifyMemberships(entryDN, attrsToBeUpdated); modifyPrimaryGroupID(entryDN, attrsToBeUpdated); - return conn.getSchemaMapping().createUid(oclass, entryDN); + return conn.getSchema().createUid(oclass, entryDN); } + @Override public Uid addAttributeValues(final Set attrs) { final ConnectorObject obj = utils.getEntryToBeUpdated(uid, oclass); final String entryDN = obj.getName().getNameValue(); - final Pair attrsToModify = getAttributesToModify(obj, attrs); + final Pair attrsToModify = getAttributesToModify(obj, attrs); modifyAttributes(entryDN, attrsToModify, DirContext.ADD_ATTRIBUTE); modifyMemberships(entryDN, attrs); @@ -193,11 +183,12 @@ public Uid addAttributeValues(final Set attrs) { return uid; } + @Override public Uid removeAttributeValues(final Set attrs) { final ConnectorObject obj = utils.getEntryToBeUpdated(uid, oclass); final String entryDN = obj.getName().getNameValue(); - final Pair attrsToModify = getAttributesToModify(obj, attrs); + final Pair attrsToModify = getAttributesToModify(obj, attrs); modifyAttributes(entryDN, attrsToModify, DirContext.REMOVE_ATTRIBUTE); @@ -209,7 +200,7 @@ public Uid removeAttributeValues(final Set attrs) { return uid; } - private Pair getAttributesToModify( + protected Pair getAttributesToModify( final ConnectorObject obj, final Set attrs) { final BasicAttributes ldapAttrs = new BasicAttributes(); @@ -286,7 +277,7 @@ private Pair getAttributesToModify( } else if (attr.is(OBJECTGUID)) { // ignore info } else { - ldapAttr = conn.getSchemaMapping().encodeAttribute(oclass, attr); + ldapAttr = conn.getSchema().encodeAttribute(oclass, attr); } addAttribute(ldapAttr, ldapAttrs); @@ -312,12 +303,12 @@ private Pair getAttributesToModify( } if (newUACValue >= 0) { - addAttribute(conn.getSchemaMapping().encodeAttribute( + addAttribute(conn.getSchema().encodeAttribute( oclass, AttributeBuilder.build(UACCONTROL_ATTR, Integer.toString(newUACValue))), ldapAttrs); } } - return new Pair(ldapAttrs, pwdAttr); + return new Pair(ldapAttrs, pwdAttr); } private void addAttribute(final javax.naming.directory.Attribute ldapAttr, final BasicAttributes ldapAttrs) { @@ -339,10 +330,11 @@ private void addAttribute(final javax.naming.directory.Attribute ldapAttr, final } } - private void modifyAttributes( + @Override + protected void modifyAttributes( final String entryDN, - final Pair attrs, - final int modifyOp) { + final Pair attrs, + final int ldapModifyOp) { final List modItems = new ArrayList(attrs.first.size()); @@ -352,7 +344,7 @@ private void modifyAttributes( final javax.naming.directory.Attribute attr = attrEnum.nextElement(); if (!attr.getID().equalsIgnoreCase(LdapConstants.LDAP_GROUPS_NAME) && !attr.getID().equalsIgnoreCase(ADConfiguration.PRIMARY_GROUP_DN_NAME)) { - modItems.add(new ModificationItem(modifyOp, attr)); + modItems.add(new ModificationItem(ldapModifyOp, attr)); } } @@ -360,10 +352,10 @@ private void modifyAttributes( attrs.second.access(new Accessor() { @Override - public void access(BasicAttribute attr) { + public void access(javax.naming.directory.Attribute attr) { try { if (attr.get() != null) { - modItems.add(new ModificationItem(modifyOp, attr)); + modItems.add(new ModificationItem(ldapModifyOp, attr)); modifyAttributes(entryDN, modItems); } } catch (NamingException e) { @@ -376,7 +368,8 @@ public void access(BasicAttribute attr) { modifyAttributes(entryDN, modItems); } - private void modifyAttributes(final String entryDN, final List modItems) { + @Override + protected void modifyAttributes(final String entryDN, final List modItems) { try { conn.getInitialContext().modifyAttributes(entryDN, modItems.toArray(new ModificationItem[modItems.size()])); } catch (NamingException e) { @@ -384,16 +377,6 @@ private void modifyAttributes(final String entryDN, final List } } - private List getStringListValue(final Set attrs, final String attrName) { - final Attribute attr = AttributeUtil.find(attrName, attrs); - - if (attr != null && attr.getValue() != null) { - return checkedListByFilter(nullAsEmpty(attr.getValue()), String.class); - } - - return null; - } - /** * Modify primaryGrupID. * @@ -430,7 +413,7 @@ private void modifyMemberships( if (ldapGroups != null) { // All current roles .... final Set currents = utils.getGroups(entryDN, - ((ADConfiguration) conn.getConfiguration()).getBaseContextsToSynchronize()); + ((ADConfiguration) conn.getConfiguration()).getBaseContexts()); // Current role into the managed group base contexts final Set oldMemberships = utils.getGroups(entryDN); @@ -451,7 +434,7 @@ private void modifyMemberships( final Set res = utils.basicLdapSearch(String.format( "(&(objectclass=group)(%s=%s))", OBJECTSID, Hex.getEscaped(groupSID.toByteArray())), - ((ADConfiguration) conn.getConfiguration()).getBaseContextsToSynchronize()); + ((ADConfiguration) conn.getConfiguration()).getBaseContexts()); if (res == null || res.isEmpty()) { LOG.warn("Error retrieving primary group for {0}", entryDN); diff --git a/src/main/java/net/tirasa/connid/bundles/ad/schema/ADSchema.java b/src/main/java/net/tirasa/connid/bundles/ad/schema/ADSchema.java index 647ff22..eb52e9e 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/schema/ADSchema.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/schema/ADSchema.java @@ -16,23 +16,50 @@ package net.tirasa.connid.bundles.ad.schema; import net.tirasa.connid.bundles.ad.ADConnection; +import net.tirasa.connid.bundles.ldap.schema.LdapSchema; +import org.identityconnectors.common.logging.Log; +import org.identityconnectors.framework.common.objects.AttributeUtil; +import org.identityconnectors.framework.common.objects.Name; +import org.identityconnectors.framework.common.objects.ObjectClass; +import org.identityconnectors.framework.common.objects.OperationalAttributes; import org.identityconnectors.framework.common.objects.Schema; +import org.identityconnectors.framework.common.objects.Uid; -public class ADSchema { - - private final ADConnection connection; - - private Schema schema; +public class ADSchema extends LdapSchema { + private static final Log LOG = Log.getLog(ADSchema.class); public ADSchema(final ADConnection connection) { - this.connection = connection; + super(connection); } - public Schema getSchema() { + public Schema schema() { if (schema == null) { - schema = new ADSchemaBuilder(connection).getSchema(); + schema = new ADSchemaBuilder((ADConnection) conn).getSchema(); } return schema; } + + @Override + public String getLdapAttribute(ObjectClass oclass, String attrName, boolean transfer) { + String result = null; + if (AttributeUtil.namesEqual(Uid.NAME, attrName)) { + result = getLdapUidAttribute(oclass); + } else if (AttributeUtil.namesEqual(Name.NAME, attrName)) { + result = getLdapNameAttribute(oclass); + } else if (OperationalAttributes.PASSWORD_NAME.equals(attrName)) { + result = getLdapPasswordAttribute(oclass); + } + + if (result == null && !AttributeUtil.isSpecialName(attrName)) { + result = attrName; + } + + if (result == null && !oclass.equals(ANY_OBJECT_CLASS)) { + LOG.warn( + "Attribute {0} of object class {1} is not mapped to an LDAP attribute", + attrName, oclass.getObjectClassValue()); + } + return result; + } } diff --git a/src/main/java/net/tirasa/connid/bundles/ad/schema/ADSchemaBuilder.java b/src/main/java/net/tirasa/connid/bundles/ad/schema/ADSchemaBuilder.java index 49d41fb..b065375 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/schema/ADSchemaBuilder.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/schema/ADSchemaBuilder.java @@ -32,6 +32,8 @@ import net.tirasa.connid.bundles.ad.ADConfiguration; import net.tirasa.connid.bundles.ad.ADConnection; import net.tirasa.connid.bundles.ad.ADConnector; +import net.tirasa.connid.bundles.ldap.schema.LdapSchema; +import net.tirasa.connid.bundles.ldap.schema.LdapSchemaBuilder; import net.tirasa.connid.bundles.ldap.search.LdapInternalSearch; import org.identityconnectors.common.StringUtil; import org.identityconnectors.common.logging.Log; @@ -41,44 +43,34 @@ import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.common.objects.ObjectClassInfo; import org.identityconnectors.framework.common.objects.ObjectClassInfoBuilder; -import org.identityconnectors.framework.common.objects.Schema; import org.identityconnectors.framework.common.objects.SchemaBuilder; -class ADSchemaBuilder { +class ADSchemaBuilder extends LdapSchemaBuilder { private static final Log LOG = Log.getLog(ADSchemaBuilder.class); - private final ADConnection connection; - - private Schema schema; - private static final String[] ATTRIBUTES_TO_GET = { "maycontain", "systemmaycontain", "mustcontain", "systemmustcontain" }; - public ADSchemaBuilder(final ADConnection connection) { - this.connection = connection; - } - - public Schema getSchema() { - if (schema == null) { - buildSchema(); - } - return schema; + public ADSchemaBuilder(final ADConnection conn) { + super(conn); } - private void buildSchema() { + @Override + protected void buildSchema() { final SchemaBuilder schemaBld = new SchemaBuilder(ADConnector.class); build(ObjectClass.ACCOUNT_NAME, schemaBld); build(ObjectClass.GROUP_NAME, schemaBld); build(ObjectClass.ALL_NAME, schemaBld); + build(LdapSchema.ANY_OBJECT_NAME, schemaBld); schema = schemaBld.build(); } private void build(final String oname, final SchemaBuilder schemaBld) { - final ADConfiguration conf = (ADConfiguration) connection.getConfiguration(); + final ADConfiguration conf = (ADConfiguration) conn.getConfiguration(); final StringBuilder filter = new StringBuilder(); @@ -99,6 +91,8 @@ private void build(final String oname, final SchemaBuilder schemaBld) { ? conf.getAccountObjectClasses() : oname.equalsIgnoreCase(ObjectClass.GROUP_NAME) ? conf.getGroupObjectClasses() + : oname.equalsIgnoreCase(LdapSchema.ANY_OBJECT_NAME) + ? conf.getAnyObjectClasses() : new String[] { oname }) { filter.append("(lDAPDisplayName=").append(oclass).append(")"); } @@ -106,7 +100,7 @@ private void build(final String oname, final SchemaBuilder schemaBld) { filter.insert(0, "(&(|").append(")(objectClass=classSchema))"); // ----------------------------------- - final LdapContext ctx = connection.getInitialContext(); + final LdapContext ctx = conn.getInitialContext(); final Set schemaNames = new HashSet(); @@ -115,13 +109,15 @@ private void build(final String oname, final SchemaBuilder schemaBld) { schemaNames.add(conf.getUidAttribute()); } else if (oname.equalsIgnoreCase(ObjectClass.GROUP_NAME)) { schemaNames.add(conf.getGidAttribute()); + } else if (oname.equalsIgnoreCase(LdapSchema.ANY_OBJECT_NAME)) { + schemaNames.add(conf.getAoidAttribute()); } else { schemaNames.add(OBJECTGUID); } final String schemaConfigurationPath = "CN=Schema,CN=Configuration"; - for (String suffix : conf.getBaseContextsToSynchronize()) { + for (String suffix : conf.getBaseContexts()) { try { final NamingEnumeration oclasses = ctx.search( schemaConfigurationPath + "," + suffix, @@ -200,13 +196,13 @@ private AttributeInfo handleAttribute(final String displayName) { final Set flags = EnumSet.noneOf(Flags.class); - boolean binary = connection.isBinarySyntax(displayName); + boolean binary = conn.isBinarySyntax(displayName); boolean objectClass = displayName == null || "objectClass".equalsIgnoreCase(displayName); - final LdapContext ctx = connection.getInitialContext(); + final LdapContext ctx = conn.getInitialContext(); - final String[] baseContexts = connection.getConfiguration().getBaseContextsToSynchronize(); + final String[] baseContexts = conn.getConfiguration().getBaseContexts(); // ------------------------------ // Search control diff --git a/src/main/java/net/tirasa/connid/bundles/ad/search/ADDefaultSearchStrategy.java b/src/main/java/net/tirasa/connid/bundles/ad/search/ADDefaultSearchStrategy.java index 4c8b3ee..5acc7f8 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/search/ADDefaultSearchStrategy.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/search/ADDefaultSearchStrategy.java @@ -16,7 +16,6 @@ package net.tirasa.connid.bundles.ad.search; import java.io.IOException; -import java.util.Arrays; import java.util.Iterator; import java.util.List; import javax.naming.InvalidNameException; @@ -38,32 +37,8 @@ public class ADDefaultSearchStrategy extends DefaultSearchStrategy { private static final Log LOG = Log.getLog(ADDefaultSearchStrategy.class); - private final boolean ignoreNonExistingBaseDNs; - - static String searchControlsToString(SearchControls controls) { - StringBuilder builder = new StringBuilder(); - builder.append("SearchControls: {returningAttributes="); - String[] attrs = controls.getReturningAttributes(); - builder.append(attrs != null ? Arrays.asList(attrs) : "null"); - builder.append(", scope="); - switch (controls.getSearchScope()) { - case SearchControls.OBJECT_SCOPE: - builder.append("OBJECT"); - break; - case SearchControls.ONELEVEL_SCOPE: - builder.append("ONELEVEL"); - break; - case SearchControls.SUBTREE_SCOPE: - builder.append("SUBTREE"); - break; - } - builder.append('}'); - return builder.toString(); - } - public ADDefaultSearchStrategy(boolean ignoreNonExistingBaseDNs) { super(ignoreNonExistingBaseDNs); - this.ignoreNonExistingBaseDNs = ignoreNonExistingBaseDNs; } @Override diff --git a/src/main/java/net/tirasa/connid/bundles/ad/search/ADPagedSearchStrategy.java b/src/main/java/net/tirasa/connid/bundles/ad/search/ADPagedSearchStrategy.java index 93da2e2..75d0a01 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/search/ADPagedSearchStrategy.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/search/ADPagedSearchStrategy.java @@ -15,26 +15,8 @@ */ package net.tirasa.connid.bundles.ad.search; -import java.io.IOException; -import java.util.Arrays; -import java.util.Base64; -import java.util.List; -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.OperationNotSupportedException; -import javax.naming.PartialResultException; -import javax.naming.directory.SearchControls; -import javax.naming.directory.SearchResult; -import javax.naming.ldap.Control; -import javax.naming.ldap.LdapContext; -import javax.naming.ldap.PagedResultsControl; -import javax.naming.ldap.PagedResultsResponseControl; -import javax.naming.ldap.SortControl; -import net.tirasa.connid.bundles.ldap.search.LdapSearchResultsHandler; import net.tirasa.connid.bundles.ldap.search.PagedSearchStrategy; -import org.identityconnectors.common.StringUtil; import org.identityconnectors.common.logging.Log; -import org.identityconnectors.framework.common.exceptions.ConnectorException; import org.identityconnectors.framework.common.objects.SortKey; import org.identityconnectors.framework.spi.SearchResultsHandler; @@ -42,37 +24,6 @@ public class ADPagedSearchStrategy extends PagedSearchStrategy { private static final Log LOG = Log.getLog(ADPagedSearchStrategy.class); - private final int pageSize; - - private final int pagedResultsOffset; - - private final String pagedResultsCookie; - - private final SearchResultsHandler searchResultHandler; - - private final SortKey[] sortKeys; - - static String searchControlsToString(SearchControls controls) { - StringBuilder builder = new StringBuilder(); - builder.append("SearchControls: {returningAttributes="); - String[] attrs = controls.getReturningAttributes(); - builder.append(attrs != null ? Arrays.asList(attrs) : "null"); - builder.append(", scope="); - switch (controls.getSearchScope()) { - case SearchControls.OBJECT_SCOPE: - builder.append("OBJECT"); - break; - case SearchControls.ONELEVEL_SCOPE: - builder.append("ONELEVEL"); - break; - case SearchControls.SUBTREE_SCOPE: - builder.append("SUBTREE"); - break; - } - builder.append('}'); - return builder.toString(); - } - public ADPagedSearchStrategy( final int pageSize, final String pagedResultsCookie, @@ -80,130 +31,5 @@ public ADPagedSearchStrategy( final SearchResultsHandler searchResultHandler, final SortKey[] sortKeys) { super(pageSize, pagedResultsCookie, pagedResultsOffset, searchResultHandler, sortKeys); - this.pageSize = pageSize; - this.pagedResultsOffset = pagedResultsOffset == null ? 0 : pagedResultsOffset; - this.pagedResultsCookie = pagedResultsCookie; - this.searchResultHandler = searchResultHandler; - this.sortKeys = sortKeys; - } - - @Override - public void doSearch( - final LdapContext initCtx, - final List baseDNs, - final String query, - final SearchControls searchControls, - final LdapSearchResultsHandler handler) - throws IOException, NamingException { - - LOG.ok("Searching in {0} with filter {1} and {2}", baseDNs, query, searchControlsToString(searchControls)); - - byte[] cookie = null; - int context = 0; - if (StringUtil.isNotBlank(pagedResultsCookie)) { - // we need to determine which base context we're dealing with... - // The cookie value is : - String[] split = pagedResultsCookie.split(":", 2); - // bit of sanity check... - if (split.length == 2) { - try { - cookie = Base64.getDecoder().decode(split[0]); - } catch (RuntimeException e) { - throw new ConnectorException("PagedResultsCookie is not properly encoded", e); - } - context = Integer.valueOf(split[1]); - } else { - throw new ConnectorException("PagedResultsCookie is not properly formatted"); - } - } - - LdapContext ctx = initCtx.newInstance(null); - int remainingResults = -1; - boolean allResultsReturned = true; - try { - boolean proceed = true; - int records = 0; - boolean needMore; - do { - SortControl sortControl = null; - if (sortKeys != null && sortKeys.length > 0) { - javax.naming.ldap.SortKey[] skis = new javax.naming.ldap.SortKey[sortKeys.length]; - for (int i = 0; i < sortKeys.length; i++) { - skis[i] = new javax.naming.ldap.SortKey(sortKeys[i].getField(), sortKeys[i].isAscendingOrder(), - null); - } - // We don't want to make this critical... better return unsorted results than nothing. - sortControl = new SortControl(skis, Control.NONCRITICAL); - } - if (sortControl == null) { - ctx.setRequestControls(new Control[] { - new PagedResultsControl(pageSize - records, cookie, Control.CRITICAL) }); - } else { - ctx.setRequestControls(new Control[] { - new PagedResultsControl(pageSize - records, cookie, Control.CRITICAL), sortControl - }); - } - - NamingEnumeration results = ctx.search(baseDNs.get(context), query, searchControls); - - try { - // hasMore call for referral resolution ... it fails with AD - while (proceed && results.hasMoreElements()) { - SearchResult result = results.next(); - records++; - if (records > pagedResultsOffset) { - proceed = handler.handle(baseDNs.get(context), result); - } - } - if ((records < pageSize) && (context + 1 < baseDNs.size())) { - needMore = true; - - context++; - cookie = null; - } else { - needMore = false; - - PagedResultsResponseControl pagedControl = getPagedControl(ctx.getResponseControls()); - if (pagedControl != null) { - cookie = pagedControl.getCookie(); - if (pagedControl.getResultSize() > 0) { - remainingResults = pagedControl.getResultSize(); - } - } - } - } finally { - results.close(); - } - } while (needMore); - } catch (OperationNotSupportedException e) { - LOG.ok("OperationNotSupportedException caught: {0}. Check the Cookie validity", e.getRemainingName()); - throw new ConnectorException("Operation Not Supported. Bad cookie"); - } catch (PartialResultException e) { - LOG.ok("PartialResultException caught: {0}", e.getRemainingName()); - allResultsReturned = false; - } finally { - ctx.close(); - } - - String returnedCookie = null; - if (cookie != null) { - returnedCookie = Base64.getEncoder().encodeToString(cookie).concat(":" + context); - } - - if (searchResultHandler != null) { - searchResultHandler.handleResult(new org.identityconnectors.framework.common.objects.SearchResult( - returnedCookie, remainingResults, allResultsReturned)); - } - } - - private PagedResultsResponseControl getPagedControl(final Control[] controls) { - if (controls != null) { - for (Control control : controls) { - if (control instanceof PagedResultsResponseControl) { - return (PagedResultsResponseControl) control; - } - } - } - return null; } } diff --git a/src/main/java/net/tirasa/connid/bundles/ad/search/ADSearch.java b/src/main/java/net/tirasa/connid/bundles/ad/search/ADSearch.java index a05a71c..8ae73bf 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/search/ADSearch.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/search/ADSearch.java @@ -15,11 +15,7 @@ */ package net.tirasa.connid.bundles.ad.search; -import static java.util.Collections.singletonList; -import static org.identityconnectors.common.StringUtil.isBlank; - import com.sun.jndi.ldap.ctl.VirtualListViewControl; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; @@ -38,31 +34,19 @@ import net.tirasa.connid.bundles.ad.util.ADUtilities; import net.tirasa.connid.bundles.ldap.LdapConnection; import net.tirasa.connid.bundles.ldap.commons.LdapConstants; +import net.tirasa.connid.bundles.ldap.schema.LdapSchema; 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.LdapSearchResultsHandler; import net.tirasa.connid.bundles.ldap.search.LdapSearchStrategy; -import net.tirasa.connid.bundles.ldap.search.LdapSearches; import org.identityconnectors.common.logging.Log; import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.common.objects.OperationOptions; -import org.identityconnectors.framework.common.objects.QualifiedUid; import org.identityconnectors.framework.common.objects.ResultsHandler; import org.identityconnectors.framework.spi.SearchResultsHandler; -public class ADSearch { - - private final LdapConnection conn; - - private final ResultsHandler handler; - - private final ObjectClass oclass; - - private final LdapFilter filter; - - private final OperationOptions options; - - private final String[] baseDNs; +public class ADSearch extends LdapSearch { private final ADUtilities utils; @@ -75,13 +59,7 @@ public ADSearch( final ResultsHandler handler, final OperationOptions options, final String[] baseDNs) { - - this.conn = conn; - this.oclass = oclass; - this.filter = filter; - this.handler = handler; - this.options = options; - this.baseDNs = baseDNs; + super(conn, oclass, filter, handler, options, baseDNs); this.utils = new ADUtilities((ADConnection) this.conn); } @@ -98,10 +76,13 @@ public ADSearch( ? ((ADConfiguration) conn.getConfiguration()).getUserBaseContexts() : oclass.is(ObjectClass.GROUP_NAME) ? ((ADConfiguration) conn.getConfiguration()).getGroupBaseContexts() - : ((ADConfiguration) conn.getConfiguration()).getBaseContextsToSynchronize()); + : oclass.is(LdapSchema.ANY_OBJECT_NAME) + ? ((ADConfiguration) conn.getConfiguration()).getAnyObjectBaseContexts() + : ((ADConfiguration) conn.getConfiguration()).getBaseContexts()); } - public final void executeADQuery(final ResultsHandler handler) { + @Override + public final void execute() { final String[] attrsToGetOption = options.getAttributesToGet(); final Set attrsToGet = utils.getAttributesToGet(attrsToGetOption, oclass); @@ -121,7 +102,8 @@ public boolean handle(final String baseDN, final SearchResult result) }); } - private LdapInternalSearch getInternalSearch(final Set attrsToGet) { + @Override + protected LdapInternalSearch getInternalSearch(final Set attrsToGet) { // This is a bit tricky. If the LdapFilter has an entry DN, // we only need to look at that entry and check whether it matches // the native filter. Moreover, when looking at the entry DN @@ -136,13 +118,17 @@ private LdapInternalSearch getInternalSearch(final Set attrsToGet) { LdapSearchStrategy strategy; List dns; int searchScope; + boolean ignoreUserAnyObjectConfig = false; final String filterEntryDN = filter == null ? null : filter.getEntryDN(); if (filterEntryDN == null) { strategy = getSearchStrategy(); dns = getBaseDNs(); - searchScope = getLdapSearchScope(); + if (options.getOptions().containsKey(OP_IGNORE_CUSTOM_ANY_OBJECT_CONFIG)) { + ignoreUserAnyObjectConfig = (boolean) options.getOptions().get(OP_IGNORE_CUSTOM_ANY_OBJECT_CONFIG); + } + searchScope = getLdapSearchScope(ignoreUserAnyObjectConfig); } else { // Would be good to check that filterEntryDN is under the configured // base contexts. However, the adapter is likely to pass entries @@ -174,7 +160,11 @@ private LdapInternalSearch getInternalSearch(final Set attrsToGet) { final String searchFilter = oclass.equals(ObjectClass.ACCOUNT) ? conn.getConfiguration().getAccountSearchFilter() - : ((ADConfiguration) conn.getConfiguration()).getGroupSearchFilter(); + : oclass.equals(ObjectClass.GROUP) + ? conn.getConfiguration().getGroupSearchFilter() + : (!ignoreUserAnyObjectConfig) + ? conn.getConfiguration().getAnyObjectSearchFilter() + : null; if (LOG.isOk()) { LOG.ok("Search filter: {0} " + searchFilter); @@ -221,37 +211,20 @@ private List buildBaseContextFilter(final String filterEntryDN) throws I } catch (InvalidNameException ine) { LOG.info(ine, "'{0}' is not am entry DN. Let's try derive it", filterEntryDN); final LdapName prefix = new LdapName(String.format("CN=%s", filterEntryDN)); - return getBaseDNs().stream(). - map(bdn -> { - try { - return new LdapName(bdn).addAll(prefix).toString(); - } catch (InvalidNameException e) { - return bdn; - } - }).collect(Collectors.toList()); + return getBaseDNs().stream().map(bdn -> { + try { + return new LdapName(bdn).addAll(prefix).toString(); + } catch (InvalidNameException e) { + return bdn; + } + }).collect(Collectors.toList()); } } - private String getSearchFilter(final String... optionalFilters) { - final StringBuilder builder = new StringBuilder(); - final String ocFilter = getObjectClassFilter(); - int nonBlank = isBlank(ocFilter) ? 0 : 1; - for (String optionalFilter : optionalFilters) { - nonBlank += (isBlank(optionalFilter) ? 0 : 1); - } - if (nonBlank > 1) { - builder.append("(&"); - } - appendFilter(ocFilter, builder); - for (String optionalFilter : optionalFilters) { - appendFilter(optionalFilter, builder); - } - if (nonBlank > 1) { - builder.append(')'); - } - + @Override + protected String getSearchFilter(final String... optionalFilters) { // replace any substring like as objectGUID=ba36c308-792a-45a9-b374-7f330e9742ab with the correct query - final String res = builder.toString(); + final String res = super.getSearchFilter(optionalFilters); final String resToLowerCase = res.toLowerCase(); // required to be case-insensitive final String toBeFound = ADConnector.OBJECTGUID.toLowerCase(); @@ -276,7 +249,8 @@ private String getSearchFilter(final String... optionalFilters) { return bld.toString(); } - private LdapSearchStrategy getSearchStrategy() { + @Override + protected LdapSearchStrategy getSearchStrategy() { final LdapSearchStrategy result; if (options.getPageSize() != null) { @@ -299,76 +273,4 @@ private LdapSearchStrategy getSearchStrategy() { } return result; } - - private static void appendFilter(String filter, StringBuilder toBuilder) { - if (!isBlank(filter)) { - final String trimmedUserFilter = filter.trim(); - final boolean enclose = filter.charAt(0) != '('; - if (enclose) { - toBuilder.append('('); - } - toBuilder.append(trimmedUserFilter); - if (enclose) { - toBuilder.append(')'); - } - } - } - - private List getBaseDNs() { - List result; - QualifiedUid container = options.getContainer(); - if (container != null) { - result = singletonList(LdapSearches.findEntryDN( - conn, container.getObjectClass(), container.getUid())); - } else { - result = Arrays.asList(baseDNs); - } - assert result != null; - return result; - } - - private String getObjectClassFilter() { - StringBuilder builder = new StringBuilder(); - List ldapClasses = conn.getSchemaMapping().getLdapClasses(oclass); - boolean and = ldapClasses.size() > 1; - if (and) { - builder.append("(&"); - } - for (String ldapClass : ldapClasses) { - builder.append("(objectClass="); - builder.append(ldapClass); - builder.append(')'); - } - if (and) { - builder.append(')'); - } - return builder.toString(); - } - - private int getLdapSearchScope() { - String scope = options.getScope(); - - if (scope == null) { - if (oclass.is(ObjectClass.ACCOUNT_NAME)) { - scope = ((ADConfiguration) conn.getConfiguration()).getUserSearchScope(); - } else { - scope = ((ADConfiguration) conn.getConfiguration()).getGroupSearchScope(); - } - } - - if (null == scope) { - return SearchControls.SUBTREE_SCOPE; - } else { - switch (scope) { - case OperationOptions.SCOPE_OBJECT: - return SearchControls.OBJECT_SCOPE; - case OperationOptions.SCOPE_ONE_LEVEL: - return SearchControls.ONELEVEL_SCOPE; - case OperationOptions.SCOPE_SUBTREE: - return SearchControls.SUBTREE_SCOPE; - default: - throw new IllegalArgumentException("Invalid search scope " + scope); - } - } - } } diff --git a/src/main/java/net/tirasa/connid/bundles/ad/search/ADVlvIndexSearchStrategy.java b/src/main/java/net/tirasa/connid/bundles/ad/search/ADVlvIndexSearchStrategy.java index c7d4d82..bce9b70 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/search/ADVlvIndexSearchStrategy.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/search/ADVlvIndexSearchStrategy.java @@ -15,24 +15,6 @@ */ package net.tirasa.connid.bundles.ad.search; -import static org.identityconnectors.common.StringUtil.isNotBlank; - -import com.sun.jndi.ldap.ctl.VirtualListViewControl; -import com.sun.jndi.ldap.ctl.VirtualListViewResponseControl; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.SearchControls; -import javax.naming.directory.SearchResult; -import javax.naming.ldap.Control; -import javax.naming.ldap.LdapContext; -import javax.naming.ldap.SortControl; -import javax.naming.ldap.SortResponseControl; -import net.tirasa.connid.bundles.ldap.search.LdapSearchResultsHandler; import net.tirasa.connid.bundles.ldap.search.VlvIndexSearchStrategy; import org.identityconnectors.common.logging.Log; @@ -40,193 +22,7 @@ public class ADVlvIndexSearchStrategy extends VlvIndexSearchStrategy { private static final Log LOG = Log.getLog(ADVlvIndexSearchStrategy.class); - private final String vlvIndexAttr; - - private final int blockSize; - - private int index; - - private int lastListSize; - - private byte[] cookie; - - static String searchControlsToString(SearchControls controls) { - StringBuilder builder = new StringBuilder(); - builder.append("SearchControls: {returningAttributes="); - String[] attrs = controls.getReturningAttributes(); - builder.append(attrs != null ? Arrays.asList(attrs) : "null"); - builder.append(", scope="); - switch (controls.getSearchScope()) { - case SearchControls.OBJECT_SCOPE: - builder.append("OBJECT"); - break; - case SearchControls.ONELEVEL_SCOPE: - builder.append("ONELEVEL"); - break; - case SearchControls.SUBTREE_SCOPE: - builder.append("SUBTREE"); - break; - } - builder.append('}'); - return builder.toString(); - } - - public ADVlvIndexSearchStrategy(String vlvSortAttr, int blockSize) { - super(vlvSortAttr, blockSize); - this.vlvIndexAttr = isNotBlank(vlvSortAttr) ? vlvSortAttr : "uid"; - this.blockSize = blockSize; - } - - @Override - public void doSearch(final LdapContext initCtx, - final List baseDNs, - final String query, - final SearchControls searchControls, - final LdapSearchResultsHandler handler) - throws IOException, NamingException { - - if (LOG.isOk()) { - LOG.ok("Searching in {0} with filter {1} and {2}", - baseDNs, query, searchControlsToString(searchControls)); - } - - Iterator baseDNIter = baseDNs.iterator(); - boolean proceed = true; - - LdapContext ctx = initCtx.newInstance(null); - try { - while (baseDNIter.hasNext() && proceed) { - proceed = searchBaseDN(ctx, baseDNIter.next(), query, searchControls, handler); - } - } finally { - ctx.close(); - } - } - - private boolean searchBaseDN( - final LdapContext ctx, - final String baseDN, - final String query, - final SearchControls searchControls, - final LdapSearchResultsHandler handler) - throws IOException, NamingException { - - if (LOG.isOk()) { - LOG.ok("Searching in {0}", baseDN); - } - - index = 1; - lastListSize = 0; - cookie = null; - - String lastResultName = null; - - for (;;) { - SortControl sortControl = new SortControl(vlvIndexAttr, Control.CRITICAL); - - int afterCount = blockSize - 1; - VirtualListViewControl vlvControl = new VirtualListViewControl( - index, lastListSize, 0, afterCount, Control.CRITICAL); - vlvControl.setContextID(cookie); - - if (LOG.isOk()) { - LOG.ok("New search: target = {0}, afterCount = {1}", - index, afterCount); - } - - ctx.setRequestControls(new Control[] { sortControl, vlvControl }); - - // Need to process the response controls, which are available after - // all results have been processed, before sending anything to the caller - // (because processing the response controls might throw exceptions that - // invalidate anything we might have sent otherwise). - // So storing the results before actually sending them to the handler. - List resultList = new ArrayList(blockSize); - - NamingEnumeration results = ctx.search(baseDN, query, searchControls); - try { - // hasMore call for referral resolution ... it fails with AD - // while (results.hasMore()) { - while (results.hasMoreElements()) { - SearchResult result = results.next(); - - boolean overlap = false; - if (lastResultName != null) { - if (lastResultName.equals(result.getName())) { - LOG.warn( - "Working around rounding error overlap at " - + "index " + index); - overlap = true; - } - lastResultName = null; - } - - if (!overlap) { - resultList.add(result); - } - } - } finally { - results.close(); - } - - processResponseControls(ctx.getResponseControls()); - - SearchResult result = null; - Iterator resultIter = resultList.iterator(); - while (resultIter.hasNext()) { - result = resultIter.next(); - index++; - if (!handler.handle(baseDN, result)) { - return false; - } - } - if (result != null) { - lastResultName = result.getName(); - } - - if (index > lastListSize) { - break; - } - - // DSEE seems to only have a single VLV index (although it claims to support more). - // It returns at the server content count the sum of sizes of all indexes, - // but it only returns the entries in the base context we are asking for. - // So, in this case, index will never reach lastListSize. To avoid an infinite loop, - // ending search if we received no results in the last iteration. - if (resultList.isEmpty()) { - LOG.warn("Ending search because received no results"); - break; - } - } - return true; - } - - private void processResponseControls(final Control[] controls) - throws NamingException { - if (controls != null) { - for (Control control : controls) { - if (control instanceof SortResponseControl) { - SortResponseControl sortControl = (SortResponseControl) control; - if (!sortControl.isSorted() - || (sortControl.getResultCode() != 0)) { - throw sortControl.getException(); - } - } - if (control instanceof VirtualListViewResponseControl) { - VirtualListViewResponseControl vlvControl = (VirtualListViewResponseControl) control; - if (vlvControl.getResultCode() == 0) { - lastListSize = vlvControl.getListSize(); - cookie = vlvControl.getContextID(); - - if (LOG.isOk()) { - LOG.ok("Response control: lastListSize = {0}", - lastListSize); - } - } else { - throw vlvControl.getException(); - } - } - } - } + public ADVlvIndexSearchStrategy(String vlvSortAttr, int pageSize) { + super(vlvSortAttr, pageSize); } } diff --git a/src/main/java/net/tirasa/connid/bundles/ad/sync/ADSyncStrategy.java b/src/main/java/net/tirasa/connid/bundles/ad/sync/ADSyncStrategy.java index 9ab9e88..33dbc83 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/sync/ADSyncStrategy.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/sync/ADSyncStrategy.java @@ -39,7 +39,10 @@ import net.tirasa.connid.bundles.ad.util.ADUtilities; import net.tirasa.connid.bundles.ad.util.DeletedControl; import net.tirasa.connid.bundles.ad.util.DirSyncUtils; +import net.tirasa.connid.bundles.ldap.schema.LdapSchema; import net.tirasa.connid.bundles.ldap.search.LdapInternalSearch; +import net.tirasa.connid.bundles.ldap.sync.LdapSyncStrategy; + import org.identityconnectors.common.StringUtil; import org.identityconnectors.common.logging.Log; import org.identityconnectors.framework.common.exceptions.ConnectorException; @@ -56,7 +59,7 @@ /** * An implementation of the sync operation based on the DirSync protocol, for Active Directory. */ -public class ADSyncStrategy { +public class ADSyncStrategy implements LdapSyncStrategy { private static final Log LOG = Log.getLog(ADSyncStrategy.class); @@ -176,8 +179,9 @@ public void sync( final String filter = oclass.is(ObjectClass.ACCOUNT_NAME) ? // get user filter DirSyncUtils.createDirSyncUFilter((ADConfiguration) conn.getConfiguration(), utils) - : // get group filter - DirSyncUtils.createDirSyncGFilter((ADConfiguration) conn.getConfiguration()); + : oclass.is(ObjectClass.GROUP_NAME) // get group filter + ? DirSyncUtils.createDirSyncGFilter((ADConfiguration) conn.getConfiguration()) + : DirSyncUtils.createDirSyncAOFilter((ADConfiguration) conn.getConfiguration()); if (LOG.isOk()) { LOG.ok("Search filter: " + filter); @@ -203,7 +207,7 @@ public void sync( LOG.error(e, "SyncDelta handling for {0} failed", sr.getName()); } } - } else { + } else if (oclass.is(ObjectClass.GROUP_NAME)) { for (SearchResult sr : changes) { try { handleSyncGDelta(ctx, sr, attrsToGet, count == 1 ? latestSyncToken : token, handler); @@ -212,6 +216,15 @@ public void sync( LOG.error(e, "SyncDelta handling for {0} failed", sr.getName()); } } + } else { + for (SearchResult sr : changes) { + try { + handleSyncAODelta(ctx, sr, attrsToGet, count == 1 ? latestSyncToken : token, handler); + count--; + } catch (NamingException e) { + LOG.error(e, "SyncDelta handling for {0} failed", sr.getName()); + } + } } if (handler instanceof SyncTokenResultsHandler) { @@ -219,7 +232,7 @@ public void sync( } } - public SyncToken getLatestSyncToken() { + public SyncToken getLatestSyncToken(ObjectClass oclass) { // ----------------------------------- // Create basicLdapSearch control // ----------------------------------- @@ -523,6 +536,90 @@ protected void handleSyncGDelta( } } + @SuppressWarnings("unchecked") + protected void handleSyncAODelta( + final LdapContext ctx, + final SearchResult sr, + final Collection attrsToGet, + final SyncToken token, + final SyncResultsHandler handler) + throws NamingException { + + if (ctx == null || sr == null) { + throw new ConnectorException("Invalid context or search result."); + } + + ctx.setRequestControls(new Control[] { new DeletedControl() }); + + // Just used to retrieve object classes and to pass to getSyncDelta + Attributes profile = sr.getAttributes(); + + if (LOG.isOk()) { + LOG.ok("Object profile: {0}", profile); + } + + String guid = GUID.getGuidAsString((byte[]) profile.get(ADConnector.OBJECTGUID).get()); + + boolean isDeleted = false; + + try { + + javax.naming.directory.Attribute attributeIsDeleted = profile.get("isDeleted"); + + isDeleted = attributeIsDeleted != null + && attributeIsDeleted.get() != null + && Boolean.parseBoolean( + attributeIsDeleted.get().toString()); + + } catch (NoSuchElementException e) { + if (LOG.isOk()) { + LOG.ok("Cannot find the isDeleted element for any-object."); + } + } catch (Throwable t) { + LOG.error(t, "Error retrieving isDeleted attribute"); + } + + // We need for this beacause DirSync can return an uncomplete profile. + profile = ctx.getAttributes(""); + + final ADConfiguration conf = (ADConfiguration) conn.getConfiguration(); + + if (LOG.isOk()) { + LOG.ok("Created/Updated/Deleted any-object {0}", sr.getNameInNamespace()); + } + + if (isDeleted) { + + if (LOG.isOk()) { + LOG.ok("Deleted any-object {0}", sr.getNameInNamespace()); + } + + if (conf.isRetrieveDeletedAnyObject()) { + handler.handle(getSyncDelta( + LdapSchema.ANY_OBJECT_CLASS, + sr.getNameInNamespace(), + SyncDeltaType.DELETE, + token, + profile, + attrsToGet, + true)); + } + + } else { + // user to be created/updated + if (LOG.isOk()) { + LOG.ok("Created/Updated any-object {0}", sr.getNameInNamespace()); + } + + String userDN = sr.getNameInNamespace(); + + handleEntry( + ctx, LdapSchema.ANY_OBJECT_CLASS, userDN, conf.getAnyObjectSearchFilter(), handler, token, conf, attrsToGet); + + ctx.setRequestControls(null); + } + } + protected SyncDelta getSyncDelta( final ObjectClass oclass, final String entryDN, @@ -545,8 +642,8 @@ protected SyncDelta getSyncDelta( Uid uid = null; - if (StringUtil.isNotBlank(conn.getSchemaMapping().getLdapUidAttribute(oclass))) { - uidAttribute = profile.get(conn.getSchemaMapping().getLdapUidAttribute(oclass)); + if (StringUtil.isNotBlank(conn.getSchema().getLdapUidAttribute(oclass))) { + uidAttribute = profile.get(conn.getSchema().getLdapUidAttribute(oclass)); if (uidAttribute != null) { uid = new Uid(uidAttribute.get().toString()); @@ -631,7 +728,8 @@ protected void handleEntry( if (deltaType != SyncDeltaType.DELETE || (oclass.is(ObjectClass.GROUP_NAME) && conf.isRetrieveDeletedGroup()) - || (oclass.is(ObjectClass.ACCOUNT_NAME) && conf.isRetrieveDeletedUser())) { + || (oclass.is(ObjectClass.ACCOUNT_NAME) && conf.isRetrieveDeletedUser()) + || (oclass.is(LdapSchema.ANY_OBJECT_NAME) && conf.isRetrieveDeletedAnyObject())) { handler.handle(getSyncDelta( oclass, diff --git a/src/main/java/net/tirasa/connid/bundles/ad/sync/USNSyncStrategy.java b/src/main/java/net/tirasa/connid/bundles/ad/sync/USNSyncStrategy.java index c66a020..280aaec 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/sync/USNSyncStrategy.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/sync/USNSyncStrategy.java @@ -39,6 +39,8 @@ import net.tirasa.connid.bundles.ad.ADConnector; import net.tirasa.connid.bundles.ad.util.ADUtilities; import net.tirasa.connid.bundles.ad.util.DeletedControl; +import net.tirasa.connid.bundles.ad.util.DirSyncUtils; +import net.tirasa.connid.bundles.ldap.schema.LdapSchema; import net.tirasa.connid.bundles.ldap.search.LdapInternalSearch; import org.identityconnectors.common.StringUtil; import org.identityconnectors.common.logging.Log; @@ -130,10 +132,9 @@ public void sync( // get lastest sync token before start pulling objects latestSyncToken = token; - if ((oclass.is(ObjectClass.ACCOUNT_NAME) - && ((ADConfiguration) conn.getConfiguration()).isRetrieveDeletedUser()) - || (oclass.is(ObjectClass.GROUP_NAME) - && ((ADConfiguration) conn.getConfiguration()).isRetrieveDeletedGroup())) { + if ((oclass.is(ObjectClass.ACCOUNT_NAME) && ((ADConfiguration) conn.getConfiguration()).isRetrieveDeletedUser()) + || (oclass.is(ObjectClass.GROUP_NAME) && ((ADConfiguration) conn.getConfiguration()).isRetrieveDeletedGroup()) + || (oclass.is(LdapSchema.ANY_OBJECT_NAME) && ((ADConfiguration) conn.getConfiguration()).isRetrieveDeletedAnyObject())) { syncDeletedObjects(token, handler, options, oclass); } @@ -227,7 +228,7 @@ private void sync( LOG.error(e, "SyncDelta handling for '{0}' failed", sr.getName()); } } - } else { + } else if (oclass.is(ObjectClass.GROUP_NAME)) { for (SearchResult sr : changes) { LOG.ok("Remaining {0} groups to be processed", count); try { @@ -242,6 +243,21 @@ private void sync( LOG.error(e, "SyncDelta handling for '{0}' failed", sr.getName()); } } + } else { + for (SearchResult sr : changes) { + LOG.ok("Remaining {0} any-objects to be processed", count); + try { + handleSyncAODelta( + ctx, + sr, + attrsToGet, + token, + handler); + count--; + } catch (NamingException e) { + LOG.error(e, "SyncDelta handling for '{0}' failed", sr.getName()); + } + } } } catch (Exception e) { throw new ConnectorException("While looking for changes", e); @@ -265,8 +281,9 @@ private void syncDeletedObjects( String filter = oclass.is(ObjectClass.ACCOUNT_NAME) ? // get user filter "(&(objectClass=user)(isDeleted=TRUE))" - : // get group filter - "(&(objectClass=group)(isDeleted=TRUE))"; + : oclass.is(ObjectClass.GROUP_NAME) // get group filter + ? "(&(objectClass=group)(isDeleted=TRUE))" + : DirSyncUtils.createDirSyncAOFilter((ADConfiguration) conn.getConfiguration(), true); sync(true, filter, token, handler, options, oclass); } @@ -283,14 +300,15 @@ private void syncCurrentObjects( String filter = oclass.is(ObjectClass.ACCOUNT_NAME) ? // get user filter createDirSyncUFilter((ADConfiguration) conn.getConfiguration(), utils) - : // get group filter - createDirSyncGFilter(); + : oclass.is(ObjectClass.GROUP_NAME) // get group filter + ? createDirSyncGFilter() + : DirSyncUtils.createDirSyncAOFilter((ADConfiguration) conn.getConfiguration(), false); sync(false, filter, token, handler, options, oclass); } @Override - public SyncToken getLatestSyncToken() { + public SyncToken getLatestSyncToken(ObjectClass oclass) { // ----------------------------------- // Create basicLdapSearch control // ----------------------------------- diff --git a/src/main/java/net/tirasa/connid/bundles/ad/util/ADGuardedPasswordAttribute.java b/src/main/java/net/tirasa/connid/bundles/ad/util/ADGuardedPasswordAttribute.java index fa8b250..1a0468c 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/util/ADGuardedPasswordAttribute.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/util/ADGuardedPasswordAttribute.java @@ -15,7 +15,6 @@ */ package net.tirasa.connid.bundles.ad.util; -import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.List; @@ -26,7 +25,9 @@ import org.identityconnectors.framework.common.objects.Attribute; import org.identityconnectors.framework.common.objects.OperationalAttributes; -public abstract class ADGuardedPasswordAttribute { +import net.tirasa.connid.bundles.ldap.schema.GuardedPasswordAttribute; + +public abstract class ADGuardedPasswordAttribute extends GuardedPasswordAttribute { private static final Log LOG = Log.getLog(ADGuardedPasswordAttribute.class); @@ -54,13 +55,6 @@ public static ADGuardedPasswordAttribute create(final String attrName) { return new Empty(attrName); } - public abstract void access(final Accessor accessor); - - public interface Accessor { - - void access(BasicAttribute passwordAttribute); - } - private static final class Simple extends ADGuardedPasswordAttribute { private final String attrName; @@ -72,7 +66,6 @@ private Simple(String attrName, GuardedString password) { this.password = password; } - @Override public void access(final Accessor accessor) { password.access(clearChars -> { final String quotedPwd = "\"" + new String(clearChars) + "\""; @@ -92,7 +85,6 @@ private Empty(String attrName) { this.attrName = attrName; } - @Override public void access(Accessor accessor) { accessor.access(new BasicAttribute(attrName)); } diff --git a/src/main/java/net/tirasa/connid/bundles/ad/util/ADUtilities.java b/src/main/java/net/tirasa/connid/bundles/ad/util/ADUtilities.java index 200e902..46ea4d9 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/util/ADUtilities.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/util/ADUtilities.java @@ -59,7 +59,7 @@ import net.tirasa.connid.bundles.ldap.commons.LdapConstants; import net.tirasa.connid.bundles.ldap.commons.LdapEntry; import net.tirasa.connid.bundles.ldap.commons.LdapUtil; -import net.tirasa.connid.bundles.ldap.schema.LdapSchemaMapping; +import net.tirasa.connid.bundles.ldap.schema.LdapSchema; import net.tirasa.connid.bundles.ldap.search.LdapFilter; import net.tirasa.connid.bundles.ldap.search.LdapInternalSearch; import net.tirasa.connid.bundles.ldap.search.LdapSearches; @@ -176,7 +176,7 @@ public Set getAttributesToGet(final String[] attributesToGet, final Obje // AD-52 (paged membership retrieving: 1k a time) // ------------------------------------------------- final String memberships - = ADConfiguration.class.cast(connection.getConfiguration()).getGroupMemberReferenceAttribute(); + = ADConfiguration.class.cast(connection.getConfiguration()).getGroupMemberAttribute(); if (oclass.is(ObjectClass.GROUP_NAME) && result.contains(memberships)) { // AD specific, for checking wether a user is enabled or not @@ -207,13 +207,13 @@ public Set getAttributesToGet(final String[] attributesToGet, final Obje private void removeNonReadableAttributes(final Set attributes, final ObjectClass oclass) { // Since the groups attributes are fake attributes, we don't want to - // send them to LdapSchemaMapping. This, for example, avoid an + // send them to LdapSchema. This, for example, avoid an // (unlikely) conflict with a custom attribute defined in the server // schema. boolean ldapGroups = attributes.remove(LdapConstants.LDAP_GROUPS_NAME); boolean posixGroups = attributes.remove(LdapConstants.POSIX_GROUPS_NAME); - connection.getSchemaMapping().removeNonReadableAttributes(oclass, attributes); + connection.getSchema().removeNonReadableAttributes(oclass, attributes); if (ldapGroups) { attributes.add(LdapConstants.LDAP_GROUPS_NAME); @@ -225,13 +225,13 @@ private void removeNonReadableAttributes(final Set attributes, final Obj } public static Set getAttributesReturnedByDefault(final LdapConnection conn, final ObjectClass oclass) { - if (oclass.equals(LdapSchemaMapping.ANY_OBJECT_CLASS)) { + if (oclass.equals(LdapSchema.ANY_OBJECT_CLASS)) { return newSet(Name.NAME); } final Set result = newCaseInsensitiveSet(); - final ObjectClassInfo oci = conn.getSchemaMapping().schema().findObjectClassInfo(oclass.getObjectClassValue()); + final ObjectClassInfo oci = conn.getSchema().schema().findObjectClassInfo(oclass.getObjectClassValue()); if (oci != null) { for (AttributeInfo info : oci.getAttributeInfo()) { @@ -251,7 +251,7 @@ public Set getLdapAttributesToGet(final Set attrsToGet, final Ob boolean posixGroups = cleanAttrsToGet.remove(LdapConstants.POSIX_GROUPS_NAME); - final Set result = connection.getSchemaMapping().getLdapAttributes(oclass, cleanAttrsToGet, true); + final Set result = connection.getSchema().getLdapAttributes(oclass, cleanAttrsToGet, true); if (posixGroups) { result.add(GroupHelper.getPosixRefAttribute()); @@ -282,13 +282,13 @@ public ConnectorObject createConnectorObject( final ConnectorObjectBuilder builder = new ConnectorObjectBuilder(); builder.setObjectClass(oclass); - if (OBJECTGUID.equals(connection.getSchemaMapping().getLdapUidAttribute(oclass))) { + if (OBJECTGUID.equals(connection.getSchema().getLdapUidAttribute(oclass))) { builder.setUid(GUID.getGuidAsString((byte[]) entry.getAttributes().get(OBJECTGUID).get())); } else { - builder.setUid(connection.getSchemaMapping().createUid(oclass, entry)); + builder.setUid(connection.getSchema().createUid(oclass, entry)); } - builder.setName(connection.getSchemaMapping().createName(oclass, entry)); + builder.setName(connection.getSchema().createName(oclass, entry)); String pgDN = null; @@ -350,7 +350,7 @@ public ConnectorObject createConnectorObject( ? AttributeBuilder.buildEnabled(true) : AttributeBuilder.buildEnabled(false)); - attribute = connection.getSchemaMapping().createAttribute(oclass, attributeName, entry, false); + attribute = connection.getSchema().createAttribute(oclass, attributeName, entry, false); } catch (NamingException e) { LOG.error(e, "While fetching " + UACCONTROL_ATTR); } @@ -371,16 +371,16 @@ public ConnectorObject createConnectorObject( attribute = AttributeBuilder.build(ADConfiguration.PRIMARY_GROUP_DN_NAME, pgDN); } else if (oclass.is(ObjectClass.GROUP_NAME) && String.format("%s;range=%d-%d", ADConfiguration.class.cast(connection.getConfiguration()). - getGroupMemberReferenceAttribute(), 0, 999).equalsIgnoreCase(attributeName)) { + getGroupMemberAttribute(), 0, 999).equalsIgnoreCase(attributeName)) { // loop on membership ranges and populate member attribute final String membAttrPrefix = ADConfiguration.class.cast(connection.getConfiguration()). - getGroupMemberReferenceAttribute(); + getGroupMemberAttribute(); // search for less than 1k memberships String membAttrName = String.format("%s;range=0-*", membAttrPrefix); - attribute = connection.getSchemaMapping().createAttribute(oclass, membAttrName, entry, true); + attribute = connection.getSchema().createAttribute(oclass, membAttrName, entry, true); final ArrayList values = new ArrayList(attribute.getValue()); @@ -389,7 +389,7 @@ public ConnectorObject createConnectorObject( int start = 0; int end = 999; membAttrName = String.format("%s;range=%d-%d", membAttrPrefix, start, end); - attribute = connection.getSchemaMapping().createAttribute(oclass, membAttrName, entry, true); + attribute = connection.getSchema().createAttribute(oclass, membAttrName, entry, true); values.addAll(attribute.getValue()); @@ -418,7 +418,7 @@ public ConnectorObject createConnectorObject( attribute = AttributeBuilder.build(membAttrPrefix, values); } else if (profile.get(attributeName) != null) { - attribute = connection.getSchemaMapping().createAttribute(oclass, attributeName, entry, false); + attribute = connection.getSchema().createAttribute(oclass, attributeName, entry, false); } // Avoid attribute adding in case of attribute name not found @@ -459,7 +459,11 @@ public final String getDN(final ObjectClass oclass, final Name nameAttr, final A return "cn=" + cn + "," + (oclass.is(ObjectClass.ACCOUNT_NAME) ? ((ADConfiguration) (connection.getConfiguration())).getDefaultPeopleContainer() - : ((ADConfiguration) (connection.getConfiguration())).getDefaultGroupContainer()); + : oclass.is(ObjectClass.GROUP_NAME) + ? ((ADConfiguration) (connection.getConfiguration())).getDefaultGroupContainer() + : oclass.is(LdapSchema.ANY_OBJECT_NAME) + ? ((ADConfiguration) (connection.getConfiguration())).getDefaultAnyObjectContainer() + : connection.getConfiguration().getBaseContexts()[0]); } /** @@ -516,7 +520,7 @@ public LdapEntry getEntryToBeUpdated(final String entryDN) { } public ConnectorObject getEntryToBeUpdated(final Uid uid, final ObjectClass oclass) { - final String filter = connection.getSchemaMapping().getLdapUidAttribute(oclass) + "=" + uid.getUidValue(); + final String filter = connection.getSchema().getLdapUidAttribute(oclass) + "=" + uid.getUidValue(); final ConnectorObject obj = LdapSearches.findObject( connection, oclass, @@ -615,7 +619,7 @@ public Set getGroups(final String entryDN) { public Set getGroups(final String entryDN, final String... baseContexts) { final String member = ((ADConfiguration) connection.getConfiguration()). - getGroupMemberReferenceAttribute(); + getGroupMemberAttribute(); final Set ldapGroups = new TreeSet(String.CASE_INSENSITIVE_ORDER); for (SearchResult res : basicLdapSearch(filterInOr(member, entryDN), baseContexts)) { diff --git a/src/main/java/net/tirasa/connid/bundles/ad/util/DirSyncUtils.java b/src/main/java/net/tirasa/connid/bundles/ad/util/DirSyncUtils.java index e007484..142bef4 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/util/DirSyncUtils.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/util/DirSyncUtils.java @@ -64,6 +64,42 @@ public static String createDirSyncGFilter(final ADConfiguration conf) { return filter.toString(); } + public static String createDirSyncAOFilter(final ADConfiguration conf) { + return createDirSyncAOFilter(conf, conf.isRetrieveDeletedAnyObject()); + } + + public static String createDirSyncAOFilter(final ADConfiguration conf, boolean isDeleted) { + final StringBuilder filter = new StringBuilder(); + final StringBuilder oclassFilter = new StringBuilder(); + String[] anyObjectClasses = conf.getAnyObjectClasses(); + if (anyObjectClasses.length > 1) { + if (conf.isFilterWithOrInsteadOfAnd()){ + oclassFilter.append("(|"); + } + else { + oclassFilter.append("(&"); + } + } + for (String oclass : anyObjectClasses) { + oclassFilter.append("(objectClass="); + oclassFilter.append(oclass); + oclassFilter.append(")"); + } + if (anyObjectClasses.length > 1) { + oclassFilter.append(")"); + } + + if (isDeleted) { + filter.append(oclassFilter.toString()); + } else { + filter.append("(&"); + filter.append(oclassFilter.toString()); + filter.append("(!(isDeleted=TRUE)))"); + } + + return filter.toString(); + } + public static String createLdapUFilter(final ADConfiguration conf) { final String[] memberships = conf.getMemberships(); diff --git a/src/main/resources/net/tirasa/connid/bundles/ad/Messages.properties b/src/main/resources/net/tirasa/connid/bundles/ad/Messages.properties index 8ea4454..0b5ddaa 100644 --- a/src/main/resources/net/tirasa/connid/bundles/ad/Messages.properties +++ b/src/main/resources/net/tirasa/connid/bundles/ad/Messages.properties @@ -29,15 +29,9 @@ excludeAttributeChangesOnUpdate.help=Specify 'TRUE' if you want to exclude attri trustAllCerts.display=Trust all certs trustAllCerts.help=Specify 'TRUE' to trust all certs. The default is "false". -retrieveDeletedUser.display=Retrieve deleted users -retrieveDeletedUser.help=Specify 'TRUE' to retrieve deleted users also. The default is "true". - memberships.display=Memberships memberships.help=Specify memberships -baseContextsToSynchronize.display=Root suffixes -baseContextsToSynchronize.help=Insert root suffixes - host.display=Server hostname host.help=Insert hostname @@ -53,45 +47,104 @@ principal.help=Insert DN of a user with administration capabilities credentials.display=Principal password credentials.help=Insert password for administrator -objectClassesToSynchronize.display=Object classes to synchronize -objectClassesToSynchronize.help=Specify object classes to identify entry to synchronize - latestSyncToken.display=Latest sync token latestSyncToken.help=Latest sync token -defaultPeopleContainer.display = Default people container -defaultPeopleContainer.help = Default people container to be used in case of entry DN is not provided - accountObjectClasses.display=Entry object classes accountObjectClasses.help=Insert object classes to assign to managed entries +groupObjectClasses.display=Group entry object classes +groupObjectClasses.help=Insert object classes to assign to managed group entries +anyObjectClasses.display=Any-object entry object classes +anyObjectClasses.help=Insert object classes to assign to managed any-object entries +retrieveDeletedUser.display=Retrieve deleted users +retrieveDeletedUser.help=Specify 'TRUE' to retrieve deleted users also. The default is "true". retrieveDeletedGroup.display=Retrieve deleted groups -retrieveDeletedGroup.help=Specify 'TRUE' to retrieve deleted groups also +retrieveDeletedGroup.help=Specify 'TRUE' to retrieve deleted groups also. The default is "true". +retrieveDeletedAnyObject.display=Retrieve deleted any-objects +retrieveDeletedAnyObject.help=Specify 'TRUE' to retrieve deleted any-objects also. The default is "true". +defaultPeopleContainer.display = Default people container +defaultPeopleContainer.help = Default people container to be used in case of entry DN is not provided defaultGroupContainer.display=Default group container defaultGroupContainer.help=Default group container to be used in case of entry DN is not provided +defaultAnyObjectContainer.display=Default any-object container +defaultAnyObjectContainer.help=Default any-object container to be used in case of entry DN is not provided -userSearchScope.display= User search scope -userSearchScope.help= Choose object, onlevel or subtree -groupSearchScope.display= Group search scope -groupSearchScope.help= Choose object, onlevel or subtree +userSearchScope.display=User search scope +userSearchScope.help=Choose object, onelevel or subtree +groupSearchScope.display=Group search scope +groupSearchScope.help=Choose object, onelevel or subtree +anyObjectSearchScope.display=Any Object search scope +anyObjectSearchScope.help=Choose object, onelevel or subtree accountSearchFilter.display=Custom user search filter accountSearchFilter.help=Custom user search filter groupSearchFilter.display=Custom group search filter groupSearchFilter.help=Custom group search filter +anyObjectSearchFilter.display=Custom any-object search filter +anyObjectSearchFilter.help=Custom any-object search filter +accountSynchronizationFilter.display=LDAP Filter for Accounts to Synchronize +accountSynchronizationFilter.help=An optional LDAP filter for the objects to synchronize. Because the change log is for all objects, this filter updates only objects that match the specified filter. If you specify a filter, an object will be synchronized only if it matches the filter and includes a synchronized object class. +attributesToSynchronize.display=Attributes to Synchronize +attributesToSynchronize.help=The names of the attributes to synchronize. This ignores updates from the change log if they do not update any of the named attributes. For example, if only "department" is listed, then only changes that affect "department" will be processed. All other updates are ignored. If blank (the default), then all changes are processed. +objectClassesToSynchronize.display=Object classes to synchronize +objectClassesToSynchronize.help=Specify object classes to identify entry to synchronize +baseContextsToSynchronize.display=Root suffixes +baseContextsToSynchronize.help=Insert root suffixes + +baseContexts.display=Base Contexts +baseContexts.help=One or more starting points in the LDAP tree that will be used when searching the tree. Searches are performed when discovering users from the LDAP server or when looking for the groups of which a user is a member. userBaseContexts.display=Base contexts for user entry searches userBaseContexts.help=DN of context to be used as starting point for user entry searches groupBaseContexts.display=Base contexts for group entry searches groupBaseContexts.help=DN of context to be used as starting point for group entry searches - -groupMemberReferenceAttribute.display=Group members reference attribute -groupMemberReferenceAttribute.help=Group attribute referencing (by DN) the users members of a group - +anyObjectBaseContexts.display=Base contexts for any-object entry searches +anyObjectBaseContexts.help=DN of context to be used as starting point for any-object entry searches + +dnAttribute.display=Entry DN attribute name +dnAttribute.help=Entry DN attribute name (default: entryDN) +accountUserNameAttributes.display=Account User Name Attributes +accountUserNameAttributes.help=Attribute or attributes which holds the account''s user name. They will be used when authenticating to find the LDAP entry for the user name to authenticate. +groupNameAttributes.display=Group Name Attributes +groupNameAttributes.help=Attribute or attributes which holds the group''s name. Default is "cn". +anyObjectNameAttributes.display=Any-object Name Attributes +anyObjectNameAttributes.help=Attribute or attributes which holds the any-object''s name. + +passwordAttribute.display=Password Attribute +passwordAttribute.help=The name of the LDAP attribute which holds the password. When changing an user''s password, the new password is set to this attribute. Default is "userPassword". +passwordAttributeToSynchronize.display=Password Attribute to Synchronize +passwordAttributeToSynchronize.help=The name of the password attribute to synchronize when performing password synchronization. +passwordDecryptionInitializationVector.display=Password Decryption Initialization Vector +passwordDecryptionInitializationVector.help=The initialization vector to decrypt passwords with when performing password synchronization. +passwordDecryptionKey.display=Password Decryption Key +passwordDecryptionKey.help=The key to decrypt passwords with when performing password synchronization. +passwordHashAlgorithm.display=Password Hash Algorithm +passwordHashAlgorithm.help=Indicates the algorithm that the Identity system should use to hash the password. Currently supported values are SSHA, SHA, SMD5, and MD5. A blank value indicates that the system will not hash passwords. This will cause cleartext passwords to be stored in LDAP unless the LDAP server performs the hash (Netscape Directory Server and iPlanet Directory Server do). +retrievePasswordsWithSearch.display=Retrieve passwords with search +retrievePasswordsWithSearch.help=Whether to retrieve user passwords when searching. The default is "false". +synchronizePasswords.display=Enable Password Synchronization +synchronizePasswords.help=If true, the connector will synchronize passwords. The Password Capture Plugin needs to be installed for password synchronization to work. Default is "false". + +syncStrategy.display=Sync strategy class +syncStrategy.help=A class implementing LdapSyncStrategy to be used for sync operations +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". + +readSchema.display=Read Schema +readSchema.help=If true, the connector will read the schema from the server. If false, the connector will provide a default schema based on the object classes in the configuration. This property must be true in order to use extended object classes. Default is "true". +connectTimeout.display=Connection Timeout (Milliseconds) +connectTimeout.help=Time to wait when opening new server connections. Value of 0 means the TCP network timeout will be used, which may be several minutes. Value less than 0 means there is no limit. +readTimeout.display=Read Timeout (Milliseconds) +readTimeout.help=Time to wait for a response to be received. If there is no response within the specified time period, the read attempt will be aborted. Value 0 or less than 0 means there is no limit. + +addPrincipalToNewGroups.display=Automatically add the configured principal as first member of a new group +addPrincipalToNewGroups.help=When enabled, the configured principal is added as first member of a new group. Default is "false". groupOwnerReferenceAttribute.display=Group owner reference attribute groupOwnerReferenceAttribute.help=Group attribute name referencing (by DN) the owner - membershipConservativePolicy.display= Conservative membership policy membershipConservativePolicy.help= Conservative managing and assignment of groups to user. The groups already assigned will not be removed. @@ -99,8 +152,15 @@ uidAttribute.display=Uid Attribute uidAttribute.help=The name of the attribute which is mapped to the Uid attribute. Default is "sAMAccountName". gidAttribute.display=Uid Attribute for groups gidAttribute.help=The name of the attribute which is mapped to the Uid attribute for groups. Default is "sAMAccountName". +aoidAttribute.display=Uid Attribute for any-objects +aoidAttribute.help=The name of the attribute which is mapped to the Uid attribute for any-objects. Default is "cn". defaultIdAttribute.display=Default Uid -defaultIdAttribute.help=The name of the attribute which is mapped to the id attribute in case of object different from account and group. Default is "cn". +defaultIdAttribute.help=The name of the attribute which is mapped to the id attribute in case of object different from account, group or anyobject. Default is "cn". + +useVlvControls.display=Use VLV Controls +useVlvControls.help=Wheter to enforce usage of VLV controls over standard LDAP controls. Default is "false". +vlvSortAttribute.display=VLV Sort Attribute +vlvSortAttribute.help=Specify the sort attribute to use for VLV indexes on the resource. Default is "uid". # Configuration properties validation. host.notBlank=The host cannot be blank @@ -111,6 +171,10 @@ baseContexts.noInvalidLdapNames=The base context {0} cannot be parsed passwordAttribute.notBlank=The password attribute cannot be blank accountObjectClasses.notEmpty=The list of account object classes cannot be empty accountObjectClasses.noBlankValues=The list of account object classes cannot contain blank values +groupObjectClasses.noBlankValues=The list of group object classes cannot contain blank values +groupObjectClasses.notEmpty=The list of group object classes cannot be empty +anyObjectClasses.noBlankValues=The list of any-object object classes cannot contain blank values +anyObjectClasses.notEmpty=The list of any-object object classes cannot be empty accountUserNameAttributes.notEmpty=The list of account user name attributes cannot be empty accountUserNameAttributes.noBlankValues=The list of account user name attributes cannot contain blank values groupMemberAttribute.notBlank=The group member attribute cannot be blank @@ -130,6 +194,24 @@ passwordAttributeToSynchronize.notBlank=The password attribute to synchronize ca decryptionKey.notBlank=The decryption key cannot be blank decryptionInitializationVector.notBlank=The decryption initialization vector cannot be blank +accountUserNameAttributes.noBlankValues=The list of account user name attributes cannot contain blank values +accountUserNameAttributes.notEmpty=The list of account user name attributes cannot be empty +groupNameAttributes.noBlankValues=The list of group name attributes cannot contain blank values +groupNameAttributes.notEmpty=The list of group name attributes cannot be empty +anyObjectNameAttributes.noBlankValues=The list of any-object name attributes cannot contain blank values +anyObjectNameAttributes.notEmpty=The list of any-object name attributes cannot be empty + +userSearchScope.invalidScope=The user search scope was invalid, it must be one of 'object', 'onelevel' or 'subtree' +userSearchScope.notBlank=The user search scope cannot be blank +groupSearchScope.invalidScope=The group search scope was invalid, it must be one of 'object', 'onelevel' or 'subtree' +groupSearchScope.notBlank=The group search scope cannot be blank +anyObjectSearchScope.invalidScope=The any object search scope was invalid, it must be one of 'object', 'onelevel' or 'subtree' +anyObjectSearchScope.notBlank=The any object search scope cannot be blank + +gidAttribute.notBlank=The attribute to map to Gid cannot be blank + +attributesToSynchronize.noBlankValues=The list of attributes to synchronize cannot contain blank values + entryNotFound=Entry "{0}" not found readingPasswordsNotSupported=Returning passwords from a search operation is not supported @@ -141,8 +223,9 @@ authenticationFailed=Authentication failed for "{0}" cannotResolveUsername=Cannot resolve "{0}" to an entry moreThanOneEntryMatched=More than one entry matched "{0}" -syncStrategy.display=Sync Strategy -helpMessageKey=Sync Strategy class name +syncStrategy.classNotFound=The specified class cannot be found +syncStrategy.classNotSyncStrategy=The specified class does not implement LdapSyncStrategy +syncStrategy.notBlank=The sync strategy cannot be blank userAuthenticationAttributes.display=User authentication attributes userAuthenticationAttributes.help=Attributes to be used during authentication operation diff --git a/src/test/java/net/tirasa/connid/bundles/ad/AbstractTest.java b/src/test/java/net/tirasa/connid/bundles/ad/AbstractTest.java index 5835860..f48dc47 100644 --- a/src/test/java/net/tirasa/connid/bundles/ad/AbstractTest.java +++ b/src/test/java/net/tirasa/connid/bundles/ad/AbstractTest.java @@ -30,6 +30,7 @@ import javax.naming.directory.DirContext; import net.tirasa.connid.bundles.ad.sync.ADSyncStrategy; import net.tirasa.connid.bundles.ad.sync.USNSyncStrategy; +import net.tirasa.connid.bundles.ldap.schema.LdapSchema; import org.identityconnectors.common.StringUtil; import org.identityconnectors.common.logging.Log; import org.identityconnectors.common.security.GuardedString; @@ -104,9 +105,11 @@ protected static ADConfiguration getSimpleConf(final Properties prop) { configuration.setUidAttribute("sAMAccountName"); configuration.setGidAttribute("sAMAccountName"); + configuration.setAoidAttribute("uid"); configuration.setDefaultPeopleContainer("CN=Users," + BASE_CONTEXT); configuration.setDefaultGroupContainer("CN=Users," + BASE_CONTEXT); + configuration.setDefaultAnyObjectContainer("CN=Computers," + BASE_CONTEXT); configuration.setObjectClassesToSynchronize("user"); @@ -114,14 +117,16 @@ protected static ADConfiguration getSimpleConf(final Properties prop) { configuration.setPort(Integer.parseInt(prop.getProperty("port"))); configuration.setAccountObjectClasses("top", "person", "organizationalPerson", "user"); + configuration.setAnyObjectClasses("top", "device"); configuration.setBaseContextsToSynchronize(prop.getProperty("baseContextToSynchronize")); configuration.setUserBaseContexts(BASE_CONTEXT); - + // set default group container as Fgroup search context configuration.setGroupBaseContexts(configuration.getDefaultGroupContainer()); - + configuration.setAnyObjectBaseContexts(configuration.getDefaultAnyObjectContainer()); + configuration.setBaseContexts(BASE_CONTEXT); configuration.setPrincipal(prop.getProperty("principal")); configuration.setCredentials(new GuardedString(prop.getProperty("credentials").toCharArray())); @@ -136,6 +141,7 @@ protected static ADConfiguration getSimpleConf(final Properties prop) { configuration.setUserSearchScope("subtree"); configuration.setGroupSearchScope("subtree"); + configuration.setAnyObjectSearchScope("subtree"); configuration.setGroupSearchFilter( "(&(cn=GroupTest*)" @@ -152,6 +158,11 @@ protected static void baseSetup(final TestUtil util) { final Uid user = connector.create(ObjectClass.ACCOUNT, uMemberOfAll, null); assertNotNull(user); + final Set aoMemberOfAll = util.getSimpleAnyObjectProfile(util.getEntryIDs("OfAll", LdapSchema.ANY_OBJECT_CLASS), + conf, true); + final Uid anyObject = connector.create(LdapSchema.ANY_OBJECT_CLASS, aoMemberOfAll, null); + assertNotNull(anyObject); + final Set gMemberInFilter = util.getSimpleGroupProfile(util. getEntryIDs("InFilter", ObjectClass.GROUP), true); @@ -180,6 +191,11 @@ public static void cleanup(final TestUtil util) { connector.delete(ObjectClass.ACCOUNT, uid, null); assertNull(connector.getObject(ObjectClass.ACCOUNT, uid, null)); + uid = new Uid(util.getEntryIDs("OfAll", LdapSchema.ANY_OBJECT_CLASS).getValue()); + connector.delete(LdapSchema.ANY_OBJECT_CLASS, uid, null); + assertNull(connector.getObject(LdapSchema.ANY_OBJECT_CLASS, uid, null)); + + uid = new Uid(util.getEntryIDs("InFilter", ObjectClass.GROUP).getValue()); connector.delete(ObjectClass.GROUP, uid, null); assertNull(connector.getObject(ObjectClass.GROUP, uid, null)); diff --git a/src/test/java/net/tirasa/connid/bundles/ad/AnyObjectTest.java b/src/test/java/net/tirasa/connid/bundles/ad/AnyObjectTest.java new file mode 100644 index 0000000..fb1fa8e --- /dev/null +++ b/src/test/java/net/tirasa/connid/bundles/ad/AnyObjectTest.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2011 ConnId (connid-dev@googlegroups.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.tirasa.connid.bundles.ad; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import net.tirasa.connid.bundles.ldap.schema.LdapSchema; + +public class AnyObjectTest extends AbstractTest { + + protected static TestUtil util; + + @BeforeAll + public static void init() { + AbstractTest.init(); + util = new TestUtil(connector, conf, LdapSchema.ANY_OBJECT_CLASS); + AbstractTest.baseSetup(util); + } + + @AfterAll + public static void cleanup() { + AbstractTest.cleanup(util); + } +} diff --git a/src/test/java/net/tirasa/connid/bundles/ad/BasicFeaturesTest.java b/src/test/java/net/tirasa/connid/bundles/ad/BasicFeaturesTest.java index b622f08..faefaa5 100644 --- a/src/test/java/net/tirasa/connid/bundles/ad/BasicFeaturesTest.java +++ b/src/test/java/net/tirasa/connid/bundles/ad/BasicFeaturesTest.java @@ -47,6 +47,7 @@ public static void init() { conf.setCredentials(new GuardedString("password".toCharArray())); conf.setUserBaseContexts("cn=users,o=isp"); conf.setReadSchema(false); + conf.setBaseContexts("o=isp"); conf.setMemberships( "cn=groupA,cn=group,o=isp", diff --git a/src/test/java/net/tirasa/connid/bundles/ad/TestUtil.java b/src/test/java/net/tirasa/connid/bundles/ad/TestUtil.java index d64fde8..fe33928 100644 --- a/src/test/java/net/tirasa/connid/bundles/ad/TestUtil.java +++ b/src/test/java/net/tirasa/connid/bundles/ad/TestUtil.java @@ -31,6 +31,7 @@ import org.identityconnectors.framework.common.objects.Name; import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.common.objects.Uid; +import net.tirasa.connid.bundles.ldap.schema.LdapSchema; public class TestUtil { @@ -84,23 +85,33 @@ public void createEntry(final int num) { } public String getEntryDN(final String cn, final ObjectClass oclass) { - return String.format("cn=%s,%s", cn, oclass.equals(ObjectClass.ACCOUNT) - ? conf.getDefaultPeopleContainer() : conf.getDefaultGroupContainer()); + return String.format("cn=%s,%s", cn, + oclass.equals(ObjectClass.ACCOUNT) + ? conf.getDefaultPeopleContainer() + : oclass.equals(ObjectClass.GROUP) + ? conf.getDefaultGroupContainer() + : oclass.equals(LdapSchema.ANY_OBJECT_CLASS) + ? conf.getDefaultAnyObjectContainer() + : conf.getBaseContexts()[0]); } public Set getSimpleProfile(final Map.Entry ids, final boolean withDN) { if (oclass.is(ObjectClass.ACCOUNT_NAME)) { return getSimpleUserProfile(ids, conf, withDN); - } else { + } else if (oclass.is(ObjectClass.GROUP_NAME)) { return getSimpleGroupProfile(ids, withDN); + } else { + return getSimpleAnyObjectProfile(ids, conf, withDN); } } public Set getSimpleProfile(final Map.Entry ids) { if (oclass.is(ObjectClass.ACCOUNT_NAME)) { return getSimpleUserProfile(ids, conf, true); - } else { + } else if (oclass.is(ObjectClass.GROUP_NAME)) { return getSimpleGroupProfile(ids, true); + } else { + return getSimpleAnyObjectProfile(ids, conf, true); } } @@ -155,6 +166,29 @@ public Set getSimpleGroupProfile(final Map.Entry ids, return attributes; } + public Set getSimpleAnyObjectProfile( + final Map.Entry ids, final ADConfiguration conf, final boolean withDN) { + + final Set attributes = new HashSet<>(); + + if (withDN) { + attributes.add(new Name(getEntryDN(ids.getKey(), LdapSchema.ANY_OBJECT_CLASS))); + } else { + attributes.add(new Name(ids.getValue())); + attributes.add(AttributeBuilder.build("cn", Collections.singletonList(ids.getKey()))); + } + + attributes.add(AttributeBuilder.build(conf.getAoidAttribute(), Collections.singletonList(ids.getValue()))); + + attributes.add(AttributeBuilder.build("serialNumber", Collections.singletonList("serialnumbertest"))); + + attributes.add(AttributeBuilder.build("description", Collections.singletonList("descriptiontest"))); + + attributes.add(AttributeBuilder.build("displayName", Collections.singletonList("dntest"))); + + return attributes; + } + public void cleanup(final int num) { for (int i = 1; i <= num; i++) { Uid uid = new Uid(getEntryIDs(String.valueOf(i)).getValue()); @@ -171,7 +205,11 @@ public void cleanup(final int num) { } public Map.Entry getEntryIDs(final String suffix, final ObjectClass oclass) { - final String prefix = oclass.equals(ObjectClass.ACCOUNT) ? "UserTest" : "GroupTest"; + final String prefix = oclass.equals(ObjectClass.ACCOUNT) + ? "UserTest" + : oclass.equals(ObjectClass.GROUP) + ? "GroupTest" + : "AnyObjectTest"; return new AbstractMap.SimpleEntry<>(prefix + suffix, "SAAN_" + prefix + suffix); } diff --git a/src/test/java/net/tirasa/connid/bundles/ad/crud/AnyObjectCrudTestITCase.java b/src/test/java/net/tirasa/connid/bundles/ad/crud/AnyObjectCrudTestITCase.java new file mode 100644 index 0000000..eb712f2 --- /dev/null +++ b/src/test/java/net/tirasa/connid/bundles/ad/crud/AnyObjectCrudTestITCase.java @@ -0,0 +1,508 @@ +/** + * Copyright (C) 2011 ConnId (connid-dev@googlegroups.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.tirasa.connid.bundles.ad.crud; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import net.tirasa.connid.bundles.ad.ADConfiguration; +import net.tirasa.connid.bundles.ad.ADConnector; +import net.tirasa.connid.bundles.ad.AnyObjectTest; +import net.tirasa.connid.bundles.ldap.schema.LdapSchema; + +import org.identityconnectors.framework.api.APIConfiguration; +import org.identityconnectors.framework.api.ConnectorFacade; +import org.identityconnectors.framework.api.ConnectorFacadeFactory; +import org.identityconnectors.framework.common.objects.Attribute; +import org.identityconnectors.framework.common.objects.AttributeBuilder; +import org.identityconnectors.framework.common.objects.AttributeUtil; +import org.identityconnectors.framework.common.objects.ConnectorObject; +import org.identityconnectors.framework.common.objects.Name; +import org.identityconnectors.framework.common.objects.OperationOptionsBuilder; +import org.identityconnectors.framework.common.objects.ResultsHandler; +import org.identityconnectors.framework.common.objects.SearchResult; +import org.identityconnectors.framework.common.objects.SortKey; +import org.identityconnectors.framework.common.objects.Uid; +import org.identityconnectors.framework.common.objects.filter.Filter; +import org.identityconnectors.framework.common.objects.filter.FilterBuilder; +import org.identityconnectors.framework.impl.api.APIConfigurationImpl; +import org.identityconnectors.framework.impl.api.local.JavaClassProperties; +import org.identityconnectors.test.common.TestHelpers; +import org.junit.jupiter.api.Test; + +public class AnyObjectCrudTestITCase extends AnyObjectTest { + + @Test + public void pagedSearch() { + final List results = new ArrayList<>(); + final ResultsHandler handler = results::add; + + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet(conf.getAoidAttribute()); + oob.setPageSize(2); + oob.setSortKeys(new SortKey(conf.getAoidAttribute(), false)); + + connector.search(LdapSchema.ANY_OBJECT_CLASS, null, handler, oob.build()); + + assertEquals(2, results.size()); + + results.clear(); + + String cookie = ""; + do { + oob.setPagedResultsCookie(cookie); + final SearchResult searchResult = connector.search(LdapSchema.ANY_OBJECT_CLASS, null, handler, oob.build()); + cookie = searchResult.getPagedResultsCookie(); + } while (cookie != null); + + assertEquals(11, results.size()); + } + + @Test + public void search() { + + final Map.Entry ids = util.getEntryIDs("1"); + + // create filter + final Filter filter = FilterBuilder.equalTo(AttributeBuilder.build(conf.getAoidAttribute(), ids.getValue())); + + // create results handler + final List results = new ArrayList<>(); + final ResultsHandler handler = results::add; + + // create options for returning attributes + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet(conf.getAoidAttribute()); + + connector.search(LdapSchema.ANY_OBJECT_CLASS, filter, handler, oob.build()); + + assertEquals(1, results.size()); + assertEquals(Collections.singletonList(ids.getValue()), + results.get(0).getAttributeByName(conf.getAoidAttribute()).getValue()); + } + + @Test + public void read() { + final Map.Entry ids = util.getEntryIDs("2"); + + // Ask just for sAMAccountName + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet(conf.getAoidAttribute()); + + final ConnectorObject object = connector.getObject(LdapSchema.ANY_OBJECT_CLASS, new Uid(ids.getValue()), oob.build()); + + assertNotNull(object); + assertNotNull(object.getAttributes()); + + // Returned attributes: sAMAccountName, NAME and UID + assertEquals(3, object.getAttributes().size()); + assertNotNull(object.getAttributeByName(conf.getAoidAttribute())); + assertEquals(Collections.singletonList(ids.getValue()), object.getAttributeByName(conf.getAoidAttribute()).getValue()); + } + + @Test + public void create() { + assertNotNull(connector); + assertNotNull(conf); + + final Map.Entry ids = util.getEntryIDs("11"); + + assertNull(connector.getObject(LdapSchema.ANY_OBJECT_CLASS, new Uid(ids.getValue()), null)); + + final Set attributes = util.getSimpleProfile(ids); + + final Uid uid = connector.create(LdapSchema.ANY_OBJECT_CLASS, attributes, null); + assertNotNull(uid); + assertEquals(ids.getValue(), uid.getUidValue()); + + // Ask just for serialNumber and description + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet(Arrays.asList("serialNumber", "description")); + + // retrieve created object + final ConnectorObject object = connector.getObject(LdapSchema.ANY_OBJECT_CLASS, uid, oob.build()); + + // check for serialNumber & description attributes + assertNotNull(object); + assertNotNull(object.getAttributes()); + + // Returned attributes: serialNumber, description, NAME and UID + assertEquals(4, object.getAttributes().size()); + assertNotNull(object.getAttributeByName("serialNumber")); + assertNotNull(object.getAttributeByName("description")); + + assertEquals(ids.getValue(), object.getUid().getUidValue()); + assertEquals( + util.getEntryDN(ids.getKey(), LdapSchema.ANY_OBJECT_CLASS).toLowerCase(), + object.getName().getNameValue().toLowerCase()); + + + connector.delete(LdapSchema.ANY_OBJECT_CLASS, uid, null); + assertNull(connector.getObject(LdapSchema.ANY_OBJECT_CLASS, uid, null)); + } + + @Test + public void createWithoutDN() { + assertNotNull(connector); + assertNotNull(conf); + + final Map.Entry ids = util.getEntryIDs("nodn11"); + + assertNull(connector.getObject(LdapSchema.ANY_OBJECT_CLASS, new Uid(ids.getValue()), null)); + + final Set attributes = util.getSimpleProfile(ids, false); + + final Uid uid = connector.create(LdapSchema.ANY_OBJECT_CLASS, attributes, null); + assertNotNull(uid); + assertEquals(ids.getValue(), uid.getUidValue()); + + // Ask just for serialNumber + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet("serialNumber"); + + // retrieve created object + final ConnectorObject object = connector.getObject(LdapSchema.ANY_OBJECT_CLASS, uid, oob.build()); + + // check for serialNumber attribute + assertNotNull(object); + assertNotNull(object.getAttributes()); + + // Returned attributes: memberOf, NAME and UID + assertEquals(3, object.getAttributes().size()); + assertNotNull(object.getAttributeByName("serialNumber")); + + assertEquals(ids.getValue(), object.getUid().getUidValue()); + assertEquals(util.getEntryDN(ids.getKey(), + LdapSchema.ANY_OBJECT_CLASS).toLowerCase(), object.getName().getNameValue().toLowerCase()); + + connector.delete(LdapSchema.ANY_OBJECT_CLASS, uid, null); + assertNull(connector.getObject(LdapSchema.ANY_OBJECT_CLASS, uid, null)); + } + + @Test + public void checkLdapGroups() { + assertNotNull(connector); + assertNotNull(conf); + + String baseContext = PROP.getProperty("baseContext"); + + final Map.Entry ids = util.getEntryIDs("20"); + + assertNull(connector.getObject(LdapSchema.ANY_OBJECT_CLASS, new Uid(ids.getValue()), null)); + + final Set attributes = util.getSimpleProfile(ids, false); + + final Attribute ldapGroups = AttributeUtil.find("ldapGroups", attributes); + attributes.remove(ldapGroups); + + final List groupsToBeAdded = new ArrayList<>(); + + if (ldapGroups != null && ldapGroups.getValue() != null) { + for (Object obj : ldapGroups.getValue()) { + groupsToBeAdded.add(obj.toString()); + } + } + + groupsToBeAdded.add("CN=Cert Publishers,CN=Users," + baseContext); + groupsToBeAdded.add("CN=Schema Admins,CN=Users," + baseContext); + + attributes.add(AttributeBuilder.build("ldapGroups", groupsToBeAdded)); + + Uid uid = connector.create(LdapSchema.ANY_OBJECT_CLASS, attributes, null); + assertNotNull(uid); + assertEquals(ids.getValue(), uid.getUidValue()); + + // Ask just for memberOf + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet("memberOf", "ldapGroups"); + + // retrieve created object + ConnectorObject object = connector.getObject(LdapSchema.ANY_OBJECT_CLASS, uid, oob.build()); + + // check for memberOf attribute + assertNotNull(object); + assertNotNull(object.getAttributes()); + + // Returned attributes: memberOf, NAME and UID + assertEquals(4, object.getAttributes().size()); + assertNotNull(object.getAttributeByName("memberOf")); + assertTrue(object.getAttributeByName("ldapGroups").getValue().contains( + "CN=Cert Publishers,CN=Users," + baseContext)); + assertTrue(object.getAttributeByName("ldapGroups").getValue().contains( + "CN=Schema Admins,CN=Users," + baseContext)); + + List attrToReplace = Arrays.asList(new Attribute[] { + AttributeBuilder.build("ldapGroups", "CN=Schema Admins,CN=Users," + baseContext) }); + + uid = connector.update( + LdapSchema.ANY_OBJECT_CLASS, + uid, + new HashSet<>(attrToReplace), + null); + + assertNotNull(uid); + assertEquals(ids.getValue(), uid.getUidValue()); + + object = connector.getObject(LdapSchema.ANY_OBJECT_CLASS, uid, oob.build()); + + assertFalse(object.getAttributeByName("ldapGroups").getValue().contains( + "CN=Cert Publishers,CN=Users," + baseContext)); + assertTrue(object.getAttributeByName("ldapGroups").getValue().contains( + "CN=Schema Admins,CN=Users," + baseContext)); + + attrToReplace = Arrays.asList(new Attribute[] { AttributeBuilder.build("ldapGroups", + "CN=Schema Admins,CN=Users," + baseContext, + "CN=Cert Publishers,CN=Users," + baseContext) }); + + uid = connector.update( + LdapSchema.ANY_OBJECT_CLASS, + uid, + new HashSet<>(attrToReplace), + null); + + assertNotNull(uid); + assertEquals(ids.getValue(), uid.getUidValue()); + + object = connector.getObject(LdapSchema.ANY_OBJECT_CLASS, uid, oob.build()); + + assertTrue(object.getAttributeByName("ldapGroups").getValue().contains( + "CN=Cert Publishers,CN=Users," + baseContext)); + assertTrue(object.getAttributeByName("ldapGroups").getValue().contains( + "CN=Schema Admins,CN=Users," + baseContext)); + + connector.delete(LdapSchema.ANY_OBJECT_CLASS, uid, null); + assertNull(connector.getObject(LdapSchema.ANY_OBJECT_CLASS, uid, null)); + } + + @Test + public void update() { + assertNotNull(connector); + assertNotNull(conf); + + final Map.Entry ids = util.getEntryIDs("3"); + + List attrToReplace = Arrays.asList(new Attribute[] { + AttributeBuilder.build("description", "descriptionupdate"), + AttributeBuilder.build("serialNumber", "serialnumberupdate")}); + + Uid uid = connector.update( + LdapSchema.ANY_OBJECT_CLASS, + new Uid(ids.getValue()), + new HashSet<>(attrToReplace), + null); + + assertNotNull(uid); + assertEquals(ids.getValue(), uid.getUidValue()); + + // Ask just for sAMAccountName + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet("description", "serialNumber"); + + final ConnectorObject object = connector.getObject(LdapSchema.ANY_OBJECT_CLASS, uid, oob.build()); + + assertNotNull(object); + assertNotNull(object.getAttributes()); + // Returned attributes: description, serialNumber, NAME and UID + assertEquals(4, object.getAttributes().size()); + assertNotNull(object.getAttributeByName("description")); + assertEquals( + Collections.singletonList("descriptionupdate"), + object.getAttributeByName("description").getValue()); + assertNotNull(object.getAttributeByName("serialNumber")); + assertEquals( + Collections.singletonList("serialnumberupdate"), + object.getAttributeByName("serialNumber").getValue()); + } + + @Test + public void rename() { + assertNotNull(connector); + assertNotNull(conf); + + final Map.Entry ids = util.getEntryIDs("5"); + + final String DN = "cn=renamed_" + ids.getKey() + ",cn=Computers," + BASE_CONTEXT; + + final List attrToReplace = Arrays.asList(new Attribute[] { + AttributeBuilder.build(Name.NAME, DN)}); + + Uid uid = connector.update( + LdapSchema.ANY_OBJECT_CLASS, new Uid(ids.getValue()), new HashSet<>(attrToReplace), null); + + assertNotNull(uid); + assertEquals(ids.getValue(), uid.getUidValue()); + + + // Ask just for description and serialNumber + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet("description", "serialNumber"); + + final ConnectorObject object = connector.getObject(LdapSchema.ANY_OBJECT_CLASS, uid, oob.build()); + + // Returned attributes: description, serialNumber, NAME and UID + assertEquals(4, object.getAttributes().size()); + assertNotNull(object.getAttributeByName("description")); + assertNotNull(object.getAttributeByName("serialNumber")); + assertTrue(DN.equalsIgnoreCase(object.getName().getNameValue())); + } + + @Test + public void noRenameWithTheSameCN() { + assertNotNull(connector); + assertNotNull(conf); + + final Map.Entry ids = util.getEntryIDs("6"); + + final List attrToReplace = Arrays.asList(new Attribute[] { + AttributeBuilder.build(Name.NAME, ids.getKey())}); + + Uid uid = connector.update( + LdapSchema.ANY_OBJECT_CLASS, new Uid(ids.getValue()), new HashSet<>(attrToReplace), null); + + assertNotNull(uid); + assertEquals(ids.getValue(), uid.getUidValue()); + + // Ask just for description and serialNumber + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet("description", "serialNumber"); + + final ConnectorObject object = connector.getObject(LdapSchema.ANY_OBJECT_CLASS, uid, oob.build()); + + // Returned attributes: description, serialNumber, NAME and UID + assertEquals(4, object.getAttributes().size()); + assertNotNull(object.getAttributeByName("description")); + assertNotNull(object.getAttributeByName("serialNumber")); + + assertTrue( + util.getEntryDN(ids.getKey(), LdapSchema.ANY_OBJECT_CLASS).equalsIgnoreCase(object.getName().getNameValue())); + } + + @Test + public void noRenameWithTheSameDN() { + assertNotNull(connector); + assertNotNull(conf); + + final Map.Entry ids = util.getEntryIDs("6"); + + final List attrToReplace = Arrays.asList(new Attribute[] { + AttributeBuilder.build(Name.NAME, util.getEntryDN(ids.getKey(), LdapSchema.ANY_OBJECT_CLASS)) }); + + Uid uid = connector.update( + LdapSchema.ANY_OBJECT_CLASS, new Uid(ids.getValue()), new HashSet<>(attrToReplace), null); + + assertNotNull(uid); + assertEquals(ids.getValue(), uid.getUidValue()); + + // Ask just for description and serialNumber + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet("description", "serialNumber"); + + final ConnectorObject object = connector.getObject(LdapSchema.ANY_OBJECT_CLASS, uid, oob.build()); + + // Returned attributes: description, serialNumber, NAME and UID + assertEquals(4, object.getAttributes().size()); + assertNotNull(object.getAttributeByName("description")); + assertNotNull(object.getAttributeByName("serialNumber")); + + assertTrue( + util.getEntryDN(ids.getKey(), LdapSchema.ANY_OBJECT_CLASS).equalsIgnoreCase(object.getName().getNameValue())); + } + + @Test + public void renameWithoutDN() { + assertNotNull(connector); + assertNotNull(conf); + + final Map.Entry ids = util.getEntryIDs("5"); + + final List attrToReplace = Arrays.asList(new Attribute[] { AttributeBuilder.build("cn", ids.getKey() + + "_new") }); + + Uid uid = connector.update( + LdapSchema.ANY_OBJECT_CLASS, new Uid(ids.getValue()), new HashSet<>(attrToReplace), null); + + assertNotNull(uid); + assertEquals(ids.getValue(), uid.getUidValue()); + + // Ask just for cn + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet("cn"); + + final ConnectorObject object = connector.getObject(LdapSchema.ANY_OBJECT_CLASS, uid, oob.build()); + + // Returned attributes: cn, NAME and UID + assertEquals(3, object.getAttributes().size()); + assertNotNull(object.getAttributeByName("cn")); + assertEquals(ids.getKey() + "_new", object.getAttributeByName("cn").getValue().get(0)); + } + + @Test + public void excludeAttributeChangesOnUpdate() { + final ADConfiguration newconf = getSimpleConf(PROP); + newconf.setExcludeAttributeChangesOnUpdate(true); + + final ConnectorFacadeFactory factory = ConnectorFacadeFactory.getInstance(); + final APIConfiguration impl = TestHelpers.createTestConfiguration(ADConnector.class, newconf); + // TODO: remove the line below when using ConnId >= 1.4.0.1 + ((APIConfigurationImpl) impl). + setConfigurationProperties(JavaClassProperties.createConfigurationProperties(newconf)); + + final ConnectorFacade newConnector = factory.newInstance(impl); + + final Map.Entry ids = util.getEntryIDs("9"); + + // 2. Update without pwd .... + List attrToReplace = Arrays.asList(new Attribute[] { + AttributeBuilder.build("serialNumber", "excludeAttributeChangesOnUpdate") }); + + Uid uid = newConnector.update( + LdapSchema.ANY_OBJECT_CLASS, + new Uid(ids.getValue()), + new HashSet<>(attrToReplace), + null); + assertEquals(ids.getValue(), uid.getUidValue()); + + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet("serialNumber"); + + ConnectorObject object = newConnector.getObject(LdapSchema.ANY_OBJECT_CLASS, uid, oob.build()); + + List serialNumber = object.getAttributeByName("serialNumber").getValue(); + assertTrue(serialNumber.size() == 1 && !serialNumber.contains("excludeAttributeChangesOnUpdate")); + + attrToReplace = Arrays.asList(new Attribute[] { AttributeBuilder.build("cn", ids.getKey() + "_new") }); + + // 0. rename should be denied + try { + newConnector.update( + LdapSchema.ANY_OBJECT_CLASS, new Uid(ids.getValue()), new HashSet<>(attrToReplace), null); + fail(); + } catch (Exception e) { + // ignore + } + } +} \ No newline at end of file diff --git a/src/test/java/net/tirasa/connid/bundles/ad/sync/SyncAnyObjectTestITCase.java b/src/test/java/net/tirasa/connid/bundles/ad/sync/SyncAnyObjectTestITCase.java new file mode 100644 index 0000000..3045f4b --- /dev/null +++ b/src/test/java/net/tirasa/connid/bundles/ad/sync/SyncAnyObjectTestITCase.java @@ -0,0 +1,232 @@ +/** + * Copyright (C) 2011 ConnId (connid-dev@googlegroups.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.tirasa.connid.bundles.ad.sync; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import javax.naming.ldap.LdapContext; +import net.tirasa.connid.bundles.ad.ADConfiguration; +import net.tirasa.connid.bundles.ad.ADConnection; +import net.tirasa.connid.bundles.ad.ADConnector; +import net.tirasa.connid.bundles.ad.AnyObjectTest; +import net.tirasa.connid.bundles.ad.util.DirSyncUtils; +import net.tirasa.connid.bundles.ldap.schema.LdapSchema; + +import org.identityconnectors.framework.api.APIConfiguration; +import org.identityconnectors.framework.api.ConnectorFacade; +import org.identityconnectors.framework.api.ConnectorFacadeFactory; +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.OperationOptionsBuilder; +import org.identityconnectors.framework.common.objects.SyncDelta; +import org.identityconnectors.framework.common.objects.SyncToken; +import org.identityconnectors.framework.common.objects.Uid; +import org.identityconnectors.framework.impl.api.APIConfigurationImpl; +import org.identityconnectors.framework.impl.api.local.JavaClassProperties; +import org.identityconnectors.test.common.TestHelpers; +import org.junit.jupiter.api.Test; + +public class SyncAnyObjectTestITCase extends AnyObjectTest { + + @Test + public void syncFromTheBeginningWithNullToken() { + // ---------------------------------- + // Handler specification + // ---------------------------------- + final TestSyncResultsHandler handler = new TestSyncResultsHandler(); + // ---------------------------------- + + // Ask just for description and serialNumber + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet(Arrays.asList(new String[] { + "description", "serialNumber" })); + + SyncToken previous = connector.sync(LdapSchema.ANY_OBJECT_CLASS, null, handler, oob.build()); + + assertNotNull(previous); + assertNotNull(previous.getValue()); + assertTrue(((byte[]) previous.getValue()).length > 0); + + Uid uid = connector.create(LdapSchema.ANY_OBJECT_CLASS, util.getSimpleProfile(util.getEntryIDs("123")), null); + connector.delete(LdapSchema.ANY_OBJECT_CLASS, uid, null); + assertNull(connector.getObject(LdapSchema.ANY_OBJECT_CLASS, uid, null)); + + SyncToken newly = connector.sync(LdapSchema.ANY_OBJECT_CLASS, previous, handler, oob.build()); + assertNotNull(newly); + assertNotNull(newly.getValue()); + assertTrue(((byte[]) newly.getValue()).length > 0); + + assertFalse(Arrays.equals((byte[]) previous.getValue(), (byte[]) newly.getValue())); + } + + @Test + public void sync() { + // We need to have several operation in the right sequence in order + // to verify synchronization ... + + // ---------------------------------- + // Handler specification + // ---------------------------------- + final TestSyncResultsHandler handler = new TestSyncResultsHandler(); + // ---------------------------------- + + // Ask just for description and serialNumber + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet(Arrays.asList(new String[] { + "description", "serialNumber" })); + + SyncToken token = connector.getLatestSyncToken(LdapSchema.ANY_OBJECT_CLASS); + connector.sync(LdapSchema.ANY_OBJECT_CLASS, token, handler, oob.build()); + + assertTrue(handler.getDeleted().isEmpty()); + assertTrue(handler.getUpdated().isEmpty()); + + handler.clear(); + + final Map.Entry ids11 = util.getEntryIDs("11"); + + Uid uid11 = null; + + try { + // ---------------------------------- + // check sync with new anyObject (token updated) + // ---------------------------------- + // anyObject added sync + uid11 = connector.create(LdapSchema.ANY_OBJECT_CLASS, util.getSimpleProfile(ids11), null); + + connector.sync(LdapSchema.ANY_OBJECT_CLASS, token, handler, oob.build()); + token = handler.getLatestReceivedToken(); + + assertTrue(handler.getDeleted().isEmpty()); + + // anyObject creation + assertFalse(handler.getUpdated().isEmpty()); + + for (SyncDelta usr : handler.getUpdated()) { + final ConnectorObject obj = usr.getObject(); + assertEquals(ids11.getValue(), obj.getUid().getUidValue()); + + // chek for returned attributes + assertNotNull(obj.getAttributeByName("description")); + assertNotNull(obj.getAttributeByName("serialNumber")); + assertNotNull(obj.getAttributeByName("__NAME__")); + assertNotNull(obj.getAttributeByName("__UID__")); + } + + handler.clear(); + + List attrToReplace = Arrays.asList(new Attribute[] { + AttributeBuilder.build("description", "descriptionupdate") + }); + + uid11 = connector.update(LdapSchema.ANY_OBJECT_CLASS, uid11, new HashSet<>(attrToReplace), null); + + connector.sync(LdapSchema.ANY_OBJECT_CLASS, token, handler, oob.build()); + token = handler.getLatestReceivedToken(); + + assertTrue(handler.getDeleted().isEmpty()); + assertEquals(1, handler.getUpdated().size()); + + handler.clear(); + + // check with updated token and without any modification + connector.sync(LdapSchema.ANY_OBJECT_CLASS, token, handler, oob.build()); + + assertTrue(handler.getDeleted().isEmpty()); + assertTrue(handler.getUpdated().isEmpty()); + // ---------------------------------- + } finally { + if (uid11 != null) { + // user delete sync + conf.setRetrieveDeletedAnyObject(true); + + final ConnectorFacadeFactory factory = ConnectorFacadeFactory.getInstance(); + final APIConfiguration impl = TestHelpers.createTestConfiguration(ADConnector.class, conf); + // TODO: remove the line below when using ConnId >= 1.4.0.1 + ((APIConfigurationImpl) impl). + setConfigurationProperties(JavaClassProperties.createConfigurationProperties(conf)); + + final ConnectorFacade newConnector = factory.newInstance(impl); + + token = newConnector.getLatestSyncToken(LdapSchema.ANY_OBJECT_CLASS); + + newConnector.delete(LdapSchema.ANY_OBJECT_CLASS, uid11, null); + + handler.clear(); + + newConnector.sync(LdapSchema.ANY_OBJECT_CLASS, token, handler, oob.build()); + + assertFalse(handler.getDeleted().isEmpty()); + assertEquals(1, handler.getDeleted().size()); + assertTrue(handler.getDeleted().get(0).getUid().getUidValue(). + startsWith(util.getEntryIDs("1").getValue())); + } + } + } + + @Test + public void verifyObjectGUID() { + // Ask just for objectGUID + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet(Collections.singleton("objectGUID")); + + final ConnectorObject object = connector.getObject(LdapSchema.ANY_OBJECT_CLASS, + new Uid(util.getEntryIDs("4").getValue()), oob.build()); + + assertNotNull(object); + + final Attribute objectGUID = object.getAttributeByName("objectGUID"); + assertNotNull(objectGUID); + assertNotNull(objectGUID.getValue()); + assertEquals(1, objectGUID.getValue().size()); + + assertTrue(objectGUID.getValue().get(0) instanceof String); + assertFalse(String.class.cast(objectGUID.getValue().get(0)).isEmpty()); + + if (LOG.isOk()) { + LOG.ok("ObjectGUID (String): {0}", objectGUID.getValue().get(0)); + } + } + + @Test + public void verifyFilter() { + // instatiate a new configuration to avoid collisions with sync test + final ADConfiguration configuration = getSimpleConf(PROP); + + final String DN = "CN=" + util.getEntryIDs("5").getKey() + "," + configuration.getAnyObjectBaseContexts()[0]; + + final ADConnection connection = new ADConnection(configuration); + final LdapContext ctx = connection.getInitialContext(); + + assertTrue(DirSyncUtils.verifyCustomFilter(ctx, DN, configuration)); + + configuration.setAccountSearchFilter("(&(Objectclass=device)(cn=" + util.getEntryIDs("5").getKey() + "))"); + assertTrue(DirSyncUtils.verifyCustomFilter(ctx, DN, configuration)); + + configuration.setAccountSearchFilter("(&(Objectclass=device)(cn=" + util.getEntryIDs("6").getKey() + "))"); + assertFalse(DirSyncUtils.verifyCustomFilter(ctx, DN, configuration)); + } +}