From 61c35265a65621bc331e3d37cee2d0b471568a9f Mon Sep 17 00:00:00 2001 From: mposolda Date: Thu, 7 May 2015 16:56:47 +0200 Subject: [PATCH] KEYCLOAK-826 Show count of created/updated users during federation sync --- .../BasePropertiesFederationFactory.java | 16 +- .../KerberosFederationProviderFactory.java | 7 +- .../ldap/LDAPFederationProvider.java | 13 +- .../ldap/LDAPFederationProviderFactory.java | 54 +++-- .../keycloak/federation/ldap/LDAPUtils.java | 4 +- .../ldap/idm/query/IdentityQuery.java | 225 ------------------ .../ldap/idm/query/IdentityQueryBuilder.java | 124 ---------- ...tIdentityQuery.java => IdentityQuery.java} | 54 +---- ...Builder.java => IdentityQueryBuilder.java} | 30 +-- .../ldap/idm/query/internal/OrCondition.java | 27 +++ .../ldap/idm/store/IdentityStore.java | 2 +- .../idm/store/ldap/LDAPIdentityStore.java | 171 +++++++------ .../idm/store/ldap/LDAPOperationManager.java | 41 +--- .../admin/resources/js/controllers/users.js | 4 +- .../models/UserFederationProviderFactory.java | 11 +- .../models/UserFederationSyncResult.java | 66 +++++ .../services/managers/UsersSyncManager.java | 9 +- .../admin/UserFederationResource.java | 13 +- .../DummyUserFederationProviderFactory.java | 7 +- .../federation/SyncProvidersTest.java | 15 +- 20 files changed, 312 insertions(+), 581 deletions(-) delete mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQuery.java delete mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQueryBuilder.java rename federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/{DefaultIdentityQuery.java => IdentityQuery.java} (75%) rename federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/{DefaultQueryBuilder.java => IdentityQueryBuilder.java} (79%) create mode 100644 federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/OrCondition.java create mode 100644 model/api/src/main/java/org/keycloak/models/UserFederationSyncResult.java diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java index 24f30b47c3..5c585f3d3d 100755 --- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java +++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java @@ -8,6 +8,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderFactory; import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.models.UserFederationSyncResult; import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; import org.keycloak.models.utils.KeycloakModelUtils; @@ -98,7 +99,9 @@ public abstract class BasePropertiesFederationFactory implements UserFederationP } @Override - public void syncAllUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) { + public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) { + final UserFederationSyncResult syncResult = new UserFederationSyncResult(); + KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() { @Override @@ -112,16 +115,21 @@ public abstract class BasePropertiesFederationFactory implements UserFederationP if (localUser == null) { // New user, let's import him - federationProvider.getUserByUsername(realm, username); + UserModel imported = federationProvider.getUserByUsername(realm, username); + if (imported != null) { + syncResult.increaseAdded(); + } } } } }); + + return syncResult; } @Override - public void syncChangedUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model, Date lastSync) { - syncAllUsers(sessionFactory, realmId, model); + public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model, Date lastSync) { + return syncAllUsers(sessionFactory, realmId, model); } } diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java index 375f2cf677..b7b6c92594 100755 --- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java +++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java @@ -14,6 +14,7 @@ import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderFactory; import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.models.UserFederationSyncResult; /** * Factory for standalone Kerberos federation provider. Standalone means that it's not backed by LDAP. For Kerberos backed by LDAP (like MS AD or ApacheDS environment) @@ -41,13 +42,15 @@ public class KerberosFederationProviderFactory implements UserFederationProvider } @Override - public void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) { + public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) { logger.warn("Sync users not supported for this provider"); + return UserFederationSyncResult.empty(); } @Override - public void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) { + public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) { logger.warn("Sync users not supported for this provider"); + return UserFederationSyncResult.empty(); } @Override diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java index 370e0f07f8..b0f3d0a97b 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java @@ -4,8 +4,8 @@ import org.jboss.logging.Logger; import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator; import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator; import org.keycloak.federation.ldap.idm.model.LDAPUser; -import org.keycloak.federation.ldap.idm.query.IdentityQuery; -import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder; +import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery; +import org.keycloak.federation.ldap.idm.query.internal.IdentityQueryBuilder; import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore; import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig; import org.keycloak.models.CredentialValidationOutput; @@ -18,6 +18,7 @@ import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.models.UserFederationSyncResult; import org.keycloak.models.UserModel; import org.keycloak.constants.KerberosConstants; @@ -334,7 +335,9 @@ public class LDAPFederationProvider implements UserFederationProvider { public void close() { } - protected void importLDAPUsers(RealmModel realm, List ldapUsers, UserFederationProviderModel fedModel) { + protected UserFederationSyncResult importLDAPUsers(RealmModel realm, List ldapUsers, UserFederationProviderModel fedModel) { + UserFederationSyncResult syncResult = new UserFederationSyncResult(); + for (LDAPUser ldapUser : ldapUsers) { String username = ldapUser.getLoginName(); UserModel currentUser = session.userStorage().getUserByUsername(username, realm); @@ -342,6 +345,7 @@ public class LDAPFederationProvider implements UserFederationProvider { if (currentUser == null) { // Add new user to Keycloak importUserFromLDAP(realm, ldapUser); + syncResult.increaseAdded(); } else { if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getId().equals(currentUser.getAttribute(LDAPConstants.LDAP_ID)))) { // Update keycloak user @@ -350,11 +354,14 @@ public class LDAPFederationProvider implements UserFederationProvider { currentUser.setFirstName(ldapUser.getFirstName()); currentUser.setLastName(ldapUser.getLastName()); logger.debugf("Updated user from LDAP: %s", currentUser.getUsername()); + syncResult.increaseUpdated(); } else { logger.warnf("User '%s' is not updated during sync as he is not linked to federation provider '%s'", username, fedModel.getDisplayName()); } } } + + return syncResult; } /** diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java index a498a93b45..7b4048957e 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java @@ -9,8 +9,8 @@ import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator; import org.keycloak.federation.ldap.idm.model.IdentityType; import org.keycloak.federation.ldap.idm.model.LDAPUser; import org.keycloak.federation.ldap.idm.query.Condition; -import org.keycloak.federation.ldap.idm.query.IdentityQuery; -import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder; +import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery; +import org.keycloak.federation.ldap.idm.query.internal.IdentityQueryBuilder; import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; @@ -20,6 +20,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderFactory; import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.models.UserFederationSyncResult; import org.keycloak.models.utils.KeycloakModelUtils; import java.util.Collections; @@ -75,40 +76,47 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact } @Override - public void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) { - logger.infof("Sync all users from LDAP to local store: realm: %s, federation provider: %s, current time: " + new Date(), realmId, model.getDisplayName()); + public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) { + logger.infof("Sync all users from LDAP to local store: realm: %s, federation provider: %s", realmId, model.getDisplayName()); LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model); IdentityQuery userQuery = ldapIdentityStore.createQueryBuilder().createIdentityQuery(LDAPUser.class); - syncImpl(sessionFactory, userQuery, realmId, model); + UserFederationSyncResult syncResult = syncImpl(sessionFactory, userQuery, realmId, model); // TODO: Remove all existing keycloak users, which have federation links, but are not in LDAP. Perhaps don't check users, which were just added or updated during this sync? + + logger.infof("Sync all users finished: %s", syncResult.getStatus()); + return syncResult; } @Override - public void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) { - logger.infof("Sync changed users from LDAP to local store: realm: %s, federation provider: %s, current time: %s, last sync time: " + lastSync, realmId, model.getDisplayName(), new Date().toString()); + public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) { + logger.infof("Sync changed users from LDAP to local store: realm: %s, federation provider: %s, last sync time: " + lastSync, realmId, model.getDisplayName()); LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model); - // Sync newly created users + // Sync newly created and updated users IdentityQueryBuilder queryBuilder = ldapIdentityStore.createQueryBuilder(); - Condition condition = queryBuilder.greaterThanOrEqualTo(IdentityType.CREATED_DATE, lastSync); - IdentityQuery userQuery = queryBuilder.createIdentityQuery(LDAPUser.class).where(condition); - syncImpl(sessionFactory, userQuery, realmId, model); + Condition createCondition = queryBuilder.greaterThanOrEqualTo(IdentityType.CREATED_DATE, lastSync); + Condition modifyCondition = queryBuilder.greaterThanOrEqualTo(LDAPUtils.MODIFY_DATE, lastSync); + Condition orCondition = queryBuilder.orCondition(createCondition, modifyCondition); + IdentityQuery userQuery = queryBuilder.createIdentityQuery(LDAPUser.class).where(orCondition); + UserFederationSyncResult result = syncImpl(sessionFactory, userQuery, realmId, model); - // Sync updated users - condition = queryBuilder.greaterThanOrEqualTo(LDAPUtils.MODIFY_DATE, lastSync); - userQuery = queryBuilder.createIdentityQuery(LDAPUser.class).where(condition); - syncImpl(sessionFactory, userQuery, realmId, model); + logger.infof("Sync changed users finished: %s", result.getStatus()); + return result; } - protected void syncImpl(KeycloakSessionFactory sessionFactory, IdentityQuery userQuery, final String realmId, final UserFederationProviderModel fedModel) { - boolean pagination = Boolean.parseBoolean(fedModel.getConfig().get(LDAPConstants.PAGINATION)); + protected UserFederationSyncResult syncImpl(KeycloakSessionFactory sessionFactory, IdentityQuery userQuery, final String realmId, final UserFederationProviderModel fedModel) { + final UserFederationSyncResult syncResult = new UserFederationSyncResult(); + + boolean pagination = Boolean.parseBoolean(fedModel.getConfig().get(LDAPConstants.PAGINATION)); if (pagination) { + String pageSizeConfig = fedModel.getConfig().get(LDAPConstants.BATCH_SIZE_FOR_SYNC); int pageSize = pageSizeConfig!=null ? Integer.parseInt(pageSizeConfig) : LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC; + boolean nextPage = true; while (nextPage) { userQuery.setLimit(pageSize); @@ -119,7 +127,8 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact @Override public void run(KeycloakSession session) { - importLdapUsers(session, realmId, fedModel, users); + UserFederationSyncResult currentPageSync = importLdapUsers(session, realmId, fedModel, users); + syncResult.add(currentPageSync); } }); @@ -131,17 +140,20 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact @Override public void run(KeycloakSession session) { - importLdapUsers(session, realmId, fedModel, users); + UserFederationSyncResult currentSync = importLdapUsers(session, realmId, fedModel, users); + syncResult.add(currentSync); } }); } + + return syncResult; } - protected void importLdapUsers(KeycloakSession session, String realmId, UserFederationProviderModel fedModel, List ldapUsers) { + protected UserFederationSyncResult importLdapUsers(KeycloakSession session, String realmId, UserFederationProviderModel fedModel, List ldapUsers) { RealmModel realm = session.realms().getRealm(realmId); LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel); - ldapFedProvider.importLDAPUsers(realm, ldapUsers, fedModel); + return ldapFedProvider.importLDAPUsers(realm, ldapUsers, fedModel); } protected SPNEGOAuthenticator createSPNEGOAuthenticator(String spnegoToken, CommonKerberosConfig kerberosConfig) { diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java index 97535926c9..74eb9f298e 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java @@ -3,9 +3,9 @@ package org.keycloak.federation.ldap; import org.keycloak.federation.ldap.idm.model.Attribute; import org.keycloak.federation.ldap.idm.model.LDAPUser; import org.keycloak.federation.ldap.idm.query.AttributeParameter; -import org.keycloak.federation.ldap.idm.query.IdentityQuery; -import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder; import org.keycloak.federation.ldap.idm.query.QueryParameter; +import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery; +import org.keycloak.federation.ldap.idm.query.internal.IdentityQueryBuilder; import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelDuplicateException; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQuery.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQuery.java deleted file mode 100644 index 1a77727fa8..0000000000 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQuery.java +++ /dev/null @@ -1,225 +0,0 @@ -package org.keycloak.federation.ldap.idm.query; - -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.keycloak.federation.ldap.idm.model.IdentityType; - -/** - *

An {@link IdentityQuery} is responsible for querying the underlying identity stores for instances of - * a given {@link IdentityType}.

- * - *

Instances of this class are obtained using the {@link IdentityQueryBuilder#createIdentityQuery(Class)} - * method.

- * - *
- *      IdentityManager identityManager = getIdentityManager();
- *
- *      // here we get the query builder
- *      IdentityQueryBuilder builder = identityManager.getQueryBuilder();
- *
- *      // create a condition
- *      Condition condition = builder.equal(User.LOGIN_NAME, "john");
- *
- *      // create a query for a specific identity type using the previously created condition
- *      IdentityQuery query = builder.createIdentityQuery(User.class).where(condition);
- *
- *      // execute the query
- *      List result = query.getResultList();
- * 
- * - *

When preparing a query you may want to create conditions to filter its results and configure how they must be retrieved. - * For that, you can use the {@link IdentityQueryBuilder}, which provides useful methods for creating - * different expressions and conditions.

- * - * @author Shane Bryzak - * @author Pedro Igor - */ -public interface IdentityQuery { - - /** - * @see #setPaginationContext(Object object) - */ - Object getPaginationContext(); - - /** - * Used for pagination models like LDAP when search will return some object (like cookie) for searching on next page - * - * @param object to be used for search next page - * - * @return this query - */ - IdentityQuery setPaginationContext(Object object); - - /** - * @deprecated Will be removed soon. - * - * @see #setSortParameters(QueryParameter...) - */ - @Deprecated - QueryParameter[] getSortParameters(); - - /** - * Parameters used to sort the results. First parameter has biggest priority. For example: setSortParameter(User.LAST_NAME, - * User.FIRST_NAME) means that results will be sorted primarily by lastName and firstName will be used to sort only records with - * same lastName - * - * @param sortParameters parameters to specify sort criteria - * - * @deprecated Use {@link IdentityQuery#sortBy(Sort...)} instead. Where you can create sort conditions - * from the {@link IdentityQueryBuilder}. - * - * @return this query - */ - @Deprecated - IdentityQuery setSortParameters(QueryParameter... sortParameters); - - /** - * @deprecated Use {@link IdentityQuery#getSorting()} for a list of sorting conditions. Will be removed soon. - * - * @return true if sorting will be ascending - * - * @see #setSortAscending(boolean) - */ - @Deprecated - boolean isSortAscending(); - - /** - * Specify if sorting will be ascending (true) or descending (false) - * - * @param sortAscending to specify if sorting will be ascending or descending - * - * @deprecated Use {@link IdentityQuery#sortBy(Sort...)} instead. Where you can create sort conditions - * from the {@link IdentityQueryBuilder}. - * - * @return this query - */ - @Deprecated - IdentityQuery setSortAscending(boolean sortAscending); - - /** - *

Set a query parameter to this query in order to filter the results.

- * - *

This method always create an equality condition. For more conditions options take a look at {@link - * IdentityQueryBuilder} and use the {@link IdentityQuery#where(Condition...)} - * instead.

- * - * @param param The query parameter. - * @param value The value to match for equality. - * - * @return - * - * @deprecated Use {@link IdentityQuery#where(Condition...)} to specify query conditions. - */ - @Deprecated - IdentityQuery setParameter(QueryParameter param, Object... value); - - /** - *

Add to this query the conditions that will be used to filter results.

- * - *

Any condition previously added to this query will be preserved and the new conditions added. If you want to clear the - * conditions you must create a new query instance.

- * - * @param condition One or more conditions created from {@link IdentityQueryBuilder}. - * - * @return - */ - IdentityQuery where(Condition... condition); - - /** - *

Add to this query the sorting conditions to be applied to the results.

- * - * @param sorts The ordering conditions. - * - * @return - */ - IdentityQuery sortBy(Sort... sorts); - - /** - *

The type used to create this query.

- * - * @return - */ - Class getIdentityType(); - - /** - *

Returns a map with all the parameter set for this query.

- * - * @return - * - * @deprecated Use {@link IdentityQuery#getConditions()} instead. Will be removed. - */ - @Deprecated - Map getParameters(); - - /** - *

Returns a set containing all conditions used by this query to filter its results.

- * - * @return - */ - Set getConditions(); - - /** - *

Returns a set containing all sorting conditions used to filter the results.

- * - * @return - */ - Set getSorting(); - - /** - *

Returns the value used to restrict the given query parameter.

- * - * @param queryParameter - * - * @return - */ - @Deprecated - Object[] getParameter(QueryParameter queryParameter); - - @Deprecated - Map getParameters(Class type); - - int getOffset(); - - /** - *

Set the position of the first result to retrieve.

- * - * @param offset - * - * @return - */ - IdentityQuery setOffset(int offset); - - /** - *

Returns the number of instances to retrieve.

- * - * @return - */ - int getLimit(); - - /** - *

Set the maximum number of results to retrieve.

- * - * @param limit the number of instances to retrieve. - * - * @return - */ - IdentityQuery setLimit(int limit); - - /** - *

Execute the query against the underlying identity stores and returns a list containing all instances of - * the type (defined when creating this query instance) that match the conditions previously specified.

- * - * @return - */ - List getResultList(); - - /** - * Count of all query results. It takes into account query parameters, but it doesn't take into account pagination parameter - * like offset and limit - * - * @return count of all query results - */ - int getResultCount(); -} diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQueryBuilder.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQueryBuilder.java deleted file mode 100644 index 022063521b..0000000000 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQueryBuilder.java +++ /dev/null @@ -1,124 +0,0 @@ -package org.keycloak.federation.ldap.idm.query; - -import org.keycloak.federation.ldap.idm.model.IdentityType; - -/** - *

The {@link IdentityQueryBuilder} is responsible for creating {@link IdentityQuery} instances and also - * provide methods to create conditions, orderings, sorting, etc.

- * - * @author Pedro Igor - */ -public interface IdentityQueryBuilder { - - /** - *

Create a condition for testing the whether the query parameter satisfies the given pattern..

- * - * @param parameter The query parameter. - * @param pattern The pattern to match. - * - * @return - */ - Condition like(QueryParameter parameter, String pattern); - - /** - *

Create a condition for testing the arguments for equality.

- * - * @param parameter The query parameter. - * @param value The value to compare. - * - * @return - */ - Condition equal(QueryParameter parameter, Object value); - - /** - *

Create a condition for testing whether the query parameter is grater than the given value..

- * - * @param parameter The query parameter. - * @param x The value to compare. - * - * @return - */ - Condition greaterThan(QueryParameter parameter, Object x); - - /** - *

Create a condition for testing whether the query parameter is grater than or equal to the given value..

- * - * @param parameter The query parameter. - * @param x The value to compare. - * - * @return - */ - Condition greaterThanOrEqualTo(QueryParameter parameter, Object x); - - /** - *

Create a condition for testing whether the query parameter is less than the given value..

- * - * @param parameter The query parameter. - * @param x The value to compare. - * - * @return - */ - Condition lessThan(QueryParameter parameter, Object x); - - /** - *

Create a condition for testing whether the query parameter is less than or equal to the given value..

- * - * @param parameter The query parameter. - * @param x The value to compare. - * - * @return - */ - Condition lessThanOrEqualTo(QueryParameter parameter, Object x); - - /** - *

Create a condition for testing whether the query parameter is between the given values.

- * - * @param parameter The query parameter. - * @param x The first value. - * @param x The second value. - * - * @return - */ - Condition between(QueryParameter parameter, Object x, Object y); - - /** - *

Create a condition for testing whether the query parameter is contained in a list of values.

- * - * @param parameter The query parameter. - * @param values A list of values. - * - * @return - */ - Condition in(QueryParameter parameter, Object... values); - - /** - *

Create an ascending order for the given parameter. Once created, you can use it to sort the results of a - * query.

- * - * @param parameter The query parameter to sort. - * - * @return - */ - Sort asc(QueryParameter parameter); - - /** - *

Create an descending order for the given parameter. Once created, you can use it to sort the results of a - * query.

- * - * @param parameter The query parameter to sort. - * - * @return - */ - Sort desc(QueryParameter parameter); - - /** - *

Create an {@link IdentityQuery} that can be used to query for {@link - * IdentityType} instances of a the given identityType.

- * - * @param identityType The type to search. If you provide the {@link IdentityType} - * base interface any of its sub-types will be returned. - * - * @return - */ - IdentityQuery createIdentityQuery(Class identityType); -} diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/DefaultIdentityQuery.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/IdentityQuery.java similarity index 75% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/DefaultIdentityQuery.java rename to federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/IdentityQuery.java index 21b02537da..44fb0fcc1e 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/DefaultIdentityQuery.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/IdentityQuery.java @@ -2,7 +2,6 @@ package org.keycloak.federation.ldap.idm.query.internal; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -11,11 +10,9 @@ import java.util.Set; import org.keycloak.federation.ldap.idm.model.IdentityType; import org.keycloak.federation.ldap.idm.query.Condition; -import org.keycloak.federation.ldap.idm.query.IdentityQuery; -import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder; import org.keycloak.federation.ldap.idm.query.QueryParameter; import org.keycloak.federation.ldap.idm.query.Sort; -import org.keycloak.federation.ldap.idm.store.IdentityStore; +import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore; import org.keycloak.models.ModelException; import static java.util.Collections.unmodifiableSet; @@ -27,11 +24,11 @@ import static java.util.Collections.unmodifiableSet; * * @author Shane Bryzak */ -public class DefaultIdentityQuery implements IdentityQuery { +public class IdentityQuery { private final Map parameters = new LinkedHashMap(); private final Class identityType; - private final IdentityStore identityStore; + private final LDAPIdentityStore identityStore; private final IdentityQueryBuilder queryBuilder; private int offset; private int limit; @@ -41,13 +38,12 @@ public class DefaultIdentityQuery implements IdentityQue private final Set conditions = new LinkedHashSet(); private final Set ordering = new LinkedHashSet(); - public DefaultIdentityQuery(IdentityQueryBuilder queryBuilder, Class identityType, IdentityStore identityStore) { + public IdentityQuery(IdentityQueryBuilder queryBuilder, Class identityType, LDAPIdentityStore identityStore) { this.queryBuilder = queryBuilder; this.identityStore = identityStore; this.identityType = identityType; } - @Override public IdentityQuery setParameter(QueryParameter queryParameter, Object... value) { if (value == null || value.length == 0) { throw new ModelException("Query Parameter values null or empty"); @@ -66,79 +62,44 @@ public class DefaultIdentityQuery implements IdentityQue return this; } - @Override public IdentityQuery where(Condition... condition) { this.conditions.addAll(Arrays.asList(condition)); return this; } - @Override public IdentityQuery sortBy(Sort... sorts) { this.ordering.addAll(Arrays.asList(sorts)); return this; } - @Override public Set getSorting() { return unmodifiableSet(this.ordering); } - @Override public Class getIdentityType() { return identityType; } - @Override - public Map getParameters() { - return parameters; - } - - @Override - public Object[] getParameter(QueryParameter queryParameter) { - return this.parameters.get(queryParameter); - } - - @Override - public Map getParameters(Class type) { - Map typedParameters = new HashMap(); - - Set> entrySet = this.parameters.entrySet(); - - for (Map.Entry entry : entrySet) { - if (type.isInstance(entry.getKey())) { - typedParameters.put(entry.getKey(), entry.getValue()); - } - } - - return typedParameters; - } - - @Override public int getLimit() { return limit; } - @Override public int getOffset() { return offset; } - @Override public Object getPaginationContext() { return paginationContext; } - @Override public QueryParameter[] getSortParameters() { return sortParameters; } - @Override public boolean isSortAscending() { return sortAscending; } - @Override public List getResultList() { // remove this statement once deprecated methods on IdentityQuery are removed @@ -165,42 +126,35 @@ public class DefaultIdentityQuery implements IdentityQue return result; } - @Override public int getResultCount() { return identityStore.countQueryResults(this); } - @Override public IdentityQuery setOffset(int offset) { this.offset = offset; return this; } - @Override public IdentityQuery setLimit(int limit) { this.limit = limit; return this; } - @Override public IdentityQuery setSortParameters(QueryParameter... sortParameters) { this.sortParameters = sortParameters; return this; } - @Override public IdentityQuery setSortAscending(boolean sortAscending) { this.sortAscending = sortAscending; return this; } - @Override public IdentityQuery setPaginationContext(Object object) { this.paginationContext = object; return this; } - @Override public Set getConditions() { return unmodifiableSet(this.conditions); } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/DefaultQueryBuilder.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/IdentityQueryBuilder.java similarity index 79% rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/DefaultQueryBuilder.java rename to federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/IdentityQueryBuilder.java index 5d3b72e165..adc3be084f 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/DefaultQueryBuilder.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/IdentityQueryBuilder.java @@ -2,83 +2,77 @@ package org.keycloak.federation.ldap.idm.query.internal; import org.keycloak.federation.ldap.idm.model.IdentityType; import org.keycloak.federation.ldap.idm.query.Condition; -import org.keycloak.federation.ldap.idm.query.IdentityQuery; -import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder; import org.keycloak.federation.ldap.idm.query.QueryParameter; import org.keycloak.federation.ldap.idm.query.Sort; -import org.keycloak.federation.ldap.idm.store.IdentityStore; +import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore; import org.keycloak.models.ModelException; /** * @author Pedro Igor */ -public class DefaultQueryBuilder implements IdentityQueryBuilder { +public class IdentityQueryBuilder { - private final IdentityStore identityStore; + private final LDAPIdentityStore identityStore; - public DefaultQueryBuilder(IdentityStore identityStore) { + public IdentityQueryBuilder(LDAPIdentityStore identityStore) { this.identityStore = identityStore; } - @Override public Condition like(QueryParameter parameter, String pattern) { return new LikeCondition(parameter, pattern); } - @Override public Condition equal(QueryParameter parameter, Object value) { return new EqualCondition(parameter, value); } - @Override public Condition greaterThan(QueryParameter parameter, Object x) { throwExceptionIfNotComparable(x); return new GreaterThanCondition(parameter, (Comparable) x, false); } - @Override public Condition greaterThanOrEqualTo(QueryParameter parameter, Object x) { throwExceptionIfNotComparable(x); return new GreaterThanCondition(parameter, (Comparable) x, true); } - @Override public Condition lessThan(QueryParameter parameter, Object x) { throwExceptionIfNotComparable(x); return new LessThanCondition(parameter, (Comparable) x, false); } - @Override public Condition lessThanOrEqualTo(QueryParameter parameter, Object x) { throwExceptionIfNotComparable(x); return new LessThanCondition(parameter, (Comparable) x, true); } - @Override public Condition between(QueryParameter parameter, Object x, Object y) { throwExceptionIfNotComparable(x); throwExceptionIfNotComparable(y); return new BetweenCondition(parameter, (Comparable) x, (Comparable) y); } - @Override + public Condition orCondition(Condition... conditions) { + if (conditions == null || conditions.length == 0) { + throw new ModelException("At least one condition should be provided to OR query"); + } + return new OrCondition(conditions); + } + public Condition in(QueryParameter parameter, Object... x) { return new InCondition(parameter, x); } - @Override public Sort asc(QueryParameter parameter) { return new Sort(parameter, true); } - @Override public Sort desc(QueryParameter parameter) { return new Sort(parameter, false); } - @Override public IdentityQuery createIdentityQuery(Class identityType) { - return new DefaultIdentityQuery(this, identityType, this.identityStore); + return new IdentityQuery(this, identityType, this.identityStore); } private void throwExceptionIfNotComparable(Object x) { diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/OrCondition.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/OrCondition.java new file mode 100644 index 0000000000..436355b27f --- /dev/null +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/OrCondition.java @@ -0,0 +1,27 @@ +package org.keycloak.federation.ldap.idm.query.internal; + +import java.util.List; + +import org.keycloak.federation.ldap.idm.query.Condition; +import org.keycloak.federation.ldap.idm.query.QueryParameter; + +/** + * @author Marek Posolda + */ +public class OrCondition implements Condition { + + private final Condition[] innerConditions; + + public OrCondition(Condition... innerConditions) { + this.innerConditions = innerConditions; + } + + public Condition[] getInnerConditions() { + return innerConditions; + } + + @Override + public QueryParameter getParameter() { + return null; + } +} diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java index 7fef705c86..447a6da0b5 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java @@ -5,7 +5,7 @@ import java.util.List; import org.keycloak.federation.ldap.idm.model.AttributedType; import org.keycloak.federation.ldap.idm.model.IdentityType; import org.keycloak.federation.ldap.idm.model.LDAPUser; -import org.keycloak.federation.ldap.idm.query.IdentityQuery; +import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery; import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStoreConfiguration; /** diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java index 8b912406ac..5e4c181998 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java @@ -23,16 +23,16 @@ import org.keycloak.federation.ldap.idm.model.IdentityType; import org.keycloak.federation.ldap.idm.model.LDAPUser; import org.keycloak.federation.ldap.idm.query.AttributeParameter; import org.keycloak.federation.ldap.idm.query.Condition; -import org.keycloak.federation.ldap.idm.query.IdentityQuery; -import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder; import org.keycloak.federation.ldap.idm.query.QueryParameter; import org.keycloak.federation.ldap.idm.query.internal.BetweenCondition; -import org.keycloak.federation.ldap.idm.query.internal.DefaultQueryBuilder; +import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery; +import org.keycloak.federation.ldap.idm.query.internal.IdentityQueryBuilder; import org.keycloak.federation.ldap.idm.query.internal.EqualCondition; import org.keycloak.federation.ldap.idm.query.internal.GreaterThanCondition; import org.keycloak.federation.ldap.idm.query.internal.InCondition; import org.keycloak.federation.ldap.idm.query.internal.LessThanCondition; import org.keycloak.federation.ldap.idm.query.internal.LikeCondition; +import org.keycloak.federation.ldap.idm.query.internal.OrCondition; import org.keycloak.federation.ldap.idm.store.IdentityStore; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelDuplicateException; @@ -183,7 +183,7 @@ public class LDAPIdentityStore implements IdentityStore { } public IdentityQueryBuilder createQueryBuilder() { - return new DefaultQueryBuilder(this); + return new IdentityQueryBuilder(this); } // *************** CREDENTIALS AND USER SPECIFIC STUFF @@ -310,94 +310,111 @@ public class LDAPIdentityStore implements IdentityStore { StringBuilder filter = new StringBuilder(); for (Condition condition : identityQuery.getConditions()) { - QueryParameter queryParameter = condition.getParameter(); + applyCondition(filter, condition, ldapEntryConfig); + } - if (!IdentityType.ID.equals(queryParameter)) { - if (AttributeParameter.class.isInstance(queryParameter)) { - AttributeParameter attributeParameter = (AttributeParameter) queryParameter; - String attributeName = ldapEntryConfig.getMappedProperties().get(attributeParameter.getName()); - if (attributeName != null) { - if (EqualCondition.class.isInstance(condition)) { - EqualCondition equalCondition = (EqualCondition) condition; - Object parameterValue = equalCondition.getValue(); + filter.insert(0, "(&"); + filter.append(getObjectClassesFilter(ldapEntryConfig)); + filter.append(")"); - if (Date.class.isInstance(parameterValue)) { - parameterValue = LDAPUtil.formatDate((Date) parameterValue); - } + logger.infof("Using filter for LDAP search: %s", filter); + return filter; + } - filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(parameterValue).append(")"); - } else if (LikeCondition.class.isInstance(condition)) { - LikeCondition likeCondition = (LikeCondition) condition; - String parameterValue = (String) likeCondition.getValue(); + protected void applyCondition(StringBuilder filter, Condition condition, LDAPMappingConfiguration ldapEntryConfig) { + if (OrCondition.class.isInstance(condition)) { + OrCondition orCondition = (OrCondition) condition; + filter.append("(|"); - } else if (GreaterThanCondition.class.isInstance(condition)) { - GreaterThanCondition greaterThanCondition = (GreaterThanCondition) condition; - Comparable parameterValue = (Comparable) greaterThanCondition.getValue(); + for (Condition innerCondition : orCondition.getInnerConditions()) { + applyCondition(filter, innerCondition, ldapEntryConfig); + } - if (Date.class.isInstance(parameterValue)) { - parameterValue = LDAPUtil.formatDate((Date) parameterValue); - } + filter.append(")"); + return; + } - if (greaterThanCondition.isOrEqual()) { - filter.append("(").append(attributeName).append(">=").append(parameterValue).append(")"); - } else { - filter.append("(").append(attributeName).append(">").append(parameterValue).append(")"); - } - } else if (LessThanCondition.class.isInstance(condition)) { - LessThanCondition lessThanCondition = (LessThanCondition) condition; - Comparable parameterValue = (Comparable) lessThanCondition.getValue(); + QueryParameter queryParameter = condition.getParameter(); - if (Date.class.isInstance(parameterValue)) { - parameterValue = LDAPUtil.formatDate((Date) parameterValue); - } + if (!IdentityType.ID.equals(queryParameter)) { + if (AttributeParameter.class.isInstance(queryParameter)) { + AttributeParameter attributeParameter = (AttributeParameter) queryParameter; + String attributeName = ldapEntryConfig.getMappedProperties().get(attributeParameter.getName()); - if (lessThanCondition.isOrEqual()) { - filter.append("(").append(attributeName).append("<=").append(parameterValue).append(")"); - } else { - filter.append("(").append(attributeName).append("<").append(parameterValue).append(")"); - } - } else if (BetweenCondition.class.isInstance(condition)) { - BetweenCondition betweenCondition = (BetweenCondition) condition; - Comparable x = betweenCondition.getX(); - Comparable y = betweenCondition.getY(); + if (attributeName != null) { + if (EqualCondition.class.isInstance(condition)) { + EqualCondition equalCondition = (EqualCondition) condition; + Object parameterValue = equalCondition.getValue(); - if (Date.class.isInstance(x)) { - x = LDAPUtil.formatDate((Date) x); - } - - if (Date.class.isInstance(y)) { - y = LDAPUtil.formatDate((Date) y); - } - - filter.append("(").append(x).append("<=").append(attributeName).append("<=").append(y).append(")"); - } else if (InCondition.class.isInstance(condition)) { - InCondition inCondition = (InCondition) condition; - Object[] valuesToCompare = inCondition.getValue(); - - filter.append("(&("); - - for (int i = 0; i< valuesToCompare.length; i++) { - Object value = valuesToCompare[i]; - - filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(value).append(")"); - } - - filter.append("))"); - } else { - throw new ModelException("Unsupported query condition [" + condition + "]."); + if (Date.class.isInstance(parameterValue)) { + parameterValue = LDAPUtil.formatDate((Date) parameterValue); } + + filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(parameterValue).append(")"); + } else if (LikeCondition.class.isInstance(condition)) { + LikeCondition likeCondition = (LikeCondition) condition; + String parameterValue = (String) likeCondition.getValue(); + + } else if (GreaterThanCondition.class.isInstance(condition)) { + GreaterThanCondition greaterThanCondition = (GreaterThanCondition) condition; + Comparable parameterValue = (Comparable) greaterThanCondition.getValue(); + + if (Date.class.isInstance(parameterValue)) { + parameterValue = LDAPUtil.formatDate((Date) parameterValue); + } + + if (greaterThanCondition.isOrEqual()) { + filter.append("(").append(attributeName).append(">=").append(parameterValue).append(")"); + } else { + filter.append("(").append(attributeName).append(">").append(parameterValue).append(")"); + } + } else if (LessThanCondition.class.isInstance(condition)) { + LessThanCondition lessThanCondition = (LessThanCondition) condition; + Comparable parameterValue = (Comparable) lessThanCondition.getValue(); + + if (Date.class.isInstance(parameterValue)) { + parameterValue = LDAPUtil.formatDate((Date) parameterValue); + } + + if (lessThanCondition.isOrEqual()) { + filter.append("(").append(attributeName).append("<=").append(parameterValue).append(")"); + } else { + filter.append("(").append(attributeName).append("<").append(parameterValue).append(")"); + } + } else if (BetweenCondition.class.isInstance(condition)) { + BetweenCondition betweenCondition = (BetweenCondition) condition; + Comparable x = betweenCondition.getX(); + Comparable y = betweenCondition.getY(); + + if (Date.class.isInstance(x)) { + x = LDAPUtil.formatDate((Date) x); + } + + if (Date.class.isInstance(y)) { + y = LDAPUtil.formatDate((Date) y); + } + + filter.append("(").append(x).append("<=").append(attributeName).append("<=").append(y).append(")"); + } else if (InCondition.class.isInstance(condition)) { + InCondition inCondition = (InCondition) condition; + Object[] valuesToCompare = inCondition.getValue(); + + filter.append("(&("); + + for (int i = 0; i< valuesToCompare.length; i++) { + Object value = valuesToCompare[i]; + + filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(value).append(")"); + } + + filter.append("))"); + } else { + throw new ModelException("Unsupported query condition [" + condition + "]."); } } } } - - - filter.insert(0, "(&("); - filter.append(getObjectClassesFilter(ldapEntryConfig)); - filter.append("))"); - - return filter; } private StringBuilder getObjectClassesFilter(final LDAPMappingConfiguration ldapEntryConfig) { diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java index 507d61fc3f..feaf654927 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java @@ -4,10 +4,12 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; import javax.naming.Binding; import javax.naming.Context; @@ -28,7 +30,7 @@ import javax.naming.ldap.PagedResultsResponseControl; import org.jboss.logging.Logger; import org.keycloak.federation.ldap.idm.model.IdentityType; -import org.keycloak.federation.ldap.idm.query.IdentityQuery; +import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelException; @@ -242,7 +244,7 @@ public class LDAPOperationManager { cons.setSearchScope(SUBTREE_SCOPE); cons.setReturningObjFlag(false); - List returningAttributes = getReturningAttributes(mappingConfiguration); + Set returningAttributes = getReturningAttributes(mappingConfiguration); cons.setReturningAttributes(returningAttributes.toArray(new String[returningAttributes.size()])); return cons; @@ -448,35 +450,6 @@ public class LDAPOperationManager { return this.config.getUniqueIdentifierAttributeName(); } - private NamingEnumeration createEmptyEnumeration() { - return new NamingEnumeration() { - @Override - public SearchResult next() throws NamingException { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public boolean hasMore() throws NamingException { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void close() throws NamingException { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public boolean hasMoreElements() { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public SearchResult nextElement() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - }; - } - public Attributes getAttributes(final String entryUUID, final String baseDN, LDAPMappingConfiguration mappingConfiguration) { SearchResult search = lookupById(baseDN, entryUUID, mappingConfiguration); @@ -560,6 +533,8 @@ public class LDAPOperationManager { LdapContext context = null; try { + // TODO: Remove this + logger.info("Executing operation: " + operation); context = createLdapContext(); return operation.execute(context); } catch (NamingException ne) { @@ -580,8 +555,8 @@ public class LDAPOperationManager { R execute(LdapContext context) throws NamingException; } - private List getReturningAttributes(final LDAPMappingConfiguration mappingConfiguration) { - List returningAttributes = new ArrayList(); + private Set getReturningAttributes(final LDAPMappingConfiguration mappingConfiguration) { + Set returningAttributes = new HashSet(); if (mappingConfiguration != null) { returningAttributes.addAll(mappingConfiguration.getMappedProperties().values()); diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js index 9be9349c1a..37b774fb06 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js @@ -705,8 +705,8 @@ module.controller('LDAPCtrl', function($scope, $location, $route, Notifications, } function triggerSync(action) { - UserFederationSync.get({ action: action, realm: $scope.realm.realm, provider: $scope.instance.id }, function() { - Notifications.success("Sync of users finished successfully"); + UserFederationSync.save({ action: action, realm: $scope.realm.realm, provider: $scope.instance.id }, {}, function(syncResult) { + Notifications.success("Sync of users finished successfully. " + syncResult.status); }, function() { Notifications.error("Error during sync of users"); }); diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java b/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java index 424764988d..746b222e8a 100755 --- a/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java +++ b/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java @@ -35,24 +35,25 @@ public interface UserFederationProviderFactory extends ProviderFactoryMarek Posolda + */ +public class UserFederationSyncResult { + + private int added; + private int updated; + private int removed; + + public int getAdded() { + return added; + } + + public void setAdded(int added) { + this.added = added; + } + + public int getUpdated() { + return updated; + } + + public void setUpdated(int updated) { + this.updated = updated; + } + + public int getRemoved() { + return removed; + } + + public void setRemoved(int removed) { + this.removed = removed; + } + + public void increaseAdded() { + added++; + } + + public void increaseUpdated() { + updated++; + } + + public void increaseRemoved() { + removed++; + } + + public void add(UserFederationSyncResult other) { + added += other.added; + updated += other.updated; + removed += other.removed; + } + + public String getStatus() { + return String.format("%d imported users, %d updated users, %d removed users", added, updated, removed); + } + + @Override + public String toString() { + return String.format("UserFederationSyncResult [ %s ]", getStatus()); + } + + public static UserFederationSyncResult empty() { + return new UserFederationSyncResult(); + } +} diff --git a/services/src/main/java/org/keycloak/services/managers/UsersSyncManager.java b/services/src/main/java/org/keycloak/services/managers/UsersSyncManager.java index 9761b72a56..bf29b78e2e 100755 --- a/services/src/main/java/org/keycloak/services/managers/UsersSyncManager.java +++ b/services/src/main/java/org/keycloak/services/managers/UsersSyncManager.java @@ -8,6 +8,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderFactory; import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.models.UserFederationSyncResult; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.timer.TimerProvider; import org.keycloak.util.Time; @@ -43,19 +44,19 @@ public class UsersSyncManager { }); } - public void syncAllUsers(final KeycloakSessionFactory sessionFactory, String realmId, final UserFederationProviderModel fedProvider) { + public UserFederationSyncResult syncAllUsers(final KeycloakSessionFactory sessionFactory, String realmId, final UserFederationProviderModel fedProvider) { final UserFederationProviderFactory fedProviderFactory = (UserFederationProviderFactory) sessionFactory.getProviderFactory(UserFederationProvider.class, fedProvider.getProviderName()); updateLastSyncInterval(sessionFactory, fedProvider, realmId); - fedProviderFactory.syncAllUsers(sessionFactory, realmId, fedProvider); + return fedProviderFactory.syncAllUsers(sessionFactory, realmId, fedProvider); } - public void syncChangedUsers(final KeycloakSessionFactory sessionFactory, String realmId, final UserFederationProviderModel fedProvider) { + public UserFederationSyncResult syncChangedUsers(final KeycloakSessionFactory sessionFactory, String realmId, final UserFederationProviderModel fedProvider) { final UserFederationProviderFactory fedProviderFactory = (UserFederationProviderFactory) sessionFactory.getProviderFactory(UserFederationProvider.class, fedProvider.getProviderName()); // See when we did last sync. int oldLastSync = fedProvider.getLastSync(); updateLastSyncInterval(sessionFactory, fedProvider, realmId); - fedProviderFactory.syncChangedUsers(sessionFactory, realmId, fedProvider, Time.toDate(oldLastSync)); + return fedProviderFactory.syncChangedUsers(sessionFactory, realmId, fedProvider, Time.toDate(oldLastSync)); } public void refreshPeriodicSyncForProvider(final KeycloakSessionFactory sessionFactory, TimerProvider timer, final UserFederationProviderModel fedProvider, final String realmId) { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java index 427f95f16f..842fc5408d 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java @@ -12,6 +12,7 @@ import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderFactory; import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.models.UserFederationSyncResult; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.provider.ProviderFactory; import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation; @@ -224,23 +225,25 @@ public class UserFederationResource { * * @return */ - @GET + @POST @Path("sync/{id}") @NoCache - public Response syncUsers(@PathParam("id") String providerId, @QueryParam("action") String action) { + public UserFederationSyncResult syncUsers(@PathParam("id") String providerId, @QueryParam("action") String action) { logger.debug("Syncing users"); auth.requireManage(); for (UserFederationProviderModel model : realm.getUserFederationProviders()) { if (model.getId().equals(providerId)) { UsersSyncManager syncManager = new UsersSyncManager(); + UserFederationSyncResult syncResult = null; if ("triggerFullSync".equals(action)) { - syncManager.syncAllUsers(session.getKeycloakSessionFactory(), realm.getId(), model); + syncResult = syncManager.syncAllUsers(session.getKeycloakSessionFactory(), realm.getId(), model); } else if ("triggerChangedUsersSync".equals(action)) { - syncManager.syncChangedUsers(session.getKeycloakSessionFactory(), realm.getId(), model); + syncResult = syncManager.syncChangedUsers(session.getKeycloakSessionFactory(), realm.getId(), model); } + adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success(); - return Response.noContent().build(); + return syncResult; } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java index 536a5b7595..b8ec10a848 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java @@ -7,6 +7,7 @@ import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderFactory; import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.models.UserFederationSyncResult; import java.util.Date; import java.util.HashSet; @@ -63,15 +64,17 @@ public class DummyUserFederationProviderFactory implements UserFederationProvide } @Override - public void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) { + public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) { logger.info("syncAllUsers invoked"); fullSyncCounter.incrementAndGet(); + return UserFederationSyncResult.empty(); } @Override - public void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) { + public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) { logger.info("syncChangedUsers invoked"); changedSyncCounter.incrementAndGet(); + return UserFederationSyncResult.empty(); } public int getFullSyncCounter() { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java index 3aa1954ac8..a7456cd6b2 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java @@ -17,6 +17,7 @@ import org.keycloak.models.LDAPConstants; import org.keycloak.models.RealmModel; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.models.UserFederationSyncResult; import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; import org.keycloak.services.managers.RealmManager; @@ -59,7 +60,7 @@ public class SyncProvidersTest { LDAPIdentityStore ldapStore = FederationProvidersIntegrationTest.getLdapIdentityStore(manager.getSession(), ldapModel); LDAPUtils.removeAllUsers(ldapStore); - for (int i=1 ; i<6 ; i++) { + for (int i=1 ; i<=5 ; i++) { LDAPUser user = LDAPUtils.addUser(ldapStore, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org"); LDAPUtils.updatePassword(ldapStore, user, "Password1"); } @@ -84,7 +85,8 @@ public class SyncProvidersTest { KeycloakSession session = keycloakRule.startSession(); try { KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); - usersSyncManager.syncAllUsers(sessionFactory, "test", ldapModel); + UserFederationSyncResult syncResult = usersSyncManager.syncAllUsers(sessionFactory, "test", ldapModel); + assertSyncEquals(syncResult, 5, 0, 0); } finally { keycloakRule.stopSession(session, false); } @@ -125,7 +127,8 @@ public class SyncProvidersTest { // Trigger partial sync KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); - usersSyncManager.syncChangedUsers(sessionFactory, "test", ldapModel); + UserFederationSyncResult syncResult = usersSyncManager.syncChangedUsers(sessionFactory, "test", ldapModel); + assertSyncEquals(syncResult, 1, 1, 0); } finally { keycloakRule.stopSession(session, false); } @@ -181,6 +184,12 @@ public class SyncProvidersTest { } } + private void assertSyncEquals(UserFederationSyncResult syncResult, int expectedAdded, int expectedUpdated, int expectedRemoved) { + Assert.assertEquals(syncResult.getAdded(), expectedAdded); + Assert.assertEquals(syncResult.getUpdated(), expectedUpdated); + Assert.assertEquals(syncResult.getRemoved(), expectedRemoved); + } + public static void assertUserImported(UserProvider userProvider, RealmModel realm, String username, String expectedFirstName, String expectedLastName, String expectedEmail) { UserModel user = userProvider.getUserByUsername(username, realm); Assert.assertNotNull(user);