keycloak-aplcache
Changes
core/src/main/java/org/keycloak/representations/idm/UserFederationProviderRepresentation.java 27(+27 -0)
examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java 47(+47 -0)
examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationFactory.java 4(+3 -1)
examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationFactory.java 4(+3 -1)
export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportImportSessionTask.java 25(+25 -0)
export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportImportUtils.java 46(+0 -46)
export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java 6(+3 -3)
export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/MultipleStepsExportProvider.java 12(+7 -5)
export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java 13(+7 -6)
export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileExportProvider.java 13(+7 -6)
export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java 11(+5 -6)
export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java 13(+7 -6)
federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java 87(+86 -1)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java 4(+2 -2)
model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java 31(+31 -0)
picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/PartitionManagerRegistry.java 14(+11 -3)
testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProvider.java 2(+1 -1)
testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProviderFactory.java 34(+32 -2)
testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java 14(+8 -6)
testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java 186(+186 -0)
timer/basic/pom.xml 5(+5 -0)
Details
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserFederationProviderRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserFederationProviderRepresentation.java
index 19f1d0c..7229435 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserFederationProviderRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserFederationProviderRepresentation.java
@@ -12,6 +12,9 @@ public class UserFederationProviderRepresentation {
private String providerName;
private Map<String, String> config;
private int priority;
+ private int fullSyncPeriod;
+ private int changedSyncPeriod;
+ private int lastSync;
public String getId() {
return id;
@@ -54,6 +57,30 @@ public class UserFederationProviderRepresentation {
this.priority = priority;
}
+ public int getFullSyncPeriod() {
+ return fullSyncPeriod;
+ }
+
+ public void setFullSyncPeriod(int fullSyncPeriod) {
+ this.fullSyncPeriod = fullSyncPeriod;
+ }
+
+ public int getChangedSyncPeriod() {
+ return changedSyncPeriod;
+ }
+
+ public void setChangedSyncPeriod(int changedSyncPeriod) {
+ this.changedSyncPeriod = changedSyncPeriod;
+ }
+
+ public int getLastSync() {
+ return lastSync;
+ }
+
+ public void setLastSync(int lastSync) {
+ this.lastSync = lastSync;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
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 04845fb..b0d9925 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
@@ -2,12 +2,19 @@ package org.keycloak.examples.federation.properties;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
+import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Date;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
@@ -84,4 +91,44 @@ public abstract class BasePropertiesFederationFactory implements UserFederationP
public void close() {
}
+
+ @Override
+ public void syncAllUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
+ KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+ @Override
+ public void run(KeycloakSession session) {
+ RealmModel realm = session.realms().getRealm(realmId);
+ BasePropertiesFederationProvider federationProvider = (BasePropertiesFederationProvider)getInstance(session, model);
+ Set<String> allUsernames = federationProvider.getProperties().stringPropertyNames();
+ for (String username : allUsernames) {
+ federationProvider.getUserByUsername(realm, username);
+ }
+ }
+
+ });
+ }
+
+ @Override
+ public void syncChangedUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model, Date lastSync) {
+ KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+ @Override
+ public void run(KeycloakSession session) {
+ RealmModel realm = session.realms().getRealm(realmId);
+ UserProvider localProvider = session.userStorage();
+ BasePropertiesFederationProvider federationProvider = (BasePropertiesFederationProvider)getInstance(session, model);
+ Set<String> allUsernames = federationProvider.getProperties().stringPropertyNames();
+ for (String username : allUsernames) {
+ UserModel localUser = localProvider.getUserByUsername(username, realm);
+
+ if (localUser == null) {
+ // New user, let's import him
+ federationProvider.getUserByUsername(realm, username);
+ }
+ }
+ }
+
+ });
+ }
}
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationFactory.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationFactory.java
index f079e8c..f06250a 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationFactory.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationFactory.java
@@ -12,6 +12,8 @@ import java.util.Properties;
*/
public class ClasspathPropertiesFederationFactory extends BasePropertiesFederationFactory {
+ public static final String PROVIDER_NAME = "classpath-properties";
+
@Override
protected BasePropertiesFederationProvider createProvider(KeycloakSession session, UserFederationProviderModel model, Properties props) {
return new ClasspathPropertiesFederationProvider(session, model, props);
@@ -30,6 +32,6 @@ public class ClasspathPropertiesFederationFactory extends BasePropertiesFederati
@Override
public String getId() {
- return "classpath-properties";
+ return PROVIDER_NAME;
}
}
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationFactory.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationFactory.java
index 5c64b73..2f89575 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationFactory.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationFactory.java
@@ -14,6 +14,8 @@ import java.util.Properties;
*/
public class FilePropertiesFederationFactory extends BasePropertiesFederationFactory {
+ public static final String PROVIDER_NAME = "file-properties";
+
@Override
protected BasePropertiesFederationProvider createProvider(KeycloakSession session, UserFederationProviderModel model, Properties props) {
return new FilePropertiesFederationProvider(session, props, model);
@@ -35,6 +37,6 @@ public class FilePropertiesFederationFactory extends BasePropertiesFederationFac
*/
@Override
public String getId() {
- return "file-properties";
+ return PROVIDER_NAME;
}
}
diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportImportSessionTask.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportImportSessionTask.java
new file mode 100644
index 0000000..185a966
--- /dev/null
+++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportImportSessionTask.java
@@ -0,0 +1,25 @@
+package org.keycloak.exportimport.util;
+
+import java.io.IOException;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionTask;
+
+/**
+ * Just to wrap {@link IOException}
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class ExportImportSessionTask implements KeycloakSessionTask {
+
+ @Override
+ public void run(KeycloakSession session) {
+ try {
+ runExportImportTask(session);
+ } catch (IOException ioe) {
+ throw new RuntimeException("Error during export/import: " + ioe.getMessage(), ioe);
+ }
+ }
+
+ protected abstract void runExportImportTask(KeycloakSession session) throws IOException;
+}
diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
index aa4d83d..edc5b52 100755
--- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
+++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
@@ -77,7 +77,7 @@ public class ImportUtils {
// We just imported master realm. All 'masterAdminApps' need to be refreshed
RealmModel adminRealm = realm;
for (RealmModel currentRealm : model.getRealms()) {
- ApplicationModel masterApp = adminRealm.getApplicationByName(ExportImportUtils.getMasterRealmAdminApplicationName(currentRealm));
+ ApplicationModel masterApp = adminRealm.getApplicationByName(KeycloakModelUtils.getMasterRealmAdminApplicationName(currentRealm));
if (masterApp != null) {
currentRealm.setMasterAdminApp(masterApp);
} else {
@@ -87,7 +87,7 @@ public class ImportUtils {
} else {
// Need to refresh masterApp for current realm
RealmModel adminRealm = model.getRealm(adminRealmId);
- ApplicationModel masterApp = adminRealm.getApplicationByName(ExportImportUtils.getMasterRealmAdminApplicationName(realm));
+ ApplicationModel masterApp = adminRealm.getApplicationByName(KeycloakModelUtils.getMasterRealmAdminApplicationName(realm));
if (masterApp != null) {
realm.setMasterAdminApp(masterApp);
} else {
@@ -113,7 +113,7 @@ public class ImportUtils {
adminRole = adminRealm.getRole(AdminRoles.ADMIN);
}
- ApplicationModel realmAdminApp = KeycloakModelUtils.createApplication(adminRealm, ExportImportUtils.getMasterRealmAdminApplicationName(realm));
+ ApplicationModel realmAdminApp = KeycloakModelUtils.createApplication(adminRealm, KeycloakModelUtils.getMasterRealmAdminApplicationName(realm));
realmAdminApp.setBearerOnly(true);
realm.setMasterAdminApp(realmAdminApp);
diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/MultipleStepsExportProvider.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/MultipleStepsExportProvider.java
index 5899757..2f66d09 100755
--- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/MultipleStepsExportProvider.java
+++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/MultipleStepsExportProvider.java
@@ -6,8 +6,10 @@ import org.keycloak.exportimport.ExportProvider;
import org.keycloak.exportimport.UsersExportStrategy;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.RealmRepresentation;
import java.io.IOException;
@@ -24,7 +26,7 @@ public abstract class MultipleStepsExportProvider implements ExportProvider {
public void exportModel(KeycloakSessionFactory factory) throws IOException {
final RealmsHolder holder = new RealmsHolder();
- ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+ KeycloakModelUtils.runJobInTransaction(factory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
@@ -46,10 +48,10 @@ public abstract class MultipleStepsExportProvider implements ExportProvider {
final UsersHolder usersHolder = new UsersHolder();
final boolean exportUsersIntoRealmFile = usersExportStrategy == UsersExportStrategy.REALM_FILE;
- ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+ KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
- public void run(KeycloakSession session) throws IOException {
+ protected void runExportImportTask(KeycloakSession session) throws IOException {
RealmModel realm = session.realms().getRealmByName(realmName);
RealmRepresentation rep = ExportUtils.exportRealm(session, realm, exportUsersIntoRealmFile);
writeRealm(realmName + "-realm.json", rep);
@@ -77,10 +79,10 @@ public abstract class MultipleStepsExportProvider implements ExportProvider {
usersHolder.currentPageEnd = usersHolder.totalCount;
}
- ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+ KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
- public void run(KeycloakSession session) throws IOException {
+ protected void runExportImportTask(KeycloakSession session) throws IOException {
RealmModel realm = session.realms().getRealmByName(realmName);
usersHolder.users = session.users().getUsers(realm, usersHolder.currentPageStart, usersHolder.currentPageEnd - usersHolder.currentPageStart);
diff --git a/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java b/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java
index 4c532f0..7399574 100755
--- a/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java
+++ b/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java
@@ -4,11 +4,12 @@ import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.exportimport.ImportProvider;
import org.keycloak.exportimport.Strategy;
-import org.keycloak.exportimport.util.ExportImportJob;
-import org.keycloak.exportimport.util.ExportImportUtils;
+import org.keycloak.exportimport.util.ExportImportSessionTask;
import org.keycloak.exportimport.util.ImportUtils;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.util.JsonSerialization;
@@ -91,10 +92,10 @@ public class DirImportProvider implements ImportProvider {
FileInputStream is = new FileInputStream(realmFile);
final RealmRepresentation realmRep = JsonSerialization.readValue(is, RealmRepresentation.class);
- ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+ KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
- public void run(KeycloakSession session) throws IOException {
+ public void runExportImportTask(KeycloakSession session) throws IOException {
ImportUtils.importRealm(session, realmRep, strategy);
}
@@ -103,10 +104,10 @@ public class DirImportProvider implements ImportProvider {
// Import users
for (File userFile : userFiles) {
final FileInputStream fis = new FileInputStream(userFile);
- ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+ KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
- public void run(KeycloakSession session) throws IOException {
+ protected void runExportImportTask(KeycloakSession session) throws IOException {
ImportUtils.importUsersFromStream(session, realmName, JsonSerialization.mapper, fis);
}
});
diff --git a/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileExportProvider.java b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileExportProvider.java
index 17cec51..a8d7d98 100755
--- a/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileExportProvider.java
+++ b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileExportProvider.java
@@ -3,12 +3,13 @@ package org.keycloak.exportimport.singlefile;
import org.codehaus.jackson.map.ObjectMapper;
import org.jboss.logging.Logger;
import org.keycloak.exportimport.ExportProvider;
-import org.keycloak.exportimport.util.ExportImportJob;
-import org.keycloak.exportimport.util.ExportImportUtils;
+import org.keycloak.exportimport.util.ExportImportSessionTask;
import org.keycloak.exportimport.util.ExportUtils;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.util.JsonSerialization;
@@ -38,10 +39,10 @@ public class SingleFileExportProvider implements ExportProvider {
@Override
public void exportModel(KeycloakSessionFactory factory) throws IOException {
logger.infof("Exporting model into file %s", this.file.getAbsolutePath());
- ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+ KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
- public void run(KeycloakSession session) throws IOException {
+ protected void runExportImportTask(KeycloakSession session) throws IOException {
List<RealmModel> realms = session.realms().getRealms();
List<RealmRepresentation> reps = new ArrayList<RealmRepresentation>();
for (RealmModel realm : realms) {
@@ -58,10 +59,10 @@ public class SingleFileExportProvider implements ExportProvider {
@Override
public void exportRealm(KeycloakSessionFactory factory, final String realmName) throws IOException {
logger.infof("Exporting realm '%s' into file %s", realmName, this.file.getAbsolutePath());
- ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+ KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
- public void run(KeycloakSession session) throws IOException {
+ protected void runExportImportTask(KeycloakSession session) throws IOException {
RealmModel realm = session.realms().getRealmByName(realmName);
RealmRepresentation realmRep = ExportUtils.exportRealm(session, realm, true);
writeToFile(realmRep);
diff --git a/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java
index 6fbe1c8..a1e1109 100755
--- a/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java
+++ b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java
@@ -3,16 +3,15 @@ package org.keycloak.exportimport.singlefile;
import org.jboss.logging.Logger;
import org.keycloak.exportimport.ImportProvider;
import org.keycloak.exportimport.Strategy;
-import org.keycloak.exportimport.util.ExportImportJob;
-import org.keycloak.exportimport.util.ExportImportUtils;
import org.keycloak.exportimport.util.ImportUtils;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
-import org.keycloak.util.JsonSerialization;
-
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import org.keycloak.exportimport.util.ExportImportSessionTask;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -30,10 +29,10 @@ public class SingleFileImportProvider implements ImportProvider {
@Override
public void importModel(KeycloakSessionFactory factory, final Strategy strategy) throws IOException {
logger.infof("Full importing from file %s", this.file.getAbsolutePath());
- ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+ KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
- public void run(KeycloakSession session) throws IOException {
+ protected void runExportImportTask(KeycloakSession session) throws IOException {
FileInputStream is = new FileInputStream(file);
ImportUtils.importFromStream(session, JsonSerialization.mapper, is, strategy);
}
diff --git a/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java b/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java
index d9a93ee..69cfd46 100755
--- a/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java
+++ b/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java
@@ -8,11 +8,12 @@ import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.exportimport.ImportProvider;
import org.keycloak.exportimport.Strategy;
-import org.keycloak.exportimport.util.ExportImportJob;
-import org.keycloak.exportimport.util.ExportImportUtils;
+import org.keycloak.exportimport.util.ExportImportSessionTask;
import org.keycloak.exportimport.util.ImportUtils;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.util.JsonSerialization;
@@ -81,10 +82,10 @@ public class ZipImportProvider implements ImportProvider {
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
final RealmRepresentation realmRep = JsonSerialization.mapper.readValue(bis, RealmRepresentation.class);
- ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+ KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
- public void run(KeycloakSession session) throws IOException {
+ protected void runExportImportTask(KeycloakSession session) throws IOException {
ImportUtils.importRealm(session, realmRep, strategy);
}
@@ -99,10 +100,10 @@ public class ZipImportProvider implements ImportProvider {
this.decrypter.extractEntry(entry, bos, this.password);
final ByteArrayInputStream bis2 = new ByteArrayInputStream(bos.toByteArray());
- ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+ KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
- public void run(KeycloakSession session) throws IOException {
+ protected void runExportImportTask(KeycloakSession session) throws IOException {
ImportUtils.importUsersFromStream(session, realmName, JsonSerialization.mapper, bis2);
}
});
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 fed574b..85a049f 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
@@ -292,4 +292,29 @@ public class LDAPFederationProvider implements UserFederationProvider {
public void close() {
//To change body of implemented methods use File | Settings | File Templates.
}
+
+ protected void importPicketlinkUsers(RealmModel realm, List<User> users, UserFederationProviderModel fedModel) {
+ for (User picketlinkUser : users) {
+ String username = picketlinkUser.getLoginName();
+ UserModel currentUser = session.userStorage().getUserByUsername(username, realm);
+
+ if (currentUser == null) {
+ // Add new user to Keycloak
+ importUserFromPicketlink(realm, picketlinkUser);
+ logger.infof("Added new user from LDAP: " + username);
+ } else {
+ if ((fedModel.getId().equals(currentUser.getFederationLink())) && (picketlinkUser.getId().equals(currentUser.getAttribute(LDAPFederationProvider.LDAP_ID)))) {
+ // Update keycloak user
+ String email = (picketlinkUser.getEmail() != null && picketlinkUser.getEmail().trim().length() > 0) ? picketlinkUser.getEmail() : null;
+ currentUser.setEmail(email);
+ currentUser.setFirstName(picketlinkUser.getFirstName());
+ currentUser.setLastName(picketlinkUser.getLastName());
+ logger.infof("Updated user from LDAP: " + currentUser.getUsername());
+ } else {
+ // TODO: We have local user of same username like LDAP user, but not linked. What to do? Delete him and import again?
+ throw new IllegalStateException("User " + username + " has invalid LDAP ID or doesn't have federation link");
+ }
+ }
+ }
+ }
}
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 53aeb0c..88bf66f 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
@@ -1,14 +1,26 @@
package org.keycloak.federation.ldap;
+import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.picketlink.PartitionManagerProvider;
+import org.picketlink.idm.IdentityManager;
import org.picketlink.idm.PartitionManager;
+import org.picketlink.idm.model.IdentityType;
+import org.picketlink.idm.model.basic.User;
+import org.picketlink.idm.query.IdentityQuery;
import java.util.Collections;
+import java.util.Date;
+import java.util.List;
import java.util.Set;
/**
@@ -17,6 +29,7 @@ import java.util.Set;
* @version $Revision: 1 $
*/
public class LDAPFederationProviderFactory implements UserFederationProviderFactory {
+ private static final Logger logger = Logger.getLogger(LDAPFederationProviderFactory.class);
public static final String PROVIDER_NAME = "ldap";
@Override
@@ -25,7 +38,7 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
}
@Override
- public UserFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) {
+ public LDAPFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) {
PartitionManagerProvider idmProvider = session.getProvider(PartitionManagerProvider.class);
PartitionManager partition = idmProvider.getPartitionManager(model);
return new LDAPFederationProvider(session, model, partition);
@@ -49,4 +62,76 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
public Set<String> getConfigurationOptions() {
return Collections.emptySet();
}
+
+ @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());
+
+ PartitionManagerProvider idmProvider = sessionFactory.create().getProvider(PartitionManagerProvider.class);
+ PartitionManager partitionMgr = idmProvider.getPartitionManager(model);
+ IdentityQuery<User> userQuery = partitionMgr.createIdentityManager().createIdentityQuery(User.class);
+ 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?
+ }
+
+ @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: " + new Date() + ", last sync time: " + lastSync, realmId, model.getDisplayName());
+
+ PartitionManagerProvider idmProvider = sessionFactory.create().getProvider(PartitionManagerProvider.class);
+ PartitionManager partitionMgr = idmProvider.getPartitionManager(model);
+
+ // Sync newly created users
+ IdentityManager identityManager = partitionMgr.createIdentityManager();
+ IdentityQuery<User> userQuery = identityManager.createIdentityQuery(User.class)
+ .setParameter(IdentityType.CREATED_AFTER, lastSync);
+ syncImpl(sessionFactory, userQuery, realmId, model);
+
+ // Sync updated users
+ userQuery = identityManager.createIdentityQuery(User.class)
+ .setParameter(IdentityType.MODIFIED_AFTER, lastSync);
+ syncImpl(sessionFactory, userQuery, realmId, model);
+ }
+
+ protected void syncImpl(KeycloakSessionFactory sessionFactory, IdentityQuery<User> userQuery, final String realmId, final UserFederationProviderModel fedModel) {
+ 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);
+ final List<User> users = userQuery.getResultList();
+ nextPage = userQuery.getPaginationContext() != null;
+
+ KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+ @Override
+ public void run(KeycloakSession session) {
+ importPicketlinkUsers(session, realmId, fedModel, users);
+ }
+
+ });
+ }
+ } else {
+ // LDAP pagination not available. Do everything in single transaction
+ final List<User> users = userQuery.getResultList();
+ KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+ @Override
+ public void run(KeycloakSession session) {
+ importPicketlinkUsers(session, realmId, fedModel, users);
+ }
+
+ });
+ }
+ }
+
+ protected void importPicketlinkUsers(KeycloakSession session, String realmId, UserFederationProviderModel fedModel, List<User> users) {
+ RealmModel realm = session.realms().getRealm(realmId);
+ LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
+ ldapFedProvider.importPicketlinkUsers(realm, users, fedModel);
+ }
}
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 d67c499..f862d03 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
@@ -29,6 +29,17 @@ public class LDAPUtils {
return picketlinkUser;
}
+ public static User updateUser(PartitionManager partitionManager, String username, String firstName, String lastName, String email) {
+ IdentityManager idmManager = getIdentityManager(partitionManager);
+ User picketlinkUser = BasicModel.getUser(idmManager, username);
+ picketlinkUser.setFirstName(firstName);
+ picketlinkUser.setLastName(lastName);
+ picketlinkUser.setEmail(email);
+ picketlinkUser.setAttribute(new Attribute("fullName", getFullName(username, firstName, lastName)));
+ idmManager.update(picketlinkUser);
+ return picketlinkUser;
+ }
+
public static void updatePassword(PartitionManager partitionManager, User picketlinkUser, String password) {
IdentityManager idmManager = getIdentityManager(partitionManager);
idmManager.updateCredential(picketlinkUser, new Password(password.toCharArray()));
@@ -48,10 +59,6 @@ public class LDAPUtils {
}
}
- public static boolean isUserExists(PartitionManager partitionManager, String username) {
- return getUser(partitionManager, username) != null;
- }
-
public static User getUser(PartitionManager partitionManager, String username) {
IdentityManager idmManager = getIdentityManager(partitionManager);
return BasicModel.getUser(idmManager, username);
diff --git a/model/api/src/main/java/org/keycloak/models/entities/UserFederationProviderEntity.java b/model/api/src/main/java/org/keycloak/models/entities/UserFederationProviderEntity.java
index 6d3916e..fe00465 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/UserFederationProviderEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/UserFederationProviderEntity.java
@@ -12,6 +12,9 @@ public class UserFederationProviderEntity {
protected Map<String, String> config;
protected int priority;
protected String displayName;
+ private int fullSyncPeriod;
+ private int changedSyncPeriod;
+ private int lastSync;
public String getId() {
@@ -53,4 +56,28 @@ public class UserFederationProviderEntity {
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
+
+ public int getFullSyncPeriod() {
+ return fullSyncPeriod;
+ }
+
+ public void setFullSyncPeriod(int fullSyncPeriod) {
+ this.fullSyncPeriod = fullSyncPeriod;
+ }
+
+ public int getChangedSyncPeriod() {
+ return changedSyncPeriod;
+ }
+
+ public void setChangedSyncPeriod(int changedSyncPeriod) {
+ this.changedSyncPeriod = changedSyncPeriod;
+ }
+
+ public int getLastSync() {
+ return lastSync;
+ }
+
+ public void setLastSync(int lastSync) {
+ this.lastSync = lastSync;
+ }
}
diff --git a/model/api/src/main/java/org/keycloak/models/LDAPConstants.java b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
index 1328c01..38b538e 100644
--- a/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
+++ b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
@@ -22,5 +22,9 @@ public class LDAPConstants {
public static final String CONNECTION_POOLING = "connectionPooling";
public static final String PAGINATION = "pagination";
+ // Count of users processed per single transaction during sync process
+ public static final String BATCH_SIZE_FOR_SYNC = "batchSizeForSync";
+ public static final int DEFAULT_BATCH_SIZE_FOR_SYNC = 1000;
+
public static final String USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE = "userAccountControlsAfterPasswordUpdate";
}
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 633756f..4ae280f 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -161,7 +161,7 @@ public interface RealmModel extends RoleContainerModel {
List<UserFederationProviderModel> getUserFederationProviders();
- UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName);
+ UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync);
void updateUserFederationProvider(UserFederationProviderModel provider);
void removeUserFederationProvider(UserFederationProviderModel provider);
void setUserFederationProviders(List<UserFederationProviderModel> providers);
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 956779f..4247649 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java
@@ -2,6 +2,7 @@ package org.keycloak.models;
import org.keycloak.provider.ProviderFactory;
+import java.util.Date;
import java.util.Set;
/**
@@ -32,4 +33,26 @@ public interface UserFederationProviderFactory extends ProviderFactory<UserFeder
*/
@Override
String getId();
+
+ /**
+ * Sync all users from the provider storage to Keycloak storage.
+ *
+ * @param sessionFactory
+ * @param realmId
+ * @param model
+ */
+ void 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)
+ *
+ * @param sessionFactory
+ * @param realmId
+ * @param model
+ * @param lastSync
+ */
+ void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync);
}
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationProviderModel.java b/model/api/src/main/java/org/keycloak/models/UserFederationProviderModel.java
index 3f6c451..9c3bf1c 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationProviderModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationProviderModel.java
@@ -16,10 +16,13 @@ public class UserFederationProviderModel {
private Map<String, String> config = new HashMap<String, String>();
private int priority;
private String displayName;
+ private int fullSyncPeriod = -1; // In seconds. -1 means that periodic full sync is disabled
+ private int changedSyncPeriod = -1; // In seconds. -1 means that periodic changed sync is disabled
+ private int lastSync; // Date when last sync was done for this provider
public UserFederationProviderModel() {};
- public UserFederationProviderModel(String id, String providerName, Map<String, String> config, int priority, String displayName) {
+ public UserFederationProviderModel(String id, String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) {
this.id = id;
this.providerName = providerName;
if (config != null) {
@@ -27,6 +30,9 @@ public class UserFederationProviderModel {
}
this.priority = priority;
this.displayName = displayName;
+ this.fullSyncPeriod = fullSyncPeriod;
+ this.changedSyncPeriod = changedSyncPeriod;
+ this.lastSync = lastSync;
}
public String getId() {
@@ -64,4 +70,28 @@ public class UserFederationProviderModel {
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
+
+ public int getFullSyncPeriod() {
+ return fullSyncPeriod;
+ }
+
+ public void setFullSyncPeriod(int fullSyncPeriod) {
+ this.fullSyncPeriod = fullSyncPeriod;
+ }
+
+ public int getChangedSyncPeriod() {
+ return changedSyncPeriod;
+ }
+
+ public void setChangedSyncPeriod(int changedSyncPeriod) {
+ this.changedSyncPeriod = changedSyncPeriod;
+ }
+
+ public int getLastSync() {
+ return lastSync;
+ }
+
+ public void setLastSync(int lastSync) {
+ this.lastSync = lastSync;
+ }
}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index ca07fdb..2f329b7 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -5,6 +5,9 @@ import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClaimMask;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
+import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
@@ -132,4 +135,36 @@ public final class KeycloakModelUtils {
}
return user;
}
+
+ /**
+ * Wrap given runnable job into KeycloakTransaction.
+ *
+ * @param factory
+ * @param task
+ */
+ public static void runJobInTransaction(KeycloakSessionFactory factory, KeycloakSessionTask task) {
+ KeycloakSession session = factory.create();
+ KeycloakTransaction tx = session.getTransaction();
+ try {
+ tx.begin();
+ task.run(session);
+
+ if (tx.isActive()) {
+ if (tx.getRollbackOnly()) {
+ tx.rollback();
+ } else {
+ tx.commit();
+ }
+ }
+ } finally {
+ if (tx.isActive()) {
+ tx.rollback();
+ }
+ session.close();
+ }
+ }
+
+ public static String getMasterRealmAdminApplicationName(RealmModel realm) {
+ return realm.getName() + "-realm";
+ }
}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index ab3c865..4499465 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -262,6 +262,9 @@ public class ModelToRepresentation {
rep.setProviderName(model.getProviderName());
rep.setPriority(model.getPriority());
rep.setDisplayName(model.getDisplayName());
+ rep.setFullSyncPeriod(model.getFullSyncPeriod());
+ rep.setChangedSyncPeriod(model.getChangedSyncPeriod());
+ rep.setLastSync(model.getLastSync());
return rep;
}
}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 66c8be9..c7d7c26 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -288,7 +288,8 @@ public class RepresentationToModel {
for (UserFederationProviderRepresentation representation : providers) {
UserFederationProviderModel model = new UserFederationProviderModel(representation.getId(), representation.getProviderName(),
- representation.getConfig(), representation.getPriority(), representation.getDisplayName());
+ representation.getConfig(), representation.getPriority(), representation.getDisplayName(),
+ representation.getFullSyncPeriod(), representation.getChangedSyncPeriod(), representation.getLastSync());
result.add(model);
}
return result;
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index 5268441..9fb8a1e 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -594,9 +594,9 @@ public class RealmAdapter implements RealmModel {
}
@Override
- public UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName) {
+ public UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) {
getDelegateForUpdate();
- return updated.addUserFederationProvider(providerName, config, priority, displayName);
+ return updated.addUserFederationProvider(providerName, config, priority, displayName, fullSyncPeriod, changedSyncPeriod, lastSync);
}
@Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
index aec144e..22370f3 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
@@ -42,6 +42,13 @@ public class UserFederationProviderEntity {
@Column(name="DISPLAY_NAME")
private String displayName;
+ @Column(name="FULL_SYNC_PERIOD")
+ private int fullSyncPeriod;
+ @Column(name="CHANGED_SYNC_PERIOD")
+ private int changedSyncPeriod;
+ @Column(name="LAST_SYNC")
+ private int lastSync;
+
public String getId() {
return id;
}
@@ -89,4 +96,28 @@ public class UserFederationProviderEntity {
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
+
+ public int getFullSyncPeriod() {
+ return fullSyncPeriod;
+ }
+
+ public void setFullSyncPeriod(int fullSyncPeriod) {
+ this.fullSyncPeriod = fullSyncPeriod;
+ }
+
+ public int getChangedSyncPeriod() {
+ return changedSyncPeriod;
+ }
+
+ public void setChangedSyncPeriod(int changedSyncPeriod) {
+ this.changedSyncPeriod = changedSyncPeriod;
+ }
+
+ public int getLastSync() {
+ return lastSync;
+ }
+
+ public void setLastSync(int lastSync) {
+ this.lastSync = lastSync;
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index c53354e..4f0da3c 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -676,14 +676,15 @@ public class RealmAdapter implements RealmModel {
});
List<UserFederationProviderModel> result = new ArrayList<UserFederationProviderModel>();
for (UserFederationProviderEntity entity : copy) {
- result.add(new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName()));
+ result.add(new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName(),
+ entity.getFullSyncPeriod(), entity.getChangedSyncPeriod(), entity.getLastSync()));
}
return result;
}
@Override
- public UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName) {
+ public UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) {
String id = KeycloakModelUtils.generateId();
UserFederationProviderEntity entity = new UserFederationProviderEntity();
entity.setId(id);
@@ -695,10 +696,13 @@ public class RealmAdapter implements RealmModel {
displayName = id;
}
entity.setDisplayName(displayName);
+ entity.setFullSyncPeriod(fullSyncPeriod);
+ entity.setChangedSyncPeriod(changedSyncPeriod);
+ entity.setLastSync(lastSync);
em.persist(entity);
realm.getUserFederationProviders().add(entity);
em.flush();
- return new UserFederationProviderModel(entity.getId(), providerName, config, priority, displayName);
+ return new UserFederationProviderModel(entity.getId(), providerName, config, priority, displayName, fullSyncPeriod, changedSyncPeriod, lastSync);
}
@Override
@@ -728,6 +732,9 @@ public class RealmAdapter implements RealmModel {
entity.setPriority(model.getPriority());
entity.setProviderName(model.getProviderName());
entity.setPriority(model.getPriority());
+ entity.setFullSyncPeriod(model.getFullSyncPeriod());
+ entity.setChangedSyncPeriod(model.getChangedSyncPeriod());
+ entity.setLastSync(model.getLastSync());
break;
}
}
@@ -750,13 +757,17 @@ public class RealmAdapter implements RealmModel {
if (displayName != null) {
entity.setDisplayName(model.getDisplayName());
}
+ entity.setFullSyncPeriod(model.getFullSyncPeriod());
+ entity.setChangedSyncPeriod(model.getChangedSyncPeriod());
+ entity.setLastSync(model.getLastSync());
found = true;
break;
}
}
if (found) continue;
- session.users().preRemove(this, new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName()));
+ session.users().preRemove(this, new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName(),
+ entity.getFullSyncPeriod(), entity.getChangedSyncPeriod(), entity.getLastSync()));
it.remove();
em.remove(entity);
}
@@ -786,6 +797,9 @@ public class RealmAdapter implements RealmModel {
displayName = entity.getId();
}
entity.setDisplayName(displayName);
+ entity.setFullSyncPeriod(model.getFullSyncPeriod());
+ entity.setChangedSyncPeriod(model.getChangedSyncPeriod());
+ entity.setLastSync(model.getLastSync());
em.persist(entity);
realm.getUserFederationProviders().add(entity);
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index f275dee..893f52e 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -757,7 +757,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
}
@Override
- public UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName) {
+ public UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) {
UserFederationProviderEntity entity = new UserFederationProviderEntity();
entity.setId(KeycloakModelUtils.generateId());
entity.setPriority(priority);
@@ -767,10 +767,13 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
displayName = entity.getId();
}
entity.setDisplayName(displayName);
+ entity.setFullSyncPeriod(fullSyncPeriod);
+ entity.setChangedSyncPeriod(changedSyncPeriod);
+ entity.setLastSync(lastSync);
realm.getUserFederationProviders().add(entity);
updateRealm();
- return new UserFederationProviderModel(entity.getId(), providerName, config, priority, displayName);
+ return new UserFederationProviderModel(entity.getId(), providerName, config, priority, displayName, fullSyncPeriod, changedSyncPeriod, lastSync);
}
@Override
@@ -779,7 +782,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
while (it.hasNext()) {
UserFederationProviderEntity entity = it.next();
if (entity.getId().equals(provider.getId())) {
- session.users().preRemove(this, new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName()));
+ session.users().preRemove(this, new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName(),
+ entity.getFullSyncPeriod(), entity.getChangedSyncPeriod(), entity.getLastSync()));
it.remove();
}
}
@@ -799,6 +803,9 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
if (displayName != null) {
entity.setDisplayName(model.getDisplayName());
}
+ entity.setFullSyncPeriod(model.getFullSyncPeriod());
+ entity.setChangedSyncPeriod(model.getChangedSyncPeriod());
+ entity.setLastSync(model.getLastSync());
}
}
updateRealm();
@@ -822,7 +829,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
});
List<UserFederationProviderModel> result = new LinkedList<UserFederationProviderModel>();
for (UserFederationProviderEntity entity : copy) {
- result.add(new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName()));
+ result.add(new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName(),
+ entity.getFullSyncPeriod(), entity.getChangedSyncPeriod(), entity.getLastSync()));
}
return result;
@@ -843,6 +851,9 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
entity.setDisplayName(entity.getId());
}
entity.setDisplayName(displayName);
+ entity.setFullSyncPeriod(model.getFullSyncPeriod());
+ entity.setChangedSyncPeriod(model.getChangedSyncPeriod());
+ entity.setLastSync(model.getLastSync());
entities.add(entity);
}
diff --git a/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/PartitionManagerRegistry.java b/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/PartitionManagerRegistry.java
index ec1ba58..6fc5237 100755
--- a/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/PartitionManagerRegistry.java
+++ b/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/PartitionManagerRegistry.java
@@ -11,13 +11,17 @@ import org.picketlink.idm.config.LDAPMappingConfigurationBuilder;
import org.picketlink.idm.config.LDAPStoreConfigurationBuilder;
import org.picketlink.idm.internal.DefaultPartitionManager;
import org.picketlink.idm.model.basic.User;
-
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
-import static org.picketlink.common.constants.LDAPConstants.*;
+import static org.picketlink.common.constants.LDAPConstants.CN;
+import static org.picketlink.common.constants.LDAPConstants.EMAIL;
+import static org.picketlink.common.constants.LDAPConstants.SN;
+import static org.picketlink.common.constants.LDAPConstants.UID;
+import static org.picketlink.common.constants.LDAPConstants.CREATE_TIMESTAMP;
+import static org.picketlink.common.constants.LDAPConstants.MODIFY_TIMESTAMP;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -80,6 +84,8 @@ public class PartitionManagerRegistry {
}
String ldapFirstNameMapping = activeDirectory ? "givenName" : CN;
+ String createTimestampMapping = activeDirectory ? "whenCreated" : CREATE_TIMESTAMP;
+ String modifyTimestampMapping = activeDirectory ? "whenChanged" : MODIFY_TIMESTAMP;
String[] userObjectClasses = getUserObjectClasses(ldapConfig);
boolean pagination = ldapConfig.containsKey(LDAPConstants.PAGINATION) ? Boolean.parseBoolean(ldapConfig.get(LDAPConstants.PAGINATION)) : false;
@@ -112,7 +118,9 @@ public class PartitionManagerRegistry {
.attribute("loginName", ldapLoginNameMapping, true)
.attribute("firstName", ldapFirstNameMapping)
.attribute("lastName", SN)
- .attribute("email", EMAIL);
+ .attribute("email", EMAIL)
+ .readOnlyAttribute("createdDate", createTimestampMapping)
+ .readOnlyAttribute("modifyDate", modifyTimestampMapping);
if (activeDirectory && ldapLoginNameMapping.equals("sAMAccountName")) {
ldapUserMappingBuilder.bindingAttribute("fullName", CN);
diff --git a/services/src/main/java/org/keycloak/services/managers/PeriodicSyncManager.java b/services/src/main/java/org/keycloak/services/managers/PeriodicSyncManager.java
new file mode 100644
index 0000000..7a86529
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/managers/PeriodicSyncManager.java
@@ -0,0 +1,105 @@
+package org.keycloak.services.managers;
+
+import java.util.List;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationProviderFactory;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.timer.TimerProvider;
+import org.keycloak.util.Time;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PeriodicSyncManager {
+
+ /**
+ * Check federationProviderModel of all realms and possibly start periodic sync for them
+ *
+ * @param sessionFactory
+ * @param timer
+ */
+ public void bootstrap(final KeycloakSessionFactory sessionFactory, final TimerProvider timer) {
+ KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+ @Override
+ public void run(KeycloakSession session) {
+ List<RealmModel> realms = session.realms().getRealms();
+ for (final RealmModel realm : realms) {
+ List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
+ for (final UserFederationProviderModel fedProvider : federationProviders) {
+ startPeriodicSyncForProvider(sessionFactory, timer, fedProvider, realm.getId());
+ }
+ }
+ }
+ });
+ }
+
+ public void startPeriodicSyncForProvider(final KeycloakSessionFactory sessionFactory, TimerProvider timer, final UserFederationProviderModel fedProvider, final String realmId) {
+ final UserFederationProviderFactory fedProviderFactory = (UserFederationProviderFactory) sessionFactory.getProviderFactory(UserFederationProvider.class, fedProvider.getProviderName());
+
+ if (fedProvider.getFullSyncPeriod() > 0) {
+ // We want periodic full sync for this provider
+ timer.schedule(new Runnable() {
+
+ @Override
+ public void run() {
+ updateLastSyncInterval(sessionFactory, fedProvider, realmId);
+ fedProviderFactory.syncAllUsers(sessionFactory, realmId, fedProvider);
+ }
+
+ }, fedProvider.getFullSyncPeriod() * 1000, fedProvider.getId() + "-FULL");
+ }
+
+ if (fedProvider.getChangedSyncPeriod() > 0) {
+ // We want periodic sync of just changed users for this provider
+ timer.schedule(new Runnable() {
+
+ @Override
+ public void run() {
+ // See when we did last sync.
+ int oldLastSync = fedProvider.getLastSync();
+ updateLastSyncInterval(sessionFactory, fedProvider, realmId);
+ fedProviderFactory.syncChangedUsers(sessionFactory, realmId, fedProvider, Time.toDate(oldLastSync));
+ }
+
+ }, fedProvider.getChangedSyncPeriod() * 1000, fedProvider.getId() + "-CHANGED");
+
+ }
+ }
+
+ public void removePeriodicSyncForProvider(TimerProvider timer, final UserFederationProviderModel fedProvider) {
+ timer.cancelTask(fedProvider.getId() + "-FULL");
+ timer.cancelTask(fedProvider.getId() + "-CHANGED");
+ }
+
+ // Update interval of last sync for given UserFederationProviderModel. Do it in separate transaction
+ private void updateLastSyncInterval(final KeycloakSessionFactory sessionFactory, final UserFederationProviderModel fedProvider, final String realmId) {
+ KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+ @Override
+ public void run(KeycloakSession session) {
+ RealmModel persistentRealm = session.realms().getRealm(realmId);
+ List<UserFederationProviderModel> persistentFedProviders = persistentRealm.getUserFederationProviders();
+ for (UserFederationProviderModel persistentFedProvider : persistentFedProviders) {
+ if (fedProvider.getId().equals(persistentFedProvider.getId())) {
+ // Update persistent provider in DB
+ int lastSync = Time.currentTime();
+ persistentFedProvider.setLastSync(lastSync);
+ persistentRealm.updateUserFederationProvider(persistentFedProvider);
+
+ // Update "cached" reference
+ fedProvider.setLastSync(lastSync);
+ }
+ }
+ }
+
+ });
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index ae1e116..c8b613c 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -12,12 +12,14 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.RealmAuditRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.timer.TimerProvider;
import java.util.Collections;
import java.util.HashSet;
@@ -127,6 +129,8 @@ public class RealmManager {
}
public boolean removeRealm(RealmModel realm) {
+ List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
+
boolean removed = model.removeRealm(realm.getId());
if (removed) {
new ApplicationManager(this).removeApplication(getKeycloakAdminstrationRealm(), realm.getMasterAdminApp());
@@ -135,6 +139,12 @@ public class RealmManager {
if (sessions != null) {
sessions.onRealmRemoved(realm);
}
+
+ // Remove all periodic syncs for configured federation providers
+ PeriodicSyncManager periodicSyncManager = new PeriodicSyncManager();
+ for (final UserFederationProviderModel fedProvider : federationProviders) {
+ periodicSyncManager.removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), fedProvider);
+ }
}
return removed;
}
@@ -202,6 +212,13 @@ public class RealmManager {
public void importRealm(RealmRepresentation rep, RealmModel newRealm) {
RepresentationToModel.importRealm(session, rep, newRealm);
+
+ // Refresh periodic sync tasks for configured federationProviders
+ List<UserFederationProviderModel> federationProviders = newRealm.getUserFederationProviders();
+ PeriodicSyncManager periodicSyncManager = new PeriodicSyncManager();
+ for (final UserFederationProviderModel fedProvider : federationProviders) {
+ periodicSyncManager.startPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), fedProvider, newRealm.getId());
+ }
}
/**
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 9344d28..b6af7a4 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -12,6 +12,7 @@ import org.keycloak.models.ApplicationModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.cache.CacheRealmProvider;
import org.keycloak.models.cache.CacheUserProvider;
@@ -21,10 +22,12 @@ import org.keycloak.representations.adapters.action.SessionStats;
import org.keycloak.representations.idm.RealmAuditRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.LDAPConnectionTestManager;
+import org.keycloak.services.managers.PeriodicSyncManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.resources.flows.Flows;
+import org.keycloak.timer.TimerProvider;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@@ -160,6 +163,13 @@ public class RealmAdminResource {
cache.setEnabled(rep.isUserCacheEnabled());
}
+ // Refresh periodic sync tasks for configured federationProviders
+ List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
+ PeriodicSyncManager periodicSyncManager = new PeriodicSyncManager();
+ for (final UserFederationProviderModel fedProvider : federationProviders) {
+ periodicSyncManager.startPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), fedProvider, realm.getId());
+ }
+
return Response.noContent().build();
} catch (ModelDuplicateException e) {
return Flows.errors().exists("Realm " + rep.getRealm() + " already exists");
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 c156c4b..6039ccb 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,8 @@ import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation;
import org.keycloak.representations.idm.UserFederationProviderRepresentation;
+import org.keycloak.services.managers.PeriodicSyncManager;
+import org.keycloak.timer.TimerProvider;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@@ -116,7 +118,10 @@ public class UserFederationResource {
if (displayName != null && displayName.trim().equals("")) {
displayName = null;
}
- UserFederationProviderModel model = realm.addUserFederationProvider(rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName);
+ UserFederationProviderModel model = realm.addUserFederationProvider(rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName,
+ rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
+ new PeriodicSyncManager().startPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), model, realm.getId());
+
return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build();
}
@@ -136,8 +141,10 @@ public class UserFederationResource {
if (displayName != null && displayName.trim().equals("")) {
displayName = null;
}
- UserFederationProviderModel model = new UserFederationProviderModel(id, rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName);
+ UserFederationProviderModel model = new UserFederationProviderModel(id, rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName,
+ rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
realm.updateUserFederationProvider(model);
+ new PeriodicSyncManager().startPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), model, realm.getId());
}
/**
@@ -170,9 +177,9 @@ public class UserFederationResource {
public void deleteProviderInstance(@PathParam("id") String id) {
logger.info("deleteProvider");
auth.requireManage();
- UserFederationProviderModel model = new UserFederationProviderModel(id, null, null, -1, null);
+ UserFederationProviderModel model = new UserFederationProviderModel(id, null, null, -1, null, -1, -1, 0);
realm.removeUserFederationProvider(model);
-
+ new PeriodicSyncManager().removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), model);
}
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 77da618..ca8db44 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -84,8 +84,8 @@ public class KeycloakApplication extends Application {
setupDefaultRealm(context.getContextPath());
- setupScheduledTasks(sessionFactory);
importRealms(context);
+ setupScheduledTasks(sessionFactory);
}
public String getContextPath() {
@@ -146,8 +146,8 @@ public class KeycloakApplication extends Application {
long interval = Config.scope("scheduled").getLong("interval", 60L) * 1000;
TimerProvider timer = sessionFactory.create().getProvider(TimerProvider.class);
- timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredAuditEvents()), interval);
- timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions()), interval);
+ timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredAuditEvents()), interval, "ClearExpiredAuditEvents");
+ timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions()), interval, "ClearExpiredUserSessions");
}
public KeycloakSessionFactory getSessionFactory() {
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProvider.java b/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProvider.java
index 04ac0a1..753409a 100755
--- a/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProvider.java
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProvider.java
@@ -38,7 +38,7 @@ public class DummyUserFederationProvider implements UserFederationProvider {
@Override
public boolean removeUser(RealmModel realm, UserModel user) {
- return true;
+ return users.remove(user.getUsername()) != null;
}
@Override
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProviderFactory.java b/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProviderFactory.java
index a5a0b6e..2809544 100755
--- a/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProviderFactory.java
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProviderFactory.java
@@ -1,19 +1,29 @@
package org.keycloak.testutils;
+import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
-
+import java.util.Date;
import java.util.HashSet;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class DummyUserFederationProviderFactory implements UserFederationProviderFactory {
+
+ private static final Logger logger = Logger.getLogger(DummyUserFederationProviderFactory.class);
+ public static final String PROVIDER_NAME = "dummy";
+
+ private AtomicInteger fullSyncCounter = new AtomicInteger();
+ private AtomicInteger changedSyncCounter = new AtomicInteger();
+
@Override
public UserFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) {
return new DummyUserFederationProvider();
@@ -43,6 +53,26 @@ public class DummyUserFederationProviderFactory implements UserFederationProvide
@Override
public String getId() {
- return "dummy";
+ return PROVIDER_NAME;
+ }
+
+ @Override
+ public void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
+ logger.info("syncAllUsers invoked");
+ fullSyncCounter.incrementAndGet();
+ }
+
+ @Override
+ public void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
+ logger.info("syncChangedUsers invoked");
+ changedSyncCounter.incrementAndGet();
+ }
+
+ public int getFullSyncCounter() {
+ return fullSyncCounter.get();
+ }
+
+ public int getChangedSyncCounter() {
+ return changedSyncCounter.get();
}
}
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/LDAPEmbeddedServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/LDAPEmbeddedServer.java
index 466952a..1c10b24 100755
--- a/testsuite/integration/src/main/java/org/keycloak/testutils/LDAPEmbeddedServer.java
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/LDAPEmbeddedServer.java
@@ -42,6 +42,7 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
protected String vendor = LDAPConstants.VENDOR_OTHER;
protected boolean connectionPooling = true;
protected boolean pagination = true;
+ protected int batchSizeForSync = LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC;
public static String IDM_TEST_LDAP_CONNECTION_URL = "idm.test.ldap.connection.url";
public static String IDM_TEST_LDAP_BASE_DN = "idm.test.ldap.base.dn";
@@ -55,6 +56,7 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
public static String IDM_TEST_LDAP_VENDOR = "idm.test.ldap.vendor";
public static String IDM_TEST_LDAP_CONNECTION_POOLING = "idm.test.ldap.connection.pooling";
public static String IDM_TEST_LDAP_PAGINATION = "idm.test.ldap.pagination";
+ public static String IDM_TEST_LDAP_BATCH_SIZE_FOR_SYNC = "idm.test.ldap.batch.size.for.sync";
public LDAPEmbeddedServer() {
@@ -84,6 +86,7 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
vendor = p.getProperty(IDM_TEST_LDAP_VENDOR);
connectionPooling = Boolean.parseBoolean(p.getProperty(IDM_TEST_LDAP_CONNECTION_POOLING, "true"));
pagination = Boolean.parseBoolean(p.getProperty(IDM_TEST_LDAP_PAGINATION, "true"));
+ batchSizeForSync = Integer.parseInt(p.getProperty(IDM_TEST_LDAP_BATCH_SIZE_FOR_SYNC, String.valueOf(batchSizeForSync)));
}
@Override
@@ -134,6 +137,7 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
ldapConfig.put(LDAPConstants.VENDOR, getVendor());
ldapConfig.put(LDAPConstants.CONNECTION_POOLING, String.valueOf(isConnectionPooling()));
ldapConfig.put(LDAPConstants.PAGINATION, String.valueOf(isPagination()));
+ ldapConfig.put(LDAPConstants.BATCH_SIZE_FOR_SYNC, String.valueOf(getBatchSizeForSync()));
return ldapConfig;
}
@@ -216,6 +220,10 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
return pagination;
}
+ public int getBatchSizeForSync() {
+ return batchSizeForSync;
+ }
+
@Override
public void importLDIF(String fileName) throws Exception {
// import LDIF only in case we are running against embedded LDAP server
diff --git a/testsuite/integration/src/main/resources/ldap/ldap-connection.properties b/testsuite/integration/src/main/resources/ldap/ldap-connection.properties
index 6119852..c275c2f 100644
--- a/testsuite/integration/src/main/resources/ldap/ldap-connection.properties
+++ b/testsuite/integration/src/main/resources/ldap/ldap-connection.properties
@@ -8,4 +8,5 @@ idm.test.ldap.start.embedded.ldap.server=true
idm.test.ldap.bind.dn=uid\=admin,ou\=system
idm.test.ldap.bind.credential=secret
idm.test.ldap.connection.pooling=true
-idm.test.ldap.pagination=true
\ No newline at end of file
+idm.test.ldap.pagination=true
+idm.test.ldap.batch.size.for.sync=3
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
index 791e686..99f6558 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
@@ -60,7 +60,7 @@ public class FederationProvidersIntegrationTest {
ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "true");
ldapConfig.put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
- ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap");
+ ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0);
// Delete all LDAP users and add some new for testing
PartitionManager partitionManager = getPartitionManager(manager.getSession(), ldapModel);
@@ -102,7 +102,7 @@ public class FederationProvidersIntegrationTest {
@WebResource
protected AccountPasswordPage changePasswordPage;
- private static UserModel addUser(KeycloakSession session, RealmModel realm, String username, String email, String password) {
+ static UserModel addUser(KeycloakSession session, RealmModel realm, String username, String email, String password) {
UserModel user = session.users().addUser(realm, username);
user.setEmail(email);
user.setEnabled(true);
@@ -166,7 +166,7 @@ public class FederationProvidersIntegrationTest {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
- ldapModel = appRealm.addUserFederationProvider(ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(), ldapModel.getDisplayName());
+ ldapModel = appRealm.addUserFederationProvider(ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(), ldapModel.getDisplayName(), -1, -1, 0);
} finally {
keycloakRule.stopSession(session, true);
}
@@ -233,7 +233,8 @@ public class FederationProvidersIntegrationTest {
try {
RealmModel appRealm = session.realms().getRealmByName("test");
- UserFederationProviderModel model = new UserFederationProviderModel(ldapModel.getId(), ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(), ldapModel.getDisplayName());
+ UserFederationProviderModel model = new UserFederationProviderModel(ldapModel.getId(), ldapModel.getProviderName(), ldapModel.getConfig(),
+ ldapModel.getPriority(), ldapModel.getDisplayName(), -1, -1, 0);
model.getConfig().put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.READ_ONLY.toString());
appRealm.updateUserFederationProvider(model);
UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm);
@@ -284,7 +285,8 @@ public class FederationProvidersIntegrationTest {
try {
RealmModel appRealm = session.realms().getRealmByName("test");
- UserFederationProviderModel model = new UserFederationProviderModel(ldapModel.getId(), ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(), ldapModel.getDisplayName());
+ UserFederationProviderModel model = new UserFederationProviderModel(ldapModel.getId(), ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(),
+ ldapModel.getDisplayName(), -1, -1, 0);
model.getConfig().put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString());
appRealm.updateUserFederationProvider(model);
UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm);
@@ -313,7 +315,7 @@ public class FederationProvidersIntegrationTest {
}
}
- private static PartitionManager getPartitionManager(KeycloakSession keycloakSession, UserFederationProviderModel ldapFedModel) {
+ static PartitionManager getPartitionManager(KeycloakSession keycloakSession, UserFederationProviderModel ldapFedModel) {
PartitionManagerProvider partitionManagerProvider = keycloakSession.getProvider(PartitionManagerProvider.class);
return partitionManagerProvider.getPartitionManager(ldapFedModel);
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java
new file mode 100644
index 0000000..21ac290
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java
@@ -0,0 +1,186 @@
+package org.keycloak.testsuite.forms;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runners.MethodSorters;
+import org.keycloak.examples.federation.properties.ClasspathPropertiesFederationFactory;
+import org.keycloak.federation.ldap.LDAPFederationProvider;
+import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
+import org.keycloak.federation.ldap.LDAPUtils;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationProviderFactory;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserProvider;
+import org.keycloak.services.managers.PeriodicSyncManager;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.LDAPRule;
+import org.keycloak.testutils.DummyUserFederationProvider;
+import org.keycloak.testutils.DummyUserFederationProviderFactory;
+import org.keycloak.testutils.LDAPEmbeddedServer;
+import org.keycloak.timer.TimerProvider;
+import org.picketlink.idm.PartitionManager;
+import org.picketlink.idm.model.basic.User;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class SyncProvidersTest {
+
+ private static LDAPRule ldapRule = new LDAPRule();
+
+ private static UserFederationProviderModel ldapModel = null;
+ private static UserFederationProviderModel dummyModel = null;
+
+ private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ LDAPEmbeddedServer ldapServer = ldapRule.getEmbeddedServer();
+ Map<String,String> ldapConfig = ldapServer.getLDAPConfig();
+ ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "false");
+ ldapConfig.put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString());
+
+ ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap",
+ -1, -1, 0);
+
+ // Delete all LDAP users and add 5 new users for testing
+ PartitionManager partitionManager = FederationProvidersIntegrationTest.getPartitionManager(manager.getSession(), ldapModel);
+ LDAPUtils.removeAllUsers(partitionManager);
+
+ User user1 = LDAPUtils.addUser(partitionManager, "user1", "User1FN", "User1LN", "user1@email.org");
+ LDAPUtils.updatePassword(partitionManager, user1, "password1");
+ User user2 = LDAPUtils.addUser(partitionManager, "user2", "User2FN", "User2LN", "user2@email.org");
+ LDAPUtils.updatePassword(partitionManager, user2, "password2");
+ User user3 = LDAPUtils.addUser(partitionManager, "user3", "User3FN", "User3LN", "user3@email.org");
+ LDAPUtils.updatePassword(partitionManager, user3, "password3");
+ User user4 = LDAPUtils.addUser(partitionManager, "user4", "User4FN", "User4LN", "user4@email.org");
+ LDAPUtils.updatePassword(partitionManager, user4, "password4");
+ User user5 = LDAPUtils.addUser(partitionManager, "user5", "User5FN", "User5LN", "user5@email.org");
+ LDAPUtils.updatePassword(partitionManager, user5, "password5");
+
+ // Add properties provider
+ dummyModel = appRealm.addUserFederationProvider(DummyUserFederationProviderFactory.PROVIDER_NAME, new HashMap<String, String>(), 1, "test-dummy", -1, 1, 0);
+ }
+ });
+
+ @ClassRule
+ public static TestRule chain = RuleChain
+ .outerRule(ldapRule)
+ .around(keycloakRule);
+
+ @Test
+ public void testLDAPSync() {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+ UserFederationProviderFactory ldapFedFactory = (UserFederationProviderFactory)sessionFactory.getProviderFactory(UserFederationProvider.class, LDAPFederationProviderFactory.PROVIDER_NAME);
+ ldapFedFactory.syncAllUsers(sessionFactory, "test", ldapModel);
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+
+ // Assert users imported (model test)
+ session = keycloakRule.startSession();
+ try {
+ RealmModel testRealm = session.realms().getRealm("test");
+ UserProvider userProvider = session.userStorage();
+ assertUserImported(userProvider, testRealm, "user1", "User1FN", "User1LN", "user1@email.org");
+ assertUserImported(userProvider, testRealm, "user2", "User2FN", "User2LN", "user2@email.org");
+ assertUserImported(userProvider, testRealm, "user3", "User3FN", "User3LN", "user3@email.org");
+ assertUserImported(userProvider, testRealm, "user4", "User4FN", "User4LN", "user4@email.org");
+ assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org");
+
+ // wait a bit
+ sleep(1000);
+ Date beforeLDAPUpdate = new Date();
+
+ // Add user to LDAP and update 'user5' in LDAP
+ PartitionManager partitionManager = FederationProvidersIntegrationTest.getPartitionManager(session, ldapModel);
+ LDAPUtils.addUser(partitionManager, "user6", "User6FN", "User6LN", "user6@email.org");
+ LDAPUtils.updateUser(partitionManager, "user5", "User5FNUpdated", "User5LNUpdated", "user5Updated@email.org");
+
+ // Assert still old users in local provider
+ assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org");
+ Assert.assertNull(userProvider.getUserByUsername("user6", testRealm));
+
+ // Trigger partial sync
+ KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+ UserFederationProviderFactory ldapFedFactory = (UserFederationProviderFactory)sessionFactory.getProviderFactory(UserFederationProvider.class, LDAPFederationProviderFactory.PROVIDER_NAME);
+ ldapFedFactory.syncChangedUsers(sessionFactory, "test", ldapModel, beforeLDAPUpdate);
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+
+ session = keycloakRule.startSession();
+ try {
+ RealmModel testRealm = session.realms().getRealm("test");
+ UserProvider userProvider = session.userStorage();
+ // Assert users updated in local provider
+ assertUserImported(userProvider, testRealm, "user5", "User5FNUpdated", "User5LNUpdated", "user5Updated@email.org");
+ assertUserImported(userProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org");
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+ }
+
+ @Test
+ public void testPeriodicSync() {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+ DummyUserFederationProviderFactory dummyFedFactory = (DummyUserFederationProviderFactory)sessionFactory.getProviderFactory(UserFederationProvider.class, DummyUserFederationProviderFactory.PROVIDER_NAME);
+ int full = dummyFedFactory.getFullSyncCounter();
+ int changed = dummyFedFactory.getChangedSyncCounter();
+
+ // Assert that after some period was DummyUserFederationProvider triggered
+ PeriodicSyncManager periodicSyncManager = new PeriodicSyncManager();
+ periodicSyncManager.bootstrap(sessionFactory, session.getProvider(TimerProvider.class));
+ sleep(1800);
+
+ // Cancel timer
+ periodicSyncManager.removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), dummyModel);
+
+ // Assert that DummyUserFederationProviderFactory.syncChangedUsers was invoked
+ int newChanged = dummyFedFactory.getChangedSyncCounter();
+ Assert.assertEquals(full, dummyFedFactory.getFullSyncCounter());
+ Assert.assertTrue(newChanged > changed);
+
+ // Assert that dummy provider won't be invoked anymore
+ sleep(1800);
+ Assert.assertEquals(full, dummyFedFactory.getFullSyncCounter());
+ Assert.assertEquals(newChanged, dummyFedFactory.getChangedSyncCounter());
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+ }
+
+ private void sleep(int time) {
+ try {
+ Thread.sleep(time);
+ } catch (InterruptedException ie) {
+ throw new RuntimeException(ie);
+ }
+ }
+
+ private void assertUserImported(UserProvider userProvider, RealmModel realm, String username, String expectedFirstName, String expectedLastName, String expectedEmail) {
+ UserModel user = userProvider.getUserByUsername(username, realm);
+ Assert.assertNotNull(user);
+ Assert.assertEquals(expectedFirstName, user.getFirstName());
+ Assert.assertEquals(expectedLastName, user.getLastName());
+ Assert.assertEquals(expectedEmail, user.getEmail());
+ }
+}
diff --git a/timer/api/src/main/java/org/keycloak/timer/TimerProvider.java b/timer/api/src/main/java/org/keycloak/timer/TimerProvider.java
index 627f00e..a8396de 100644
--- a/timer/api/src/main/java/org/keycloak/timer/TimerProvider.java
+++ b/timer/api/src/main/java/org/keycloak/timer/TimerProvider.java
@@ -7,6 +7,8 @@ import org.keycloak.provider.Provider;
*/
public interface TimerProvider extends Provider {
- public void schedule(Runnable runnable, long interval);
+ public void schedule(Runnable runnable, long interval, String taskName);
+
+ public void cancelTask(String taskName);
}
timer/basic/pom.xml 5(+5 -0)
diff --git a/timer/basic/pom.xml b/timer/basic/pom.xml
index 268c76a..658e4c5 100755
--- a/timer/basic/pom.xml
+++ b/timer/basic/pom.xml
@@ -31,6 +31,11 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/timer/basic/src/main/java/org/keycloak/timer/basic/BasicTimerProvider.java b/timer/basic/src/main/java/org/keycloak/timer/basic/BasicTimerProvider.java
index 267f17c..aa1a3f9 100644
--- a/timer/basic/src/main/java/org/keycloak/timer/basic/BasicTimerProvider.java
+++ b/timer/basic/src/main/java/org/keycloak/timer/basic/BasicTimerProvider.java
@@ -1,5 +1,6 @@
package org.keycloak.timer.basic;
+import org.jboss.logging.Logger;
import org.keycloak.timer.TimerProvider;
import java.util.Timer;
@@ -10,15 +11,18 @@ import java.util.TimerTask;
*/
public class BasicTimerProvider implements TimerProvider {
- private Timer timer;
+ private static final Logger logger = Logger.getLogger(BasicTimerProvider.class);
- public BasicTimerProvider(Timer timer) {
+ private final Timer timer;
+ private final BasicTimerProviderFactory factory;
+ public BasicTimerProvider(Timer timer, BasicTimerProviderFactory factory) {
this.timer = timer;
+ this.factory = factory;
}
@Override
- public void schedule(final Runnable runnable, final long interval) {
+ public void schedule(final Runnable runnable, final long interval, String taskName) {
TimerTask task = new TimerTask() {
@Override
public void run() {
@@ -26,10 +30,26 @@ public class BasicTimerProvider implements TimerProvider {
}
};
+ TimerTask existingTask = factory.putTask(taskName, task);
+ if (existingTask != null) {
+ logger.infof("Existing timer task '%s' found. Cancelling it", taskName);
+ existingTask.cancel();
+ }
+
+ logger.infof("Starting task '%s' with interval '%d'", taskName, interval);
timer.schedule(task, interval, interval);
}
@Override
+ public void cancelTask(String taskName) {
+ TimerTask existingTask = factory.removeTask(taskName);
+ if (existingTask != null) {
+ logger.infof("Cancelling task '%s'", taskName);
+ existingTask.cancel();
+ }
+ }
+
+ @Override
public void close() {
// do nothing
}
diff --git a/timer/basic/src/main/java/org/keycloak/timer/basic/BasicTimerProviderFactory.java b/timer/basic/src/main/java/org/keycloak/timer/basic/BasicTimerProviderFactory.java
index b0e5063..0069b2c 100644
--- a/timer/basic/src/main/java/org/keycloak/timer/basic/BasicTimerProviderFactory.java
+++ b/timer/basic/src/main/java/org/keycloak/timer/basic/BasicTimerProviderFactory.java
@@ -6,6 +6,9 @@ import org.keycloak.timer.TimerProvider;
import org.keycloak.timer.TimerProviderFactory;
import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -14,9 +17,11 @@ public class BasicTimerProviderFactory implements TimerProviderFactory {
private Timer timer;
+ private ConcurrentMap<String, TimerTask> scheduledTasks = new ConcurrentHashMap<String, TimerTask>();
+
@Override
public TimerProvider create(KeycloakSession session) {
- return new BasicTimerProvider(timer);
+ return new BasicTimerProvider(timer, this);
}
@Override
@@ -35,4 +40,12 @@ public class BasicTimerProviderFactory implements TimerProviderFactory {
return "basic";
}
+ protected TimerTask putTask(String taskName, TimerTask task) {
+ return scheduledTasks.put(taskName, task);
+ }
+
+ protected TimerTask removeTask(String taskName) {
+ return scheduledTasks.remove(taskName);
+ }
+
}