KEYCLOAK-826 Show count of created/updated users during federation sync

This commit is contained in:
mposolda 2015-05-07 16:56:47 +02:00
parent 0f4497e53e
commit 61c35265a6
20 changed files with 312 additions and 581 deletions

View file

@ -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);
}
}

View file

@ -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

View file

@ -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<LDAPUser> ldapUsers, UserFederationProviderModel fedModel) {
protected UserFederationSyncResult importLDAPUsers(RealmModel realm, List<LDAPUser> 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;
}
/**

View file

@ -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<LDAPUser> 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<LDAPUser> 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<LDAPUser> 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<LDAPUser> userQuery, final String realmId, final UserFederationProviderModel fedModel) {
boolean pagination = Boolean.parseBoolean(fedModel.getConfig().get(LDAPConstants.PAGINATION));
protected UserFederationSyncResult syncImpl(KeycloakSessionFactory sessionFactory, IdentityQuery<LDAPUser> 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<LDAPUser> ldapUsers) {
protected UserFederationSyncResult importLdapUsers(KeycloakSession session, String realmId, UserFederationProviderModel fedModel, List<LDAPUser> 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) {

View file

@ -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;

View file

@ -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;
/**
* <p>An {@link IdentityQuery} is responsible for querying the underlying identity stores for instances of
* a given {@link IdentityType}.</p>
*
* <p>Instances of this class are obtained using the {@link IdentityQueryBuilder#createIdentityQuery(Class)}
* method.</p>
*
* <pre>
* 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<User> result = query.getResultList();
* </pre>
*
* <p>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.</p>
*
* @author Shane Bryzak
* @author Pedro Igor
*/
public interface IdentityQuery<T extends IdentityType> {
/**
* @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<T> 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<T> 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<T> setSortAscending(boolean sortAscending);
/**
* <p>Set a query parameter to this query in order to filter the results.</p>
*
* <p>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.</p>
*
* @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<T> setParameter(QueryParameter param, Object... value);
/**
* <p>Add to this query the conditions that will be used to filter results.</p>
*
* <p>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.</p>
*
* @param condition One or more conditions created from {@link IdentityQueryBuilder}.
*
* @return
*/
IdentityQuery<T> where(Condition... condition);
/**
* <p>Add to this query the sorting conditions to be applied to the results.</p>
*
* @param sorts The ordering conditions.
*
* @return
*/
IdentityQuery<T> sortBy(Sort... sorts);
/**
* <p>The type used to create this query.</p>
*
* @return
*/
Class<T> getIdentityType();
/**
* <p>Returns a map with all the parameter set for this query.</p>
*
* @return
*
* @deprecated Use {@link IdentityQuery#getConditions()} instead. Will be removed.
*/
@Deprecated
Map<QueryParameter, Object[]> getParameters();
/**
* <p>Returns a set containing all conditions used by this query to filter its results.</p>
*
* @return
*/
Set<Condition> getConditions();
/**
* <p>Returns a set containing all sorting conditions used to filter the results.</p>
*
* @return
*/
Set<Sort> getSorting();
/**
* <p>Returns the value used to restrict the given query parameter.</p>
*
* @param queryParameter
*
* @return
*/
@Deprecated
Object[] getParameter(QueryParameter queryParameter);
@Deprecated
Map<QueryParameter, Object[]> getParameters(Class<?> type);
int getOffset();
/**
* <p>Set the position of the first result to retrieve.</p>
*
* @param offset
*
* @return
*/
IdentityQuery<T> setOffset(int offset);
/**
* <p>Returns the number of instances to retrieve.</p>
*
* @return
*/
int getLimit();
/**
* <p>Set the maximum number of results to retrieve.</p>
*
* @param limit the number of instances to retrieve.
*
* @return
*/
IdentityQuery<T> setLimit(int limit);
/**
* <p>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.</p>
*
* @return
*/
List<T> 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();
}

View file

@ -1,124 +0,0 @@
package org.keycloak.federation.ldap.idm.query;
import org.keycloak.federation.ldap.idm.model.IdentityType;
/**
* <p>The {@link IdentityQueryBuilder} is responsible for creating {@link IdentityQuery} instances and also
* provide methods to create conditions, orderings, sorting, etc.</p>
*
* @author Pedro Igor
*/
public interface IdentityQueryBuilder {
/**
* <p>Create a condition for testing the whether the query parameter satisfies the given pattern..</p>
*
* @param parameter The query parameter.
* @param pattern The pattern to match.
*
* @return
*/
Condition like(QueryParameter parameter, String pattern);
/**
* <p>Create a condition for testing the arguments for equality.</p>
*
* @param parameter The query parameter.
* @param value The value to compare.
*
* @return
*/
Condition equal(QueryParameter parameter, Object value);
/**
* <p>Create a condition for testing whether the query parameter is grater than the given value..</p>
*
* @param parameter The query parameter.
* @param x The value to compare.
*
* @return
*/
Condition greaterThan(QueryParameter parameter, Object x);
/**
* <p>Create a condition for testing whether the query parameter is grater than or equal to the given value..</p>
*
* @param parameter The query parameter.
* @param x The value to compare.
*
* @return
*/
Condition greaterThanOrEqualTo(QueryParameter parameter, Object x);
/**
* <p>Create a condition for testing whether the query parameter is less than the given value..</p>
*
* @param parameter The query parameter.
* @param x The value to compare.
*
* @return
*/
Condition lessThan(QueryParameter parameter, Object x);
/**
* <p>Create a condition for testing whether the query parameter is less than or equal to the given value..</p>
*
* @param parameter The query parameter.
* @param x The value to compare.
*
* @return
*/
Condition lessThanOrEqualTo(QueryParameter parameter, Object x);
/**
* <p>Create a condition for testing whether the query parameter is between the given values.</p>
*
* @param parameter The query parameter.
* @param x The first value.
* @param x The second value.
*
* @return
*/
Condition between(QueryParameter parameter, Object x, Object y);
/**
* <p>Create a condition for testing whether the query parameter is contained in a list of values.</p>
*
* @param parameter The query parameter.
* @param values A list of values.
*
* @return
*/
Condition in(QueryParameter parameter, Object... values);
/**
* <p>Create an ascending order for the given <code>parameter</code>. Once created, you can use it to sort the results of a
* query.</p>
*
* @param parameter The query parameter to sort.
*
* @return
*/
Sort asc(QueryParameter parameter);
/**
* <p>Create an descending order for the given <code>parameter</code>. Once created, you can use it to sort the results of a
* query.</p>
*
* @param parameter The query parameter to sort.
*
* @return
*/
Sort desc(QueryParameter parameter);
/**
* <p> Create an {@link IdentityQuery} that can be used to query for {@link
* IdentityType} instances of a the given <code>identityType</code>. </p>
*
* @param identityType The type to search. If you provide the {@link IdentityType}
* base interface any of its sub-types will be returned.
*
* @return
*/
<T extends IdentityType> IdentityQuery<T> createIdentityQuery(Class<T> identityType);
}

View file

@ -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<T extends IdentityType> implements IdentityQuery<T> {
public class IdentityQuery<T extends IdentityType> {
private final Map<QueryParameter, Object[]> parameters = new LinkedHashMap<QueryParameter, Object[]>();
private final Class<T> 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<T extends IdentityType> implements IdentityQue
private final Set<Condition> conditions = new LinkedHashSet<Condition>();
private final Set<Sort> ordering = new LinkedHashSet<Sort>();
public DefaultIdentityQuery(IdentityQueryBuilder queryBuilder, Class<T> identityType, IdentityStore identityStore) {
public IdentityQuery(IdentityQueryBuilder queryBuilder, Class<T> identityType, LDAPIdentityStore identityStore) {
this.queryBuilder = queryBuilder;
this.identityStore = identityStore;
this.identityType = identityType;
}
@Override
public IdentityQuery<T> 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<T extends IdentityType> implements IdentityQue
return this;
}
@Override
public IdentityQuery<T> where(Condition... condition) {
this.conditions.addAll(Arrays.asList(condition));
return this;
}
@Override
public IdentityQuery<T> sortBy(Sort... sorts) {
this.ordering.addAll(Arrays.asList(sorts));
return this;
}
@Override
public Set<Sort> getSorting() {
return unmodifiableSet(this.ordering);
}
@Override
public Class<T> getIdentityType() {
return identityType;
}
@Override
public Map<QueryParameter, Object[]> getParameters() {
return parameters;
}
@Override
public Object[] getParameter(QueryParameter queryParameter) {
return this.parameters.get(queryParameter);
}
@Override
public Map<QueryParameter, Object[]> getParameters(Class<?> type) {
Map<QueryParameter, Object[]> typedParameters = new HashMap<QueryParameter, Object[]>();
Set<Map.Entry<QueryParameter, Object[]>> entrySet = this.parameters.entrySet();
for (Map.Entry<QueryParameter, Object[]> 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<T> getResultList() {
// remove this statement once deprecated methods on IdentityQuery are removed
@ -165,42 +126,35 @@ public class DefaultIdentityQuery<T extends IdentityType> implements IdentityQue
return result;
}
@Override
public int getResultCount() {
return identityStore.countQueryResults(this);
}
@Override
public IdentityQuery<T> setOffset(int offset) {
this.offset = offset;
return this;
}
@Override
public IdentityQuery<T> setLimit(int limit) {
this.limit = limit;
return this;
}
@Override
public IdentityQuery<T> setSortParameters(QueryParameter... sortParameters) {
this.sortParameters = sortParameters;
return this;
}
@Override
public IdentityQuery<T> setSortAscending(boolean sortAscending) {
this.sortAscending = sortAscending;
return this;
}
@Override
public IdentityQuery<T> setPaginationContext(Object object) {
this.paginationContext = object;
return this;
}
@Override
public Set<Condition> getConditions() {
return unmodifiableSet(this.conditions);
}

View file

@ -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 <T extends IdentityType> IdentityQuery createIdentityQuery(Class<T> identityType) {
return new DefaultIdentityQuery(this, identityType, this.identityStore);
return new IdentityQuery(this, identityType, this.identityStore);
}
private void throwExceptionIfNotComparable(Object x) {

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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;
}
}

View file

@ -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;
/**

View file

@ -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) {

View file

@ -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<String> returningAttributes = getReturningAttributes(mappingConfiguration);
Set<String> 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<SearchResult> createEmptyEnumeration() {
return new NamingEnumeration<SearchResult>() {
@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<String> getReturningAttributes(final LDAPMappingConfiguration mappingConfiguration) {
List<String> returningAttributes = new ArrayList<String>();
private Set<String> getReturningAttributes(final LDAPMappingConfiguration mappingConfiguration) {
Set<String> returningAttributes = new HashSet<String>();
if (mappingConfiguration != null) {
returningAttributes.addAll(mappingConfiguration.getMappedProperties().values());

View file

@ -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");
});

View file

@ -35,24 +35,25 @@ public interface UserFederationProviderFactory extends ProviderFactory<UserFeder
String getId();
/**
* Sync all users from the provider storage to Keycloak storage.
* Sync all users from the provider storage to Keycloak storage. Alternatively can update existing users or remove keycloak users, which are no longer
* available in federation storage (depends on the implementation)
*
* @param sessionFactory
* @param realmId
* @param model
* @return result with count of added/updated/removed users
*/
void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model);
UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model);
/**
* Sync just changed (added / updated / removed) users from the provider storage to Keycloak storage. This is useful in case
* that your storage supports "changelogs" (Tracking what users changed since specified date). It's implementation specific to
* decide what exactly will be changed (For example LDAP supports tracking of added / updated users, but not removed users. So
* removed users are not synced)
* decide what exactly will be changed
*
* @param sessionFactory
* @param realmId
* @param model
* @param lastSync
*/
void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync);
UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync);
}

View file

@ -0,0 +1,66 @@
package org.keycloak.models;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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();
}
}

View file

@ -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) {

View file

@ -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;
}
}

View file

@ -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() {

View file

@ -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);