keycloak-aplcache
Changes
examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java 16(+12 -4)
federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java 7(+5 -2)
federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQuery.java 225(+0 -225)
federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/IdentityQueryBuilder.java 124(+0 -124)
federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/IdentityQuery.java 54(+4 -50)
federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/IdentityQueryBuilder.java 30(+12 -18)
federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/OrCondition.java 27(+27 -0)
federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java 157(+87 -70)
federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java 41(+8 -33)
federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java 56(+34 -22)
Details
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java
index 24f30b4..5c585f3 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java
@@ -8,6 +8,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.utils.KeycloakModelUtils;
@@ -98,7 +99,9 @@ public abstract class BasePropertiesFederationFactory implements UserFederationP
}
@Override
- public void syncAllUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
+ public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
+ final UserFederationSyncResult syncResult = new UserFederationSyncResult();
+
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
@@ -112,16 +115,21 @@ public abstract class BasePropertiesFederationFactory implements UserFederationP
if (localUser == null) {
// New user, let's import him
- federationProvider.getUserByUsername(realm, username);
+ UserModel imported = federationProvider.getUserByUsername(realm, username);
+ if (imported != null) {
+ syncResult.increaseAdded();
+ }
}
}
}
});
+
+ return syncResult;
}
@Override
- public void syncChangedUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model, Date lastSync) {
- syncAllUsers(sessionFactory, realmId, model);
+ public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model, Date lastSync) {
+ return syncAllUsers(sessionFactory, realmId, model);
}
}
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java
index 375f2cf..b7b6c92 100755
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java
@@ -14,6 +14,7 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
/**
* Factory for standalone Kerberos federation provider. Standalone means that it's not backed by LDAP. For Kerberos backed by LDAP (like MS AD or ApacheDS environment)
@@ -41,13 +42,15 @@ public class KerberosFederationProviderFactory implements UserFederationProvider
}
@Override
- public void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
+ public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
logger.warn("Sync users not supported for this provider");
+ return UserFederationSyncResult.empty();
}
@Override
- public void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
+ public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
logger.warn("Sync users not supported for this provider");
+ return UserFederationSyncResult.empty();
}
@Override
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/OrCondition.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/OrCondition.java
new file mode 100644
index 0000000..436355b
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/OrCondition.java
@@ -0,0 +1,27 @@
+package org.keycloak.federation.ldap.idm.query.internal;
+
+import java.util.List;
+
+import org.keycloak.federation.ldap.idm.query.Condition;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+
+/**
+ * @author <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;
+ }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java
index 7fef705..447a6da 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/IdentityStore.java
@@ -5,7 +5,7 @@ import java.util.List;
import org.keycloak.federation.ldap.idm.model.AttributedType;
import org.keycloak.federation.ldap.idm.model.IdentityType;
import org.keycloak.federation.ldap.idm.model.LDAPUser;
-import org.keycloak.federation.ldap.idm.query.IdentityQuery;
+import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStoreConfiguration;
/**
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
index 8b91240..5e4c181 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
@@ -23,16 +23,16 @@ import org.keycloak.federation.ldap.idm.model.IdentityType;
import org.keycloak.federation.ldap.idm.model.LDAPUser;
import org.keycloak.federation.ldap.idm.query.AttributeParameter;
import org.keycloak.federation.ldap.idm.query.Condition;
-import org.keycloak.federation.ldap.idm.query.IdentityQuery;
-import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
import org.keycloak.federation.ldap.idm.query.internal.BetweenCondition;
-import org.keycloak.federation.ldap.idm.query.internal.DefaultQueryBuilder;
+import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery;
+import org.keycloak.federation.ldap.idm.query.internal.IdentityQueryBuilder;
import org.keycloak.federation.ldap.idm.query.internal.EqualCondition;
import org.keycloak.federation.ldap.idm.query.internal.GreaterThanCondition;
import org.keycloak.federation.ldap.idm.query.internal.InCondition;
import org.keycloak.federation.ldap.idm.query.internal.LessThanCondition;
import org.keycloak.federation.ldap.idm.query.internal.LikeCondition;
+import org.keycloak.federation.ldap.idm.query.internal.OrCondition;
import org.keycloak.federation.ldap.idm.store.IdentityStore;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelDuplicateException;
@@ -183,7 +183,7 @@ public class LDAPIdentityStore implements IdentityStore {
}
public IdentityQueryBuilder createQueryBuilder() {
- return new DefaultQueryBuilder(this);
+ return new IdentityQueryBuilder(this);
}
// *************** CREDENTIALS AND USER SPECIFIC STUFF
@@ -310,94 +310,111 @@ public class LDAPIdentityStore implements IdentityStore {
StringBuilder filter = new StringBuilder();
for (Condition condition : identityQuery.getConditions()) {
- QueryParameter queryParameter = condition.getParameter();
+ applyCondition(filter, condition, ldapEntryConfig);
+ }
- if (!IdentityType.ID.equals(queryParameter)) {
- if (AttributeParameter.class.isInstance(queryParameter)) {
- AttributeParameter attributeParameter = (AttributeParameter) queryParameter;
- String attributeName = ldapEntryConfig.getMappedProperties().get(attributeParameter.getName());
- if (attributeName != null) {
- if (EqualCondition.class.isInstance(condition)) {
- EqualCondition equalCondition = (EqualCondition) condition;
- Object parameterValue = equalCondition.getValue();
+ filter.insert(0, "(&");
+ filter.append(getObjectClassesFilter(ldapEntryConfig));
+ filter.append(")");
- if (Date.class.isInstance(parameterValue)) {
- parameterValue = LDAPUtil.formatDate((Date) parameterValue);
- }
+ logger.infof("Using filter for LDAP search: %s", filter);
+ return filter;
+ }
- filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(parameterValue).append(")");
- } else if (LikeCondition.class.isInstance(condition)) {
- LikeCondition likeCondition = (LikeCondition) condition;
- String parameterValue = (String) likeCondition.getValue();
+ protected void applyCondition(StringBuilder filter, Condition condition, LDAPMappingConfiguration ldapEntryConfig) {
+ if (OrCondition.class.isInstance(condition)) {
+ OrCondition orCondition = (OrCondition) condition;
+ filter.append("(|");
- } else if (GreaterThanCondition.class.isInstance(condition)) {
- GreaterThanCondition greaterThanCondition = (GreaterThanCondition) condition;
- Comparable parameterValue = (Comparable) greaterThanCondition.getValue();
+ for (Condition innerCondition : orCondition.getInnerConditions()) {
+ applyCondition(filter, innerCondition, ldapEntryConfig);
+ }
- if (Date.class.isInstance(parameterValue)) {
- parameterValue = LDAPUtil.formatDate((Date) parameterValue);
- }
+ filter.append(")");
+ return;
+ }
- if (greaterThanCondition.isOrEqual()) {
- filter.append("(").append(attributeName).append(">=").append(parameterValue).append(")");
- } else {
- filter.append("(").append(attributeName).append(">").append(parameterValue).append(")");
- }
- } else if (LessThanCondition.class.isInstance(condition)) {
- LessThanCondition lessThanCondition = (LessThanCondition) condition;
- Comparable parameterValue = (Comparable) lessThanCondition.getValue();
+ QueryParameter queryParameter = condition.getParameter();
- if (Date.class.isInstance(parameterValue)) {
- parameterValue = LDAPUtil.formatDate((Date) parameterValue);
- }
+ if (!IdentityType.ID.equals(queryParameter)) {
+ if (AttributeParameter.class.isInstance(queryParameter)) {
+ AttributeParameter attributeParameter = (AttributeParameter) queryParameter;
+ String attributeName = ldapEntryConfig.getMappedProperties().get(attributeParameter.getName());
- if (lessThanCondition.isOrEqual()) {
- filter.append("(").append(attributeName).append("<=").append(parameterValue).append(")");
- } else {
- filter.append("(").append(attributeName).append("<").append(parameterValue).append(")");
- }
- } else if (BetweenCondition.class.isInstance(condition)) {
- BetweenCondition betweenCondition = (BetweenCondition) condition;
- Comparable x = betweenCondition.getX();
- Comparable y = betweenCondition.getY();
+ if (attributeName != null) {
+ if (EqualCondition.class.isInstance(condition)) {
+ EqualCondition equalCondition = (EqualCondition) condition;
+ Object parameterValue = equalCondition.getValue();
- if (Date.class.isInstance(x)) {
- x = LDAPUtil.formatDate((Date) x);
- }
+ if (Date.class.isInstance(parameterValue)) {
+ parameterValue = LDAPUtil.formatDate((Date) parameterValue);
+ }
- if (Date.class.isInstance(y)) {
- y = LDAPUtil.formatDate((Date) y);
- }
+ 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();
- 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();
+ } else if (GreaterThanCondition.class.isInstance(condition)) {
+ GreaterThanCondition greaterThanCondition = (GreaterThanCondition) condition;
+ Comparable parameterValue = (Comparable) greaterThanCondition.getValue();
- filter.append("(&(");
+ if (Date.class.isInstance(parameterValue)) {
+ parameterValue = LDAPUtil.formatDate((Date) parameterValue);
+ }
- for (int i = 0; i< valuesToCompare.length; i++) {
- Object value = valuesToCompare[i];
+ 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();
- filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(value).append(")");
- }
+ if (Date.class.isInstance(parameterValue)) {
+ parameterValue = LDAPUtil.formatDate((Date) parameterValue);
+ }
- filter.append("))");
+ if (lessThanCondition.isOrEqual()) {
+ filter.append("(").append(attributeName).append("<=").append(parameterValue).append(")");
} else {
- throw new ModelException("Unsupported query condition [" + condition + "].");
+ filter.append("(").append(attributeName).append("<").append(parameterValue).append(")");
+ }
+ } else if (BetweenCondition.class.isInstance(condition)) {
+ BetweenCondition betweenCondition = (BetweenCondition) condition;
+ Comparable x = betweenCondition.getX();
+ Comparable y = betweenCondition.getY();
+
+ if (Date.class.isInstance(x)) {
+ x = LDAPUtil.formatDate((Date) x);
+ }
+
+ if (Date.class.isInstance(y)) {
+ y = LDAPUtil.formatDate((Date) y);
+ }
+
+ filter.append("(").append(x).append("<=").append(attributeName).append("<=").append(y).append(")");
+ } else if (InCondition.class.isInstance(condition)) {
+ InCondition inCondition = (InCondition) condition;
+ Object[] valuesToCompare = inCondition.getValue();
+
+ filter.append("(&(");
+
+ for (int i = 0; i< valuesToCompare.length; i++) {
+ Object value = valuesToCompare[i];
+
+ filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(value).append(")");
}
+
+ filter.append("))");
+ } else {
+ throw new ModelException("Unsupported query condition [" + condition + "].");
}
}
}
}
-
-
- filter.insert(0, "(&(");
- filter.append(getObjectClassesFilter(ldapEntryConfig));
- filter.append("))");
-
- return filter;
}
private StringBuilder getObjectClassesFilter(final LDAPMappingConfiguration ldapEntryConfig) {
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java
index 507d61f..feaf654 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java
@@ -4,10 +4,12 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
+import java.util.Set;
import javax.naming.Binding;
import javax.naming.Context;
@@ -28,7 +30,7 @@ import javax.naming.ldap.PagedResultsResponseControl;
import org.jboss.logging.Logger;
import org.keycloak.federation.ldap.idm.model.IdentityType;
-import org.keycloak.federation.ldap.idm.query.IdentityQuery;
+import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelException;
@@ -242,7 +244,7 @@ public class LDAPOperationManager {
cons.setSearchScope(SUBTREE_SCOPE);
cons.setReturningObjFlag(false);
- List<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());
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
index 370e0f0..b0f3d0a 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -4,8 +4,8 @@ import org.jboss.logging.Logger;
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
import org.keycloak.federation.ldap.idm.model.LDAPUser;
-import org.keycloak.federation.ldap.idm.query.IdentityQuery;
-import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
+import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery;
+import org.keycloak.federation.ldap.idm.query.internal.IdentityQueryBuilder;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
import org.keycloak.models.CredentialValidationOutput;
@@ -18,6 +18,7 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.models.UserModel;
import org.keycloak.constants.KerberosConstants;
@@ -334,7 +335,9 @@ public class LDAPFederationProvider implements UserFederationProvider {
public void close() {
}
- protected void importLDAPUsers(RealmModel realm, List<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;
}
/**
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
index a498a93..7b40489 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
@@ -9,8 +9,8 @@ import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
import org.keycloak.federation.ldap.idm.model.IdentityType;
import org.keycloak.federation.ldap.idm.model.LDAPUser;
import org.keycloak.federation.ldap.idm.query.Condition;
-import org.keycloak.federation.ldap.idm.query.IdentityQuery;
-import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
+import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery;
+import org.keycloak.federation.ldap.idm.query.internal.IdentityQueryBuilder;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -20,6 +20,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Collections;
@@ -75,40 +76,47 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
}
@Override
- public void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
- logger.infof("Sync all users from LDAP to local store: realm: %s, federation provider: %s, current time: " + new Date(), realmId, model.getDisplayName());
+ public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
+ logger.infof("Sync all users from LDAP to local store: realm: %s, federation provider: %s", realmId, model.getDisplayName());
LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model);
IdentityQuery<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);
-
- // Sync updated users
- condition = queryBuilder.greaterThanOrEqualTo(LDAPUtils.MODIFY_DATE, lastSync);
- 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);
+
+ 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) {
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
index 9753592..74eb9f2 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
@@ -3,9 +3,9 @@ package org.keycloak.federation.ldap;
import org.keycloak.federation.ldap.idm.model.Attribute;
import org.keycloak.federation.ldap.idm.model.LDAPUser;
import org.keycloak.federation.ldap.idm.query.AttributeParameter;
-import org.keycloak.federation.ldap.idm.query.IdentityQuery;
-import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
+import org.keycloak.federation.ldap.idm.query.internal.IdentityQuery;
+import org.keycloak.federation.ldap.idm.query.internal.IdentityQueryBuilder;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelDuplicateException;
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
index 9be9349..37b774f 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
@@ -705,8 +705,8 @@ module.controller('LDAPCtrl', function($scope, $location, $route, Notifications,
}
function triggerSync(action) {
- UserFederationSync.get({ action: action, realm: $scope.realm.realm, provider: $scope.instance.id }, function() {
- Notifications.success("Sync of users finished successfully");
+ UserFederationSync.save({ action: action, realm: $scope.realm.realm, provider: $scope.instance.id }, {}, function(syncResult) {
+ Notifications.success("Sync of users finished successfully. " + syncResult.status);
}, function() {
Notifications.error("Error during sync of users");
});
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java b/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java
index 4247649..746b222 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java
@@ -35,24 +35,25 @@ public interface UserFederationProviderFactory extends 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);
}
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationSyncResult.java b/model/api/src/main/java/org/keycloak/models/UserFederationSyncResult.java
new file mode 100644
index 0000000..e6d465b
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationSyncResult.java
@@ -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();
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/managers/UsersSyncManager.java b/services/src/main/java/org/keycloak/services/managers/UsersSyncManager.java
index 9761b72..bf29b78 100755
--- a/services/src/main/java/org/keycloak/services/managers/UsersSyncManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/UsersSyncManager.java
@@ -8,6 +8,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.timer.TimerProvider;
import org.keycloak.util.Time;
@@ -43,19 +44,19 @@ public class UsersSyncManager {
});
}
- public void syncAllUsers(final KeycloakSessionFactory sessionFactory, String realmId, final UserFederationProviderModel fedProvider) {
+ public UserFederationSyncResult syncAllUsers(final KeycloakSessionFactory sessionFactory, String realmId, final UserFederationProviderModel fedProvider) {
final UserFederationProviderFactory fedProviderFactory = (UserFederationProviderFactory) sessionFactory.getProviderFactory(UserFederationProvider.class, fedProvider.getProviderName());
updateLastSyncInterval(sessionFactory, fedProvider, realmId);
- fedProviderFactory.syncAllUsers(sessionFactory, realmId, fedProvider);
+ return fedProviderFactory.syncAllUsers(sessionFactory, realmId, fedProvider);
}
- public void syncChangedUsers(final KeycloakSessionFactory sessionFactory, String realmId, final UserFederationProviderModel fedProvider) {
+ public UserFederationSyncResult syncChangedUsers(final KeycloakSessionFactory sessionFactory, String realmId, final UserFederationProviderModel fedProvider) {
final UserFederationProviderFactory fedProviderFactory = (UserFederationProviderFactory) sessionFactory.getProviderFactory(UserFederationProvider.class, fedProvider.getProviderName());
// See when we did last sync.
int oldLastSync = fedProvider.getLastSync();
updateLastSyncInterval(sessionFactory, fedProvider, realmId);
- fedProviderFactory.syncChangedUsers(sessionFactory, realmId, fedProvider, Time.toDate(oldLastSync));
+ return fedProviderFactory.syncChangedUsers(sessionFactory, realmId, fedProvider, Time.toDate(oldLastSync));
}
public void refreshPeriodicSyncForProvider(final KeycloakSessionFactory sessionFactory, TimerProvider timer, final UserFederationProviderModel fedProvider, final String realmId) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
index 427f95f..842fc54 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
@@ -12,6 +12,7 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation;
@@ -224,23 +225,25 @@ public class UserFederationResource {
*
* @return
*/
- @GET
+ @POST
@Path("sync/{id}")
@NoCache
- public Response syncUsers(@PathParam("id") String providerId, @QueryParam("action") String action) {
+ public UserFederationSyncResult syncUsers(@PathParam("id") String providerId, @QueryParam("action") String action) {
logger.debug("Syncing users");
auth.requireManage();
for (UserFederationProviderModel model : realm.getUserFederationProviders()) {
if (model.getId().equals(providerId)) {
UsersSyncManager syncManager = new UsersSyncManager();
+ UserFederationSyncResult syncResult = null;
if ("triggerFullSync".equals(action)) {
- syncManager.syncAllUsers(session.getKeycloakSessionFactory(), realm.getId(), model);
+ syncResult = syncManager.syncAllUsers(session.getKeycloakSessionFactory(), realm.getId(), model);
} else if ("triggerChangedUsersSync".equals(action)) {
- syncManager.syncChangedUsers(session.getKeycloakSessionFactory(), realm.getId(), model);
+ syncResult = syncManager.syncChangedUsers(session.getKeycloakSessionFactory(), realm.getId(), model);
}
+
adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
- return Response.noContent().build();
+ return syncResult;
}
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java
index 536a5b7..b8ec10a 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java
@@ -7,6 +7,7 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
import java.util.Date;
import java.util.HashSet;
@@ -63,15 +64,17 @@ public class DummyUserFederationProviderFactory implements UserFederationProvide
}
@Override
- public void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
+ public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
logger.info("syncAllUsers invoked");
fullSyncCounter.incrementAndGet();
+ return UserFederationSyncResult.empty();
}
@Override
- public void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
+ public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
logger.info("syncChangedUsers invoked");
changedSyncCounter.incrementAndGet();
+ return UserFederationSyncResult.empty();
}
public int getFullSyncCounter() {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java
index 3aa1954..a7456cd 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/SyncProvidersTest.java
@@ -17,6 +17,7 @@ import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.services.managers.RealmManager;
@@ -59,7 +60,7 @@ public class SyncProvidersTest {
LDAPIdentityStore ldapStore = FederationProvidersIntegrationTest.getLdapIdentityStore(manager.getSession(), ldapModel);
LDAPUtils.removeAllUsers(ldapStore);
- for (int i=1 ; i<6 ; i++) {
+ for (int i=1 ; i<=5 ; i++) {
LDAPUser user = LDAPUtils.addUser(ldapStore, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org");
LDAPUtils.updatePassword(ldapStore, user, "Password1");
}
@@ -84,7 +85,8 @@ public class SyncProvidersTest {
KeycloakSession session = keycloakRule.startSession();
try {
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
- usersSyncManager.syncAllUsers(sessionFactory, "test", ldapModel);
+ UserFederationSyncResult syncResult = usersSyncManager.syncAllUsers(sessionFactory, "test", ldapModel);
+ assertSyncEquals(syncResult, 5, 0, 0);
} finally {
keycloakRule.stopSession(session, false);
}
@@ -125,7 +127,8 @@ public class SyncProvidersTest {
// Trigger partial sync
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
- usersSyncManager.syncChangedUsers(sessionFactory, "test", ldapModel);
+ UserFederationSyncResult syncResult = usersSyncManager.syncChangedUsers(sessionFactory, "test", ldapModel);
+ assertSyncEquals(syncResult, 1, 1, 0);
} finally {
keycloakRule.stopSession(session, false);
}
@@ -181,6 +184,12 @@ public class SyncProvidersTest {
}
}
+ private void assertSyncEquals(UserFederationSyncResult syncResult, int expectedAdded, int expectedUpdated, int expectedRemoved) {
+ Assert.assertEquals(syncResult.getAdded(), expectedAdded);
+ Assert.assertEquals(syncResult.getUpdated(), expectedUpdated);
+ Assert.assertEquals(syncResult.getRemoved(), expectedRemoved);
+ }
+
public static void assertUserImported(UserProvider userProvider, RealmModel realm, String username, String expectedFirstName, String expectedLastName, String expectedEmail) {
UserModel user = userProvider.getUserByUsername(username, realm);
Assert.assertNotNull(user);