KEYCLOAK-826 Show count of created/updated users during federation sync
This commit is contained in:
parent
0f4497e53e
commit
61c35265a6
20 changed files with 312 additions and 581 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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) {
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue