keycloak-aplcache

Changes

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)

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