diff --git a/core/src/main/java/org/keycloak/representations/idm/ComponentTypeRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ComponentTypeRepresentation.java index 76ba16d0341..1662fa25222 100644 --- a/core/src/main/java/org/keycloak/representations/idm/ComponentTypeRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ComponentTypeRepresentation.java @@ -17,7 +17,9 @@ package org.keycloak.representations.idm; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * @author Marek Posolda @@ -27,6 +29,8 @@ public class ComponentTypeRepresentation { protected String helpText; protected List properties; + protected Map metadata = new HashMap<>(); + public String getId() { return id; @@ -51,4 +55,18 @@ public class ComponentTypeRepresentation { public void setProperties(List properties) { this.properties = properties; } + + /** + * Extra information about the component that might come from annotations or interfaces that the component implements + * For example, if UserStorageProvider implements ImportSynchronization + * + * @return + */ + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } } 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 21aa8d06484..61cb9c4c26a 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 @@ -2158,6 +2158,9 @@ public class RealmAdapter implements RealmModel, JpaModel { protected void setConfig(ComponentModel model, ComponentEntity c) { for (String key : model.getConfig().keySet()) { List vals = model.getConfig().get(key); + if (vals == null) { + continue; + } for (String val : vals) { ComponentConfigEntity config = new ComponentConfigEntity(); config.setId(KeycloakModelUtils.generateId()); diff --git a/server-spi/src/main/java/org/keycloak/component/ComponentModel.java b/server-spi/src/main/java/org/keycloak/component/ComponentModel.java index c80df698249..0d6f23bdee9 100755 --- a/server-spi/src/main/java/org/keycloak/component/ComponentModel.java +++ b/server-spi/src/main/java/org/keycloak/component/ComponentModel.java @@ -43,6 +43,7 @@ public class ComponentModel implements Serializable { this.name = copy.name; this.providerId = copy.providerId; this.providerType = copy.providerType; + this.parentId = copy.parentId; this.config = copy.config; } diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSessionFactory.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSessionFactory.java index 0028ae7b8f4..d060c898711 100755 --- a/server-spi/src/main/java/org/keycloak/models/KeycloakSessionFactory.java +++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSessionFactory.java @@ -34,6 +34,8 @@ public interface KeycloakSessionFactory extends ProviderEventManager { Set getSpis(); + Spi getSpi(Class providerClass); + ProviderFactory getProviderFactory(Class clazz); ProviderFactory getProviderFactory(Class clazz, String id); diff --git a/server-spi/src/main/java/org/keycloak/provider/Spi.java b/server-spi/src/main/java/org/keycloak/provider/Spi.java index e4561b0ffa1..b9c47f8e5cb 100644 --- a/server-spi/src/main/java/org/keycloak/provider/Spi.java +++ b/server-spi/src/main/java/org/keycloak/provider/Spi.java @@ -17,6 +17,9 @@ package org.keycloak.provider; +import java.util.Collections; +import java.util.List; + /** * @author Stian Thorgersen */ @@ -26,5 +29,4 @@ public interface Spi { String getName(); Class getProviderClass(); Class getProviderFactoryClass(); - } diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java index ded16244d00..4cd038baa10 100755 --- a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java @@ -44,8 +44,11 @@ public class UserStorageProviderModel extends PrioritizedComponentModel { public boolean isImportEnabled() { if (importEnabled == null) { String val = getConfig().getFirst("importEnabled"); - if (val == null) importEnabled = false; - importEnabled = Boolean.valueOf(val); + if (val == null) { + importEnabled = true; + } else { + importEnabled = Boolean.valueOf(val); + } } return importEnabled; @@ -59,8 +62,11 @@ public class UserStorageProviderModel extends PrioritizedComponentModel { public int getFullSyncPeriod() { if (fullSyncPeriod == null) { String val = getConfig().getFirst("fullSyncPeriod"); - if (val == null) fullSyncPeriod = -1; - fullSyncPeriod = Integer.valueOf(val); + if (val == null) { + fullSyncPeriod = -1; + } else { + fullSyncPeriod = Integer.valueOf(val); + } } return fullSyncPeriod; } @@ -73,8 +79,11 @@ public class UserStorageProviderModel extends PrioritizedComponentModel { public int getChangedSyncPeriod() { if (changedSyncPeriod == null) { String val = getConfig().getFirst("changedSyncPeriod"); - if (val == null) changedSyncPeriod = -1; - changedSyncPeriod = Integer.valueOf(val); + if (val == null) { + changedSyncPeriod = -1; + } else { + changedSyncPeriod = Integer.valueOf(val); + } } return changedSyncPeriod; } @@ -87,8 +96,11 @@ public class UserStorageProviderModel extends PrioritizedComponentModel { public int getLastSync() { if (lastSync == null) { String val = getConfig().getFirst("lastSync"); - if (val == null) lastSync = 0; - lastSync = Integer.valueOf(val); + if (val == null) { + lastSync = 0; + } else { + lastSync = Integer.valueOf(val); + } } return lastSync; } diff --git a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java index 5801efd63d7..9adbae6abb7 100644 --- a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java +++ b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java @@ -122,34 +122,36 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser UserFederationProvider link = session.users().getFederationLink(realm, user); if (link != null) { session.users().validateUser(realm, user); - Iterator it = toValidate.iterator(); - while (it.hasNext()) { - CredentialInput input = it.next(); - if (link.supportsCredentialType(input.getType()) - && link.isValid(realm, user, input)) { - it.remove(); - } + validate(realm, user, toValidate, link); + } // + else if (user.getFederationLink() != null) { + UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, user.getFederationLink()); + if (provider != null && provider instanceof CredentialInputValidator) { + validate(realm, user, toValidate, ((CredentialInputValidator)provider)); } } - // } if (toValidate.isEmpty()) return true; List credentialProviders = getCredentialProviders(realm, CredentialInputValidator.class); for (CredentialInputValidator validator : credentialProviders) { - Iterator it = toValidate.iterator(); - while (it.hasNext()) { - CredentialInput input = it.next(); - if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) { - it.remove(); - } - } + validate(realm, user, toValidate, validator); } return toValidate.isEmpty(); } + private void validate(RealmModel realm, UserModel user, List toValidate, CredentialInputValidator validator) { + Iterator it = toValidate.iterator(); + while (it.hasNext()) { + CredentialInput input = it.next(); + if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) { + it.remove(); + } + } + } + protected List getCredentialProviders(RealmModel realm, Class type) { List list = new LinkedList(); for (ProviderFactory f : session.getKeycloakSessionFactory().getProviderFactories(CredentialProvider.class)) { @@ -178,6 +180,12 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser if (link.updateCredential(realm, user, input)) return; } // + else if (user.getFederationLink() != null) { + UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, user.getFederationLink()); + if (provider != null && provider instanceof CredentialInputUpdater) { + if (((CredentialInputUpdater)provider).updateCredential(realm, user, input)) return; + } + } } List credentialProviders = getCredentialProviders(realm, CredentialInputUpdater.class); @@ -203,6 +211,12 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser if (link != null && link.getSupportedCredentialTypes().contains(credentialType)) { link.disableCredentialType(realm, user, credentialType); } + else if (user.getFederationLink() != null) { + UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, user.getFederationLink()); + if (provider != null && provider instanceof CredentialInputUpdater) { + ((CredentialInputUpdater)provider).disableCredentialType(realm, user, credentialType); + } + } } @@ -233,6 +247,12 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser if (link.isConfiguredFor(realm, user, type)) return true; } // + else if (user.getFederationLink() != null) { + UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, user.getFederationLink()); + if (provider != null && provider instanceof CredentialInputValidator) { + if (((CredentialInputValidator)provider).isConfiguredFor(realm, user, type)) return true; + } + } } diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java index 9cdb0650cae..caf281a0464 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java @@ -299,6 +299,14 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr return spis; } + @Override + public Spi getSpi(Class providerClass) { + for (Spi spi : spis) { + if (spi.getProviderClass().equals(providerClass)) return spi; + } + return null; + } + @Override public ProviderFactory getProviderFactory(Class clazz) { return getProviderFactory(clazz, provider.get(clazz)); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java index f3e099afcd1..ff7b86ae17d 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java @@ -54,9 +54,9 @@ public class ComponentResource { protected RealmModel realm; - private RealmAuth auth; + protected RealmAuth auth; - private AdminEventBuilder adminEvent; + protected AdminEventBuilder adminEvent; @Context protected ClientConnection clientConnection; @@ -93,12 +93,16 @@ public class ComponentResource { } List reps = new LinkedList<>(); for (ComponentModel component : components) { - ComponentRepresentation rep = ModelToRepresentation.toRepresentation(component); + ComponentRepresentation rep = getRepresentation(component); reps.add(rep); } return reps; } + protected ComponentRepresentation getRepresentation(ComponentModel component) { + return ModelToRepresentation.toRepresentation(component); + } + @POST @Consumes(MediaType.APPLICATION_JSON) public Response create(ComponentRepresentation rep) { @@ -121,7 +125,7 @@ public class ComponentResource { if (model == null) { throw new NotFoundException("Could not find component"); } - return ModelToRepresentation.toRepresentation(model); + return getRepresentation(model); } 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 29c4cbf3bde..15fb2866233 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -361,6 +361,14 @@ public class RealmAdminResource { return fed; } + @Path("user-storage") + public UserStorageProviderResource userStorage() { + UserStorageProviderResource fed = new UserStorageProviderResource(realm, auth, adminEvent); + ResteasyProviderFactory.getInstance().injectProperties(fed); + //resourceContext.initResource(fed); + return fed; + } + @Path("authentication") public AuthenticationManagementResource flows() { AuthenticationManagementResource resource = new AuthenticationManagementResource(realm, session, auth, adminEvent); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java new file mode 100644 index 00000000000..1112416ccbf --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java @@ -0,0 +1,124 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.services.resources.admin; + +import org.jboss.resteasy.annotations.cache.NoCache; +import org.jboss.resteasy.spi.NotFoundException; +import org.keycloak.common.ClientConnection; +import org.keycloak.component.ComponentModel; +import org.keycloak.events.admin.OperationType; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.services.ServicesLogger; +import org.keycloak.services.managers.UserStorageSyncManager; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.storage.user.SynchronizationResult; + +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UserStorageProviderResource { + protected static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER; + + protected RealmModel realm; + + protected RealmAuth auth; + + protected AdminEventBuilder adminEvent; + + @Context + protected ClientConnection clientConnection; + + @Context + protected UriInfo uriInfo; + + @Context + protected KeycloakSession session; + + @Context + protected HttpHeaders headers; + + public UserStorageProviderResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) { + this.auth = auth; + this.realm = realm; + this.adminEvent = adminEvent; + + auth.init(RealmAuth.Resource.USER); + } + + /** + * Trigger sync of users + * + * @return + */ + @POST + @Path("{id}/sync") + @NoCache + @Produces(MediaType.APPLICATION_JSON) + public SynchronizationResult syncUsers(@PathParam("id") String id, + @QueryParam("action") String action) { + auth.requireManage(); + + ComponentModel model = realm.getComponent(id); + if (model == null) { + throw new NotFoundException("Could not find component"); + } + if (!model.getProviderType().equals(UserStorageProvider.class.getName())) { + throw new NotFoundException("found, but not a UserStorageProvider"); + } + + UserStorageProviderModel providerModel = new UserStorageProviderModel(model); + + + + logger.debug("Syncing users"); + + UserStorageSyncManager syncManager = new UserStorageSyncManager(); + SynchronizationResult syncResult; + if ("triggerFullSync".equals(action)) { + syncResult = syncManager.syncAllUsers(session.getKeycloakSessionFactory(), realm.getId(), providerModel); + } else if ("triggerChangedUsersSync".equals(action)) { + syncResult = syncManager.syncChangedUsers(session.getKeycloakSessionFactory(), realm.getId(), providerModel); + } else { + throw new NotFoundException("Unknown action: " + action); + } + + Map eventRep = new HashMap<>(); + eventRep.put("action", action); + eventRep.put("result", syncResult); + adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).representation(eventRep).success(); + + return syncResult; + } + + + +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java index 9a87030ab86..8120e7f0b4b 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java @@ -50,6 +50,7 @@ import org.keycloak.representations.info.ServerInfoRepresentation; import org.keycloak.representations.info.SpiInfoRepresentation; import org.keycloak.representations.info.SystemInfoRepresentation; import org.keycloak.representations.info.ThemeInfoRepresentation; +import org.keycloak.storage.user.ImportSynchronization; import org.keycloak.theme.Theme; import org.keycloak.theme.ThemeProvider; @@ -135,6 +136,9 @@ public class ServerInfoAdminResource { List configProperties = configured.getConfigProperties(); if (configProperties == null) configProperties = Collections.EMPTY_LIST; rep.setProperties(ModelToRepresentation.toRepresentation(configProperties)); + if (pi instanceof ImportSynchronization) { + rep.getMetadata().put("synchronizable", true); + } List reps = info.getComponentTypes().get(spi.getProviderClass().getName()); if (reps == null) { reps = new LinkedList<>(); diff --git a/services/src/main/java/org/keycloak/storage/UserStorageManager.java b/services/src/main/java/org/keycloak/storage/UserStorageManager.java index 94d6232b0a0..995043a3fa9 100755 --- a/services/src/main/java/org/keycloak/storage/UserStorageManager.java +++ b/services/src/main/java/org/keycloak/storage/UserStorageManager.java @@ -227,6 +227,13 @@ public class UserStorageManager implements UserProvider, OnUserCache { } } + /** + * Allows a UserStorageProvider to proxy and/or synchronize an imported user. + * + * @param realm + * @param user + * @return + */ protected UserModel importValidation(RealmModel realm, UserModel user) { if (user == null || user.getFederationLink() == null) return user; UserStorageProvider provider = getStorageProvider(session, realm, user.getFederationLink()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorageFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorageFactory.java index 291f0846939..02f97cd49bb 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorageFactory.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorageFactory.java @@ -22,8 +22,12 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.storage.UserStorageProviderFactory; +import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.storage.user.ImportSynchronization; +import org.keycloak.storage.user.SynchronizationResult; import java.io.IOException; +import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Properties; @@ -32,7 +36,7 @@ import java.util.Properties; * @author Bill Burke * @version $Revision: 1 $ */ -public class UserPropertyFileStorageFactory implements UserStorageProviderFactory { +public class UserPropertyFileStorageFactory implements UserStorageProviderFactory, ImportSynchronization { public static final String PROVIDER_ID = "user-password-props"; @@ -80,4 +84,14 @@ public class UserPropertyFileStorageFactory implements UserStorageProviderFactor public void close() { } + + @Override + public SynchronizationResult sync(KeycloakSessionFactory sessionFactory, String realmId, UserStorageProviderModel model) { + return SynchronizationResult.ignored(); + } + + @Override + public SynchronizationResult syncSince(Date lastSync, KeycloakSessionFactory sessionFactory, String realmId, UserStorageProviderModel model) { + return SynchronizationResult.ignored(); + } } diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js index b262f651e23..e5cf535af3b 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js @@ -700,7 +700,8 @@ module.controller('UserFederationCtrl', function($scope, $location, $route, real }; }); -module.controller('GenericUserStorageCtrl', function($scope, $location, Notifications, $route, Dialog, realm, serverInfo, instance, providerId, Components) { +module.controller('GenericUserStorageCtrl', function($scope, $location, Notifications, $route, Dialog, realm, + serverInfo, instance, providerId, Components, UserStorageSync) { console.log('GenericUserStorageCtrl'); console.log('providerId: ' + providerId); $scope.create = !instance.providerId; @@ -719,6 +720,7 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica } $scope.provider = instance; + $scope.showSync = false; console.log("providerFactory: " + providerFactory.id); @@ -733,6 +735,12 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica }; instance.config['priority'] = ["0"]; + $scope.fullSyncEnabled = false; + $scope.changedSyncEnabled = false; + if (providerFactory.metadata.synchronizable) { + instance.config['fullSyncPeriod'] = ['-1']; + instance.config['changedSyncPeriod'] = ['-1']; + } if (providerFactory.properties) { for (var i = 0; i < providerFactory.properties.length; i++) { @@ -747,6 +755,20 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica } } else { + $scope.fullSyncEnabled = (instance.config['fullSyncPeriod'] && instance.config['fullSyncPeriod'][0] > 0); + $scope.changedSyncEnabled = (instance.config['changedSyncPeriod'] && instance.config['changedSyncPeriod'][0]> 0); + if (providerFactory.metadata.synchronizable) { + if (!instance.config['fullSyncPeriod']) { + console.log('setting to -1'); + instance.config['fullSyncPeriod'] = ['-1']; + + } + if (!instance.config['changedSyncPeriod']) { + console.log('setting to -1'); + instance.config['changedSyncPeriod'] = ['-1']; + + } + } /* console.log('Manage instance'); console.log(instance.name); @@ -758,6 +780,13 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica } */ } + if (providerFactory.metadata.synchronizable) { + if (instance.config && instance.config['importEnabled']) { + $scope.showSync = instance.config['importEnabled'][0] == 'true'; + } else { + $scope.showSync = true; + } + } $scope.changed = false; } @@ -773,6 +802,25 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica }, true); + $scope.$watch('fullSyncEnabled', function(newVal, oldVal) { + if (oldVal == newVal) { + return; + } + + $scope.instance.config['fullSyncPeriod'][0] = $scope.fullSyncEnabled ? "604800" : "-1"; + $scope.changed = true; + }); + + $scope.$watch('changedSyncEnabled', function(newVal, oldVal) { + if (oldVal == newVal) { + return; + } + + $scope.instance.config['changedSyncPeriod'][0] = $scope.changedSyncEnabled ? "86400" : "-1"; + $scope.changed = true; + }); + + $scope.save = function() { $scope.changed = false; if ($scope.create) { @@ -814,6 +862,27 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica $route.reload(); } }; + + $scope.triggerFullSync = function() { + console.log('GenericCtrl: triggerFullSync'); + triggerSync('triggerFullSync'); + } + + $scope.triggerChangedUsersSync = function() { + console.log('GenericCtrl: triggerChangedUsersSync'); + triggerSync('triggerChangedUsersSync'); + } + + function triggerSync(action) { + UserStorageSync.save({ action: action, realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) { + $route.reload(); + Notifications.success("Sync of users finished successfully. " + syncResult.status); + }, function() { + $route.reload(); + Notifications.error("Error during sync of users"); + }); + } + }); diff --git a/themes/src/main/resources/theme/base/admin/resources/js/services.js b/themes/src/main/resources/theme/base/admin/resources/js/services.js index 236ad44be48..621675ce19c 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -1661,4 +1661,11 @@ module.factory('Components', function($resource) { }); }); +module.factory('UserStorageSync', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/user-storage/:componentId/sync', { + realm : '@realm', + componentId : '@componentId' + }); +}); + diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-generic.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-generic.html index e3ff5f68eb2..68b64890c0b 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-generic.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-generic.html @@ -35,6 +35,39 @@ +
+ {{:: 'sync-settings' | translate}} +
+ +
+ +
+ {{:: 'periodic-full-sync.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'full-sync-period.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'periodic-changed-users-sync.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'changed-users-sync-period.tooltip' | translate}} +
+
+ +
@@ -46,6 +79,8 @@
+ +
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-component-config.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-component-config.html index 57bbc06de49..97d88762ee6 100755 --- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-component-config.html +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-component-config.html @@ -33,7 +33,7 @@
-
+
{{config[option.name]}}