Merge pull request #622 from mposolda/master

Sync admin UI and fixes.
This commit is contained in:
Marek Posolda 2014-08-12 19:13:50 +02:00
commit 8cd8472003
15 changed files with 340 additions and 96 deletions

View file

@ -42,11 +42,13 @@ public class MongoKeycloakTransaction implements KeycloakTransaction {
} catch (MongoException e) { } catch (MongoException e) {
throw MongoStoreImpl.convertException(e); throw MongoStoreImpl.convertException(e);
} }
started = false;
} }
@Override @Override
public void rollback() { public void rollback() {
invocationContext.rollback(); invocationContext.rollback();
started = false;
} }
@Override @Override

View file

@ -101,24 +101,7 @@ public abstract class BasePropertiesFederationFactory implements UserFederationP
RealmModel realm = session.realms().getRealm(realmId); RealmModel realm = session.realms().getRealm(realmId);
BasePropertiesFederationProvider federationProvider = (BasePropertiesFederationProvider)getInstance(session, model); BasePropertiesFederationProvider federationProvider = (BasePropertiesFederationProvider)getInstance(session, model);
Set<String> allUsernames = federationProvider.getProperties().stringPropertyNames(); 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(); UserProvider localProvider = session.userStorage();
BasePropertiesFederationProvider federationProvider = (BasePropertiesFederationProvider)getInstance(session, model);
Set<String> allUsernames = federationProvider.getProperties().stringPropertyNames();
for (String username : allUsernames) { for (String username : allUsernames) {
UserModel localUser = localProvider.getUserByUsername(username, realm); UserModel localUser = localProvider.getUserByUsername(username, realm);
@ -131,4 +114,9 @@ public abstract class BasePropertiesFederationFactory implements UserFederationP
}); });
} }
@Override
public void syncChangedUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model, Date lastSync) {
syncAllUsers(sessionFactory, realmId, model);
}
} }

View file

@ -356,25 +356,50 @@ module.controller('UserFederationCtrl', function($scope, $location, realm, UserF
}); });
module.controller('GenericUserFederationCtrl', function($scope, $location, Notifications, Dialog, realm, instance, providerFactory, UserFederationInstances) { module.controller('GenericUserFederationCtrl', function($scope, $location, Notifications, Dialog, realm, instance, providerFactory, UserFederationInstances, UserFederationSync) {
console.log('GenericUserFederationCtrl'); console.log('GenericUserFederationCtrl');
$scope.instance = angular.copy(instance);
$scope.create = !instance.providerName; $scope.create = !instance.providerName;
$scope.providerFactory = providerFactory; $scope.providerFactory = providerFactory;
console.log("providerFactory: " + providerFactory.id); console.log("providerFactory: " + providerFactory.id);
if ($scope.create) { function initFederationSettings() {
$scope.instance.providerName = providerFactory.id; if ($scope.create) {
$scope.instance.config = {}; instance.providerName = providerFactory.id;
$scope.instance.priority = 0; instance.config = {};
instance.priority = 0;
$scope.fullSyncEnabled = false;
$scope.changedSyncEnabled = false;
} else {
$scope.fullSyncEnabled = (instance.fullSyncPeriod && instance.fullSyncPeriod > 0);
$scope.changedSyncEnabled = (instance.changedSyncPeriod && instance.changedSyncPeriod > 0);
}
$scope.changed = false;
} }
initFederationSettings();
$scope.instance = angular.copy(instance);
$scope.realm = realm; $scope.realm = realm;
$scope.$watch('fullSyncEnabled', function(newVal, oldVal) {
if (oldVal == newVal) {
return;
}
$scope.changed = false; $scope.instance.fullSyncPeriod = $scope.fullSyncEnabled ? 604800 : -1;
$scope.changed = true;
});
$scope.$watch('changedSyncEnabled', function(newVal, oldVal) {
if (oldVal == newVal) {
return;
}
$scope.instance.changedSyncPeriod = $scope.changedSyncEnabled ? 86400 : -1;
$scope.changed = true;
});
$scope.$watch('instance', function() { $scope.$watch('instance', function() {
if (!angular.equals($scope.instance, instance)) { if (!angular.equals($scope.instance, instance)) {
@ -405,13 +430,8 @@ module.controller('GenericUserFederationCtrl', function($scope, $location, Notif
}; };
$scope.reset = function() { $scope.reset = function() {
initFederationSettings();
$scope.instance = angular.copy(instance); $scope.instance = angular.copy(instance);
if ($scope.create) {
$scope.instance.providerName = providerFactory.id;
$scope.instance.config = {};
$scope.instance.priority = 0;
}
$scope.changed = false;
}; };
$scope.cancel = function() { $scope.cancel = function() {
@ -430,37 +450,70 @@ module.controller('GenericUserFederationCtrl', function($scope, $location, Notif
}); });
}; };
$scope.triggerFullSync = function() {
console.log('GenericCtrl: triggerFullSync');
triggerSync('triggerFullSync');
}
$scope.triggerChangedUsersSync = function() {
console.log('GenericCtrl: triggerChangedUsersSync');
triggerSync('triggerChangedUsersSync');
}
function triggerSync(action) {
UserFederationSync.get({ action: action, realm: $scope.realm.realm, provider: $scope.instance.id }, function() {
Notifications.success("Sync of users finished successfully");
}, function() {
Notifications.error("Error during sync of users");
});
}
}); });
module.controller('LDAPCtrl', function($scope, $location, Notifications, Dialog, realm, instance, UserFederationInstances, RealmLDAPConnectionTester) { module.controller('LDAPCtrl', function($scope, $location, Notifications, Dialog, realm, instance, UserFederationInstances, UserFederationSync, RealmLDAPConnectionTester) {
console.log('LDAPCtrl'); console.log('LDAPCtrl');
var DEFAULT_BATCH_SIZE = "1000";
$scope.instance = angular.copy(instance);
$scope.create = !instance.providerName; $scope.create = !instance.providerName;
if ($scope.create) { function initFederationSettings() {
$scope.instance.providerName = "ldap"; if ($scope.create) {
$scope.instance.config = {}; instance.providerName = "ldap";
$scope.instance.priority = 0; instance.config = {};
$scope.syncRegistrations = false; instance.priority = 0;
$scope.syncRegistrations = false;
$scope.userAccountControlsAfterPasswordUpdate = true; $scope.userAccountControlsAfterPasswordUpdate = true;
$scope.instance.config.userAccountControlsAfterPasswordUpdate = "true"; instance.config.userAccountControlsAfterPasswordUpdate = "true";
$scope.connectionPooling = true; $scope.connectionPooling = true;
$scope.instance.config.connectionPooling = "true"; instance.config.connectionPooling = "true";
$scope.pagination = true; $scope.pagination = true;
$scope.instance.config.pagination = "true"; instance.config.pagination = "true";
} else { instance.config.batchSizeForSync = DEFAULT_BATCH_SIZE;
$scope.syncRegistrations = instance.config.syncRegistrations && instance.config.syncRegistrations == "true";
$scope.userAccountControlsAfterPasswordUpdate = instance.config.userAccountControlsAfterPasswordUpdate && instance.config.userAccountControlsAfterPasswordUpdate == "true"; $scope.fullSyncEnabled = false;
$scope.connectionPooling = instance.config.connectionPooling && instance.config.connectionPooling == "true"; $scope.changedSyncEnabled = false;
$scope.pagination = instance.config.pagination && instance.config.pagination == "true"; } else {
$scope.syncRegistrations = instance.config.syncRegistrations && instance.config.syncRegistrations == "true";
$scope.userAccountControlsAfterPasswordUpdate = instance.config.userAccountControlsAfterPasswordUpdate && instance.config.userAccountControlsAfterPasswordUpdate == "true";
$scope.connectionPooling = instance.config.connectionPooling && instance.config.connectionPooling == "true";
$scope.pagination = instance.config.pagination && instance.config.pagination == "true";
if (!instance.config.batchSizeForSync) {
instance.config.batchSizeForSync = DEFAULT_BATCH_SIZE;
}
$scope.fullSyncEnabled = (instance.fullSyncPeriod && instance.fullSyncPeriod > 0);
$scope.changedSyncEnabled = (instance.changedSyncPeriod && instance.changedSyncPeriod > 0);
}
$scope.changed = false;
$scope.lastVendor = instance.config.vendor;
} }
initFederationSettings();
$scope.instance = angular.copy(instance);
$scope.ldapVendors = [ $scope.ldapVendors = [
{ "id": "ad", "name": "Active Directory" }, { "id": "ad", "name": "Active Directory" },
{ "id": "rhds", "name": "Red Hat Directory Server" }, { "id": "rhds", "name": "Red Hat Directory Server" },
@ -473,11 +526,6 @@ module.controller('LDAPCtrl', function($scope, $location, Notifications, Dialog,
$scope.realm = realm; $scope.realm = realm;
$scope.changed = false;
$scope.lastVendor = $scope.instance.config.vendor;
function watchBooleanProperty(propertyName) { function watchBooleanProperty(propertyName) {
$scope.$watch(propertyName, function() { $scope.$watch(propertyName, function() {
if ($scope[propertyName]) { if ($scope[propertyName]) {
@ -493,6 +541,24 @@ module.controller('LDAPCtrl', function($scope, $location, Notifications, Dialog,
watchBooleanProperty('connectionPooling'); watchBooleanProperty('connectionPooling');
watchBooleanProperty('pagination'); watchBooleanProperty('pagination');
$scope.$watch('fullSyncEnabled', function(newVal, oldVal) {
if (oldVal == newVal) {
return;
}
$scope.instance.fullSyncPeriod = $scope.fullSyncEnabled ? 604800 : -1;
$scope.changed = true;
});
$scope.$watch('changedSyncEnabled', function(newVal, oldVal) {
if (oldVal == newVal) {
return;
}
$scope.instance.changedSyncPeriod = $scope.changedSyncEnabled ? 86400 : -1;
$scope.changed = true;
});
$scope.$watch('instance', function() { $scope.$watch('instance', function() {
if (!angular.equals($scope.instance, instance)) { if (!angular.equals($scope.instance, instance)) {
$scope.changed = true; $scope.changed = true;
@ -514,6 +580,13 @@ module.controller('LDAPCtrl', function($scope, $location, Notifications, Dialog,
$scope.save = function() { $scope.save = function() {
$scope.changed = false; $scope.changed = false;
if (!parseInt($scope.instance.config.batchSizeForSync)) {
$scope.instance.config.batchSizeForSync = DEFAULT_BATCH_SIZE;
} else {
$scope.instance.config.batchSizeForSync = parseInt($scope.instance.config.batchSizeForSync).toString();
}
if ($scope.create) { if ($scope.create) {
UserFederationInstances.save({realm: realm.realm}, $scope.instance, function () { UserFederationInstances.save({realm: realm.realm}, $scope.instance, function () {
$scope.changed = false; $scope.changed = false;
@ -534,15 +607,8 @@ module.controller('LDAPCtrl', function($scope, $location, Notifications, Dialog,
}; };
$scope.reset = function() { $scope.reset = function() {
initFederationSettings();
$scope.instance = angular.copy(instance); $scope.instance = angular.copy(instance);
if ($scope.create) {
$scope.instance.providerName = "ldap";
$scope.instance.config = {};
$scope.instance.priority = 0;
$scope.syncRegistrations = false;
}
$scope.changed = false;
$scope.lastVendor = $scope.instance.config.vendor;
}; };
$scope.cancel = function() { $scope.cancel = function() {
@ -589,5 +655,24 @@ module.controller('LDAPCtrl', function($scope, $location, Notifications, Dialog,
Notifications.error("LDAP authentication failed. See server.log for details"); Notifications.error("LDAP authentication failed. See server.log for details");
}); });
} }
$scope.triggerFullSync = function() {
console.log('LDAPCtrl: triggerFullSync');
triggerSync('triggerFullSync');
}
$scope.triggerChangedUsersSync = function() {
console.log('LDAPCtrl: triggerChangedUsersSync');
triggerSync('triggerChangedUsersSync');
}
function triggerSync(action) {
UserFederationSync.get({ action: action, realm: $scope.realm.realm, provider: $scope.instance.id }, function() {
Notifications.success("Sync of users finished successfully");
}, function() {
Notifications.error("Error during sync of users");
});
}
}); });

View file

@ -217,6 +217,10 @@ module.factory('UserFederationProviders', function($resource) {
}); });
}); });
module.factory('UserFederationSync', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/user-federation/sync/:provider');
});
module.factory('UserSessionStats', function($resource) { module.factory('UserSessionStats', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/users/:user/session-stats', { return $resource(authUrl + '/admin/realms/:realm/users/:user/session-stats', {

View file

@ -40,6 +40,34 @@
</fieldset> </fieldset>
<fieldset>
<legend><span class="text">Sync settings</span></legend>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="fullSyncEnabled">Periodic full sync</label>
<div class="col-sm-4">
<input ng-model="fullSyncEnabled" name="fullSyncEnabled" id="fullSyncEnabled" onoffswitch />
</div>
</div>
<div class="form-group clearfix" data-ng-show="fullSyncEnabled">
<label class="col-sm-2 control-label" for="fullSyncPeriod">Full sync period</label>
<div class="col-sm-4">
<input class="form-control" type="number" ng-model="instance.fullSyncPeriod" id="fullSyncPeriod" />
</div>
</div>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="changedSyncEnabled">Periodic changed users sync</label>
<div class="col-sm-4">
<input ng-model="changedSyncEnabled" name="changedSyncEnabled" id="changedSyncEnabled" onoffswitch />
</div>
</div>
<div class="form-group clearfix" data-ng-show="changedSyncEnabled">
<label class="col-sm-2 control-label" for="changedSyncPeriod">Changed users sync period</label>
<div class="col-sm-4">
<input class="form-control" type="number" ng-model="instance.changedSyncPeriod" id="changedSyncPeriod" />
</div>
</div>
</fieldset>
<div class="pull-right form-actions" data-ng-show="create && access.manageUsers"> <div class="pull-right form-actions" data-ng-show="create && access.manageUsers">
<button kc-cancel data-ng-click="cancel()">Cancel</button> <button kc-cancel data-ng-click="cancel()">Cancel</button>
<button kc-save data-ng-show="changed">Save</button> <button kc-save data-ng-show="changed">Save</button>
@ -49,6 +77,8 @@
<button kc-reset data-ng-show="changed">Clear changes</button> <button kc-reset data-ng-show="changed">Clear changes</button>
<button kc-save data-ng-show="changed">Save</button> <button kc-save data-ng-show="changed">Save</button>
<button kc-delete data-ng-click="remove()" data-ng-hide="changed">Delete</button> <button kc-delete data-ng-click="remove()" data-ng-hide="changed">Delete</button>
<button kc-delete data-ng-click="triggerFullSync()" data-ng-hide="changed">Synchronize all users</button>
<button kc-delete data-ng-click="triggerChangedUsersSync()" data-ng-hide="changed">Synchronize changed users</button>
</div> </div>
</form> </form>
</div> </div>

View file

@ -133,6 +133,40 @@
</div> </div>
</fieldset> </fieldset>
<fieldset>
<legend><span class="text">Sync settings</span></legend>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="batchSizeForSync">Batch size</label>
<div class="col-sm-4">
<input class="form-control" type="text" ng-model="instance.config.batchSizeForSync" id="batchSizeForSync" />
</div>
</div>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="fullSyncEnabled">Periodic full sync</label>
<div class="col-sm-4">
<input ng-model="fullSyncEnabled" name="fullSyncEnabled" id="fullSyncEnabled" onoffswitch />
</div>
</div>
<div class="form-group clearfix" data-ng-show="fullSyncEnabled">
<label class="col-sm-2 control-label" for="fullSyncPeriod">Full sync period</label>
<div class="col-sm-4">
<input class="form-control" type="number" ng-model="instance.fullSyncPeriod" id="fullSyncPeriod" />
</div>
</div>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="changedSyncEnabled">Periodic changed users sync</label>
<div class="col-sm-4">
<input ng-model="changedSyncEnabled" name="changedSyncEnabled" id="changedSyncEnabled" onoffswitch />
</div>
</div>
<div class="form-group clearfix" data-ng-show="changedSyncEnabled">
<label class="col-sm-2 control-label" for="changedSyncPeriod">Changed users sync period</label>
<div class="col-sm-4">
<input class="form-control" type="number" ng-model="instance.changedSyncPeriod" id="changedSyncPeriod" />
</div>
</div>
</fieldset>
<div class="pull-right form-actions" data-ng-show="create && access.manageUsers"> <div class="pull-right form-actions" data-ng-show="create && access.manageUsers">
<button kc-cancel data-ng-click="cancel()">Cancel</button> <button kc-cancel data-ng-click="cancel()">Cancel</button>
<button kc-save data-ng-show="changed">Save</button> <button kc-save data-ng-show="changed">Save</button>
@ -142,6 +176,8 @@
<button kc-reset data-ng-show="changed">Clear changes</button> <button kc-reset data-ng-show="changed">Clear changes</button>
<button kc-save data-ng-show="changed">Save</button> <button kc-save data-ng-show="changed">Save</button>
<button kc-delete data-ng-click="remove()" data-ng-hide="changed">Delete</button> <button kc-delete data-ng-click="remove()" data-ng-hide="changed">Delete</button>
<button kc-delete data-ng-click="triggerFullSync()" data-ng-hide="changed">Synchronize all users</button>
<button kc-delete data-ng-click="triggerChangedUsersSync()" data-ng-hide="changed">Synchronize changed users</button>
</div> </div>
</form> </form>
</div> </div>

View file

@ -122,12 +122,14 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
cache.clear(); cache.clear();
} }
runInvalidations(); runInvalidations();
transactionActive = false;
} }
@Override @Override
public void rollback() { public void rollback() {
setRollbackOnly = true; setRollbackOnly = true;
runInvalidations(); runInvalidations();
transactionActive = false;
} }
@Override @Override

View file

@ -87,12 +87,14 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
cache.clear(); cache.clear();
} }
runInvalidations(); runInvalidations();
transactionActive = false;
} }
@Override @Override
public void rollback() { public void rollback() {
setRollbackOnly = true; setRollbackOnly = true;
runInvalidations(); runInvalidations();
transactionActive = false;
} }
@Override @Override

View file

@ -144,9 +144,9 @@ public class RealmManager {
} }
// Remove all periodic syncs for configured federation providers // Remove all periodic syncs for configured federation providers
PeriodicSyncManager periodicSyncManager = new PeriodicSyncManager(); UsersSyncManager usersSyncManager = new UsersSyncManager();
for (final UserFederationProviderModel fedProvider : federationProviders) { for (final UserFederationProviderModel fedProvider : federationProviders) {
periodicSyncManager.removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), fedProvider); usersSyncManager.removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), fedProvider);
} }
} }
return removed; return removed;
@ -218,9 +218,9 @@ public class RealmManager {
// Refresh periodic sync tasks for configured federationProviders // Refresh periodic sync tasks for configured federationProviders
List<UserFederationProviderModel> federationProviders = newRealm.getUserFederationProviders(); List<UserFederationProviderModel> federationProviders = newRealm.getUserFederationProviders();
PeriodicSyncManager periodicSyncManager = new PeriodicSyncManager(); UsersSyncManager usersSyncManager = new UsersSyncManager();
for (final UserFederationProviderModel fedProvider : federationProviders) { for (final UserFederationProviderModel fedProvider : federationProviders) {
periodicSyncManager.startPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), fedProvider, newRealm.getId()); usersSyncManager.refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), fedProvider, newRealm.getId());
} }
} }

View file

@ -2,6 +2,7 @@ package org.keycloak.services.managers;
import java.util.List; import java.util.List;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakSessionTask; import org.keycloak.models.KeycloakSessionTask;
@ -16,7 +17,9 @@ import org.keycloak.util.Time;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
public class PeriodicSyncManager { public class UsersSyncManager {
protected static final Logger logger = Logger.getLogger(UsersSyncManager.class);
/** /**
* Check federationProviderModel of all realms and possibly start periodic sync for them * Check federationProviderModel of all realms and possibly start periodic sync for them
@ -24,7 +27,7 @@ public class PeriodicSyncManager {
* @param sessionFactory * @param sessionFactory
* @param timer * @param timer
*/ */
public void bootstrap(final KeycloakSessionFactory sessionFactory, final TimerProvider timer) { public void bootstrapPeriodic(final KeycloakSessionFactory sessionFactory, final TimerProvider timer) {
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() { KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override @Override
@ -33,27 +36,45 @@ public class PeriodicSyncManager {
for (final RealmModel realm : realms) { for (final RealmModel realm : realms) {
List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders(); List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
for (final UserFederationProviderModel fedProvider : federationProviders) { for (final UserFederationProviderModel fedProvider : federationProviders) {
startPeriodicSyncForProvider(sessionFactory, timer, fedProvider, realm.getId()); refreshPeriodicSyncForProvider(sessionFactory, timer, fedProvider, realm.getId());
} }
} }
} }
}); });
} }
public void startPeriodicSyncForProvider(final KeycloakSessionFactory sessionFactory, TimerProvider timer, final UserFederationProviderModel fedProvider, final String realmId) { public void syncAllUsers(final KeycloakSessionFactory sessionFactory, String realmId, final UserFederationProviderModel fedProvider) {
final UserFederationProviderFactory fedProviderFactory = (UserFederationProviderFactory) sessionFactory.getProviderFactory(UserFederationProvider.class, fedProvider.getProviderName());
updateLastSyncInterval(sessionFactory, fedProvider, realmId);
fedProviderFactory.syncAllUsers(sessionFactory, realmId, fedProvider);
}
public void syncChangedUsers(final KeycloakSessionFactory sessionFactory, String realmId, final UserFederationProviderModel fedProvider) {
final UserFederationProviderFactory fedProviderFactory = (UserFederationProviderFactory) sessionFactory.getProviderFactory(UserFederationProvider.class, fedProvider.getProviderName()); final UserFederationProviderFactory fedProviderFactory = (UserFederationProviderFactory) sessionFactory.getProviderFactory(UserFederationProvider.class, fedProvider.getProviderName());
// See when we did last sync.
int oldLastSync = fedProvider.getLastSync();
updateLastSyncInterval(sessionFactory, fedProvider, realmId);
fedProviderFactory.syncChangedUsers(sessionFactory, realmId, fedProvider, Time.toDate(oldLastSync));
}
public void refreshPeriodicSyncForProvider(final KeycloakSessionFactory sessionFactory, TimerProvider timer, final UserFederationProviderModel fedProvider, final String realmId) {
if (fedProvider.getFullSyncPeriod() > 0) { if (fedProvider.getFullSyncPeriod() > 0) {
// We want periodic full sync for this provider // We want periodic full sync for this provider
timer.schedule(new Runnable() { timer.schedule(new Runnable() {
@Override @Override
public void run() { public void run() {
updateLastSyncInterval(sessionFactory, fedProvider, realmId); try {
fedProviderFactory.syncAllUsers(sessionFactory, realmId, fedProvider); syncAllUsers(sessionFactory, realmId, fedProvider);
} catch (Throwable t) {
logger.error("Error occured during full sync of users", t);
}
} }
}, fedProvider.getFullSyncPeriod() * 1000, fedProvider.getId() + "-FULL"); }, fedProvider.getFullSyncPeriod() * 1000, fedProvider.getId() + "-FULL");
} else {
timer.cancelTask(fedProvider.getId() + "-FULL");
} }
if (fedProvider.getChangedSyncPeriod() > 0) { if (fedProvider.getChangedSyncPeriod() > 0) {
@ -62,14 +83,17 @@ public class PeriodicSyncManager {
@Override @Override
public void run() { public void run() {
// See when we did last sync. try {
int oldLastSync = fedProvider.getLastSync(); syncChangedUsers(sessionFactory, realmId, fedProvider);
updateLastSyncInterval(sessionFactory, fedProvider, realmId); } catch (Throwable t) {
fedProviderFactory.syncChangedUsers(sessionFactory, realmId, fedProvider, Time.toDate(oldLastSync)); logger.error("Error occured during sync of changed users", t);
}
} }
}, fedProvider.getChangedSyncPeriod() * 1000, fedProvider.getId() + "-CHANGED"); }, fedProvider.getChangedSyncPeriod() * 1000, fedProvider.getId() + "-CHANGED");
} else {
timer.cancelTask(fedProvider.getId() + "-CHANGED");
} }
} }

View file

@ -15,7 +15,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.DefaultKeycloakSessionFactory; import org.keycloak.services.DefaultKeycloakSessionFactory;
import org.keycloak.services.managers.ApplianceBootstrap; import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.managers.BruteForceProtector; import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.managers.PeriodicSyncManager; import org.keycloak.services.managers.UsersSyncManager;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.TokenManager; import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.resources.admin.AdminRoot; import org.keycloak.services.resources.admin.AdminRoot;
@ -149,7 +149,7 @@ public class KeycloakApplication extends Application {
TimerProvider timer = sessionFactory.create().getProvider(TimerProvider.class); TimerProvider timer = sessionFactory.create().getProvider(TimerProvider.class);
timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredAuditEvents()), interval, "ClearExpiredAuditEvents"); timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredAuditEvents()), interval, "ClearExpiredAuditEvents");
timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions()), interval, "ClearExpiredUserSessions"); timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions()), interval, "ClearExpiredUserSessions");
new PeriodicSyncManager().bootstrap(sessionFactory, timer); new UsersSyncManager().bootstrapPeriodic(sessionFactory, timer);
} }
public KeycloakSessionFactory getSessionFactory() { public KeycloakSessionFactory getSessionFactory() {

View file

@ -22,7 +22,7 @@ import org.keycloak.representations.adapters.action.SessionStats;
import org.keycloak.representations.idm.RealmAuditRepresentation; import org.keycloak.representations.idm.RealmAuditRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.LDAPConnectionTestManager; import org.keycloak.services.managers.LDAPConnectionTestManager;
import org.keycloak.services.managers.PeriodicSyncManager; import org.keycloak.services.managers.UsersSyncManager;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.managers.TokenManager; import org.keycloak.services.managers.TokenManager;
@ -165,9 +165,9 @@ public class RealmAdminResource {
// Refresh periodic sync tasks for configured federationProviders // Refresh periodic sync tasks for configured federationProviders
List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders(); List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
PeriodicSyncManager periodicSyncManager = new PeriodicSyncManager(); UsersSyncManager usersSyncManager = new UsersSyncManager();
for (final UserFederationProviderModel fedProvider : federationProviders) { for (final UserFederationProviderModel fedProvider : federationProviders) {
periodicSyncManager.startPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), fedProvider, realm.getId()); usersSyncManager.refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), fedProvider, realm.getId());
} }
return Response.noContent().build(); return Response.noContent().build();

View file

@ -12,7 +12,7 @@ import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation; import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation;
import org.keycloak.representations.idm.UserFederationProviderRepresentation; import org.keycloak.representations.idm.UserFederationProviderRepresentation;
import org.keycloak.services.managers.PeriodicSyncManager; import org.keycloak.services.managers.UsersSyncManager;
import org.keycloak.timer.TimerProvider; import org.keycloak.timer.TimerProvider;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
@ -23,6 +23,7 @@ import javax.ws.rs.PUT;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
@ -120,7 +121,7 @@ public class UserFederationResource {
} }
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()); rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
new PeriodicSyncManager().startPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), model, realm.getId()); new UsersSyncManager().refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), model, realm.getId());
return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build(); return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build();
} }
@ -144,7 +145,7 @@ public class UserFederationResource {
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()); rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
realm.updateUserFederationProvider(model); realm.updateUserFederationProvider(model);
new PeriodicSyncManager().startPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), model, realm.getId()); new UsersSyncManager().refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), model, realm.getId());
} }
/** /**
@ -179,7 +180,7 @@ public class UserFederationResource {
auth.requireManage(); auth.requireManage();
UserFederationProviderModel model = new UserFederationProviderModel(id, null, null, -1, null, -1, -1, 0); UserFederationProviderModel model = new UserFederationProviderModel(id, null, null, -1, null, -1, -1, 0);
realm.removeUserFederationProvider(model); realm.removeUserFederationProvider(model);
new PeriodicSyncManager().removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), model); new UsersSyncManager().removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), model);
} }
@ -202,5 +203,32 @@ public class UserFederationResource {
return reps; return reps;
} }
/**
* trigger sync of users
*
* @return
*/
@GET
@Path("sync/{id}")
@NoCache
public Response syncUsers(@PathParam("id") String providerId, @QueryParam("action") String action) {
logger.info("triggerSync");
auth.requireManage();
for (UserFederationProviderModel model : realm.getUserFederationProviders()) {
if (model.getId().equals(providerId)) {
UsersSyncManager syncManager = new UsersSyncManager();
if ("triggerFullSync".equals(action)) {
syncManager.syncAllUsers(session.getKeycloakSessionFactory(), realm.getId(), model);
} else if ("triggerChangedUsersSync".equals(action)) {
syncManager.syncChangedUsers(session.getKeycloakSessionFactory(), realm.getId(), model);
}
return Response.noContent().build();
}
}
throw new NotFoundException("could not find provider");
}
} }

View file

@ -11,7 +11,6 @@ import org.junit.Test;
import org.junit.rules.RuleChain; import org.junit.rules.RuleChain;
import org.junit.rules.TestRule; import org.junit.rules.TestRule;
import org.junit.runners.MethodSorters; import org.junit.runners.MethodSorters;
import org.keycloak.examples.federation.properties.ClasspathPropertiesFederationFactory;
import org.keycloak.federation.ldap.LDAPFederationProvider; import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.LDAPFederationProviderFactory; import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
import org.keycloak.federation.ldap.LDAPUtils; import org.keycloak.federation.ldap.LDAPUtils;
@ -23,11 +22,10 @@ import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider; import org.keycloak.models.UserProvider;
import org.keycloak.services.managers.PeriodicSyncManager; import org.keycloak.services.managers.UsersSyncManager;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.LDAPRule; import org.keycloak.testsuite.rule.LDAPRule;
import org.keycloak.testutils.DummyUserFederationProvider;
import org.keycloak.testutils.DummyUserFederationProviderFactory; import org.keycloak.testutils.DummyUserFederationProviderFactory;
import org.keycloak.testutils.LDAPEmbeddedServer; import org.keycloak.testutils.LDAPEmbeddedServer;
import org.keycloak.timer.TimerProvider; import org.keycloak.timer.TimerProvider;
@ -84,29 +82,43 @@ public class SyncProvidersTest {
@Test @Test
public void testLDAPSync() { public void testLDAPSync() {
UsersSyncManager usersSyncManager = new UsersSyncManager();
// wait a bit
sleep(1000);
KeycloakSession session = keycloakRule.startSession(); KeycloakSession session = keycloakRule.startSession();
try { try {
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
UserFederationProviderFactory ldapFedFactory = (UserFederationProviderFactory)sessionFactory.getProviderFactory(UserFederationProvider.class, LDAPFederationProviderFactory.PROVIDER_NAME); usersSyncManager.syncAllUsers(sessionFactory, "test", ldapModel);
ldapFedFactory.syncAllUsers(sessionFactory, "test", ldapModel);
} finally { } finally {
keycloakRule.stopSession(session, false); keycloakRule.stopSession(session, false);
} }
// Assert users imported (model test)
session = keycloakRule.startSession(); session = keycloakRule.startSession();
try { try {
RealmModel testRealm = session.realms().getRealm("test"); RealmModel testRealm = session.realms().getRealm("test");
UserProvider userProvider = session.userStorage(); UserProvider userProvider = session.userStorage();
// Assert users imported
assertUserImported(userProvider, testRealm, "user1", "User1FN", "User1LN", "user1@email.org"); assertUserImported(userProvider, testRealm, "user1", "User1FN", "User1LN", "user1@email.org");
assertUserImported(userProvider, testRealm, "user2", "User2FN", "User2LN", "user2@email.org"); assertUserImported(userProvider, testRealm, "user2", "User2FN", "User2LN", "user2@email.org");
assertUserImported(userProvider, testRealm, "user3", "User3FN", "User3LN", "user3@email.org"); assertUserImported(userProvider, testRealm, "user3", "User3FN", "User3LN", "user3@email.org");
assertUserImported(userProvider, testRealm, "user4", "User4FN", "User4LN", "user4@email.org"); assertUserImported(userProvider, testRealm, "user4", "User4FN", "User4LN", "user4@email.org");
assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org"); assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org");
// Assert lastSync time updated
Assert.assertTrue(ldapModel.getLastSync() > 0);
for (UserFederationProviderModel persistentFedModel : testRealm.getUserFederationProviders()) {
if (LDAPFederationProviderFactory.PROVIDER_NAME.equals(persistentFedModel.getProviderName())) {
Assert.assertTrue(persistentFedModel.getLastSync() > 0);
} else {
// Dummy provider has still 0
Assert.assertEquals(0, persistentFedModel.getLastSync());
}
}
// wait a bit // wait a bit
sleep(1000); sleep(1000);
Date beforeLDAPUpdate = new Date();
// Add user to LDAP and update 'user5' in LDAP // Add user to LDAP and update 'user5' in LDAP
PartitionManager partitionManager = FederationProvidersIntegrationTest.getPartitionManager(session, ldapModel); PartitionManager partitionManager = FederationProvidersIntegrationTest.getPartitionManager(session, ldapModel);
@ -119,8 +131,7 @@ public class SyncProvidersTest {
// Trigger partial sync // Trigger partial sync
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
UserFederationProviderFactory ldapFedFactory = (UserFederationProviderFactory)sessionFactory.getProviderFactory(UserFederationProvider.class, LDAPFederationProviderFactory.PROVIDER_NAME); usersSyncManager.syncChangedUsers(sessionFactory, "test", ldapModel);
ldapFedFactory.syncChangedUsers(sessionFactory, "test", ldapModel, beforeLDAPUpdate);
} finally { } finally {
keycloakRule.stopSession(session, false); keycloakRule.stopSession(session, false);
} }
@ -147,12 +158,12 @@ public class SyncProvidersTest {
int changed = dummyFedFactory.getChangedSyncCounter(); int changed = dummyFedFactory.getChangedSyncCounter();
// Assert that after some period was DummyUserFederationProvider triggered // Assert that after some period was DummyUserFederationProvider triggered
PeriodicSyncManager periodicSyncManager = new PeriodicSyncManager(); UsersSyncManager usersSyncManager = new UsersSyncManager();
periodicSyncManager.bootstrap(sessionFactory, session.getProvider(TimerProvider.class)); usersSyncManager.bootstrapPeriodic(sessionFactory, session.getProvider(TimerProvider.class));
sleep(1800); sleep(1800);
// Cancel timer // Cancel timer
periodicSyncManager.removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), dummyModel); usersSyncManager.removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), dummyModel);
// Assert that DummyUserFederationProviderFactory.syncChangedUsers was invoked // Assert that DummyUserFederationProviderFactory.syncChangedUsers was invoked
int newChanged = dummyFedFactory.getChangedSyncCounter(); int newChanged = dummyFedFactory.getChangedSyncCounter();

View file

@ -0,0 +1,32 @@
package org.keycloak.testsuite.model;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.models.KeycloakSession;
import org.keycloak.testsuite.rule.KeycloakRule;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class TransactionsTest {
@ClassRule
public static KeycloakRule kc = new KeycloakRule();
@Test
public void testTransactionActive() {
KeycloakSession session = kc.startSession();
Assert.assertTrue(session.getTransaction().isActive());
session.getTransaction().commit();
Assert.assertFalse(session.getTransaction().isActive());
session.getTransaction().begin();
Assert.assertTrue(session.getTransaction().isActive());
session.getTransaction().rollback();
Assert.assertFalse(session.getTransaction().isActive());
session.close();
}
}