sync and import

This commit is contained in:
Bill Burke 2016-10-13 20:38:49 -04:00
parent fbaa731dfa
commit 0938390654
18 changed files with 369 additions and 31 deletions

View file

@ -17,7 +17,9 @@
package org.keycloak.representations.idm; package org.keycloak.representations.idm;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -27,6 +29,8 @@ public class ComponentTypeRepresentation {
protected String helpText; protected String helpText;
protected List<ConfigPropertyRepresentation> properties; protected List<ConfigPropertyRepresentation> properties;
protected Map<String, Object> metadata = new HashMap<>();
public String getId() { public String getId() {
return id; return id;
@ -51,4 +55,18 @@ public class ComponentTypeRepresentation {
public void setProperties(List<ConfigPropertyRepresentation> properties) { public void setProperties(List<ConfigPropertyRepresentation> properties) {
this.properties = 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<String, Object> getMetadata() {
return metadata;
}
public void setMetadata(Map<String, Object> metadata) {
this.metadata = metadata;
}
} }

View file

@ -2158,6 +2158,9 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
protected void setConfig(ComponentModel model, ComponentEntity c) { protected void setConfig(ComponentModel model, ComponentEntity c) {
for (String key : model.getConfig().keySet()) { for (String key : model.getConfig().keySet()) {
List<String> vals = model.getConfig().get(key); List<String> vals = model.getConfig().get(key);
if (vals == null) {
continue;
}
for (String val : vals) { for (String val : vals) {
ComponentConfigEntity config = new ComponentConfigEntity(); ComponentConfigEntity config = new ComponentConfigEntity();
config.setId(KeycloakModelUtils.generateId()); config.setId(KeycloakModelUtils.generateId());

View file

@ -43,6 +43,7 @@ public class ComponentModel implements Serializable {
this.name = copy.name; this.name = copy.name;
this.providerId = copy.providerId; this.providerId = copy.providerId;
this.providerType = copy.providerType; this.providerType = copy.providerType;
this.parentId = copy.parentId;
this.config = copy.config; this.config = copy.config;
} }

View file

@ -34,6 +34,8 @@ public interface KeycloakSessionFactory extends ProviderEventManager {
Set<Spi> getSpis(); Set<Spi> getSpis();
Spi getSpi(Class<? extends Provider> providerClass);
<T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz); <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz);
<T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz, String id); <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz, String id);

View file

@ -17,6 +17,9 @@
package org.keycloak.provider; package org.keycloak.provider;
import java.util.Collections;
import java.util.List;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
@ -26,5 +29,4 @@ public interface Spi {
String getName(); String getName();
Class<? extends Provider> getProviderClass(); Class<? extends Provider> getProviderClass();
Class<? extends ProviderFactory> getProviderFactoryClass(); Class<? extends ProviderFactory> getProviderFactoryClass();
} }

View file

@ -44,8 +44,11 @@ public class UserStorageProviderModel extends PrioritizedComponentModel {
public boolean isImportEnabled() { public boolean isImportEnabled() {
if (importEnabled == null) { if (importEnabled == null) {
String val = getConfig().getFirst("importEnabled"); String val = getConfig().getFirst("importEnabled");
if (val == null) importEnabled = false; if (val == null) {
importEnabled = Boolean.valueOf(val); importEnabled = true;
} else {
importEnabled = Boolean.valueOf(val);
}
} }
return importEnabled; return importEnabled;
@ -59,8 +62,11 @@ public class UserStorageProviderModel extends PrioritizedComponentModel {
public int getFullSyncPeriod() { public int getFullSyncPeriod() {
if (fullSyncPeriod == null) { if (fullSyncPeriod == null) {
String val = getConfig().getFirst("fullSyncPeriod"); String val = getConfig().getFirst("fullSyncPeriod");
if (val == null) fullSyncPeriod = -1; if (val == null) {
fullSyncPeriod = Integer.valueOf(val); fullSyncPeriod = -1;
} else {
fullSyncPeriod = Integer.valueOf(val);
}
} }
return fullSyncPeriod; return fullSyncPeriod;
} }
@ -73,8 +79,11 @@ public class UserStorageProviderModel extends PrioritizedComponentModel {
public int getChangedSyncPeriod() { public int getChangedSyncPeriod() {
if (changedSyncPeriod == null) { if (changedSyncPeriod == null) {
String val = getConfig().getFirst("changedSyncPeriod"); String val = getConfig().getFirst("changedSyncPeriod");
if (val == null) changedSyncPeriod = -1; if (val == null) {
changedSyncPeriod = Integer.valueOf(val); changedSyncPeriod = -1;
} else {
changedSyncPeriod = Integer.valueOf(val);
}
} }
return changedSyncPeriod; return changedSyncPeriod;
} }
@ -87,8 +96,11 @@ public class UserStorageProviderModel extends PrioritizedComponentModel {
public int getLastSync() { public int getLastSync() {
if (lastSync == null) { if (lastSync == null) {
String val = getConfig().getFirst("lastSync"); String val = getConfig().getFirst("lastSync");
if (val == null) lastSync = 0; if (val == null) {
lastSync = Integer.valueOf(val); lastSync = 0;
} else {
lastSync = Integer.valueOf(val);
}
} }
return lastSync; return lastSync;
} }

View file

@ -122,34 +122,36 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser
UserFederationProvider link = session.users().getFederationLink(realm, user); UserFederationProvider link = session.users().getFederationLink(realm, user);
if (link != null) { if (link != null) {
session.users().validateUser(realm, user); session.users().validateUser(realm, user);
Iterator<CredentialInput> it = toValidate.iterator(); validate(realm, user, toValidate, link);
while (it.hasNext()) { } // </deprecate>
CredentialInput input = it.next(); else if (user.getFederationLink() != null) {
if (link.supportsCredentialType(input.getType()) UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, user.getFederationLink());
&& link.isValid(realm, user, input)) { if (provider != null && provider instanceof CredentialInputValidator) {
it.remove(); validate(realm, user, toValidate, ((CredentialInputValidator)provider));
}
} }
} }
// </deprecate>
} }
if (toValidate.isEmpty()) return true; if (toValidate.isEmpty()) return true;
List<CredentialInputValidator> credentialProviders = getCredentialProviders(realm, CredentialInputValidator.class); List<CredentialInputValidator> credentialProviders = getCredentialProviders(realm, CredentialInputValidator.class);
for (CredentialInputValidator validator : credentialProviders) { for (CredentialInputValidator validator : credentialProviders) {
Iterator<CredentialInput> it = toValidate.iterator(); validate(realm, user, toValidate, validator);
while (it.hasNext()) {
CredentialInput input = it.next();
if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) {
it.remove();
}
}
} }
return toValidate.isEmpty(); return toValidate.isEmpty();
} }
private void validate(RealmModel realm, UserModel user, List<CredentialInput> toValidate, CredentialInputValidator validator) {
Iterator<CredentialInput> it = toValidate.iterator();
while (it.hasNext()) {
CredentialInput input = it.next();
if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) {
it.remove();
}
}
}
protected <T> List<T> getCredentialProviders(RealmModel realm, Class<T> type) { protected <T> List<T> getCredentialProviders(RealmModel realm, Class<T> type) {
List<T> list = new LinkedList<T>(); List<T> list = new LinkedList<T>();
for (ProviderFactory f : session.getKeycloakSessionFactory().getProviderFactories(CredentialProvider.class)) { 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; if (link.updateCredential(realm, user, input)) return;
} }
// </deprecated> // </deprecated>
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<CredentialInputUpdater> credentialProviders = getCredentialProviders(realm, CredentialInputUpdater.class); List<CredentialInputUpdater> credentialProviders = getCredentialProviders(realm, CredentialInputUpdater.class);
@ -203,6 +211,12 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser
if (link != null && link.getSupportedCredentialTypes().contains(credentialType)) { if (link != null && link.getSupportedCredentialTypes().contains(credentialType)) {
link.disableCredentialType(realm, user, 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; if (link.isConfiguredFor(realm, user, type)) return true;
} }
// </deprecate> // </deprecate>
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;
}
}
} }

View file

@ -299,6 +299,14 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
return spis; return spis;
} }
@Override
public Spi getSpi(Class<? extends Provider> providerClass) {
for (Spi spi : spis) {
if (spi.getProviderClass().equals(providerClass)) return spi;
}
return null;
}
@Override @Override
public <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz) { public <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz) {
return getProviderFactory(clazz, provider.get(clazz)); return getProviderFactory(clazz, provider.get(clazz));

View file

@ -54,9 +54,9 @@ public class ComponentResource {
protected RealmModel realm; protected RealmModel realm;
private RealmAuth auth; protected RealmAuth auth;
private AdminEventBuilder adminEvent; protected AdminEventBuilder adminEvent;
@Context @Context
protected ClientConnection clientConnection; protected ClientConnection clientConnection;
@ -93,12 +93,16 @@ public class ComponentResource {
} }
List<ComponentRepresentation> reps = new LinkedList<>(); List<ComponentRepresentation> reps = new LinkedList<>();
for (ComponentModel component : components) { for (ComponentModel component : components) {
ComponentRepresentation rep = ModelToRepresentation.toRepresentation(component); ComponentRepresentation rep = getRepresentation(component);
reps.add(rep); reps.add(rep);
} }
return reps; return reps;
} }
protected ComponentRepresentation getRepresentation(ComponentModel component) {
return ModelToRepresentation.toRepresentation(component);
}
@POST @POST
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public Response create(ComponentRepresentation rep) { public Response create(ComponentRepresentation rep) {
@ -121,7 +125,7 @@ public class ComponentResource {
if (model == null) { if (model == null) {
throw new NotFoundException("Could not find component"); throw new NotFoundException("Could not find component");
} }
return ModelToRepresentation.toRepresentation(model); return getRepresentation(model);
} }

View file

@ -361,6 +361,14 @@ public class RealmAdminResource {
return fed; 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") @Path("authentication")
public AuthenticationManagementResource flows() { public AuthenticationManagementResource flows() {
AuthenticationManagementResource resource = new AuthenticationManagementResource(realm, session, auth, adminEvent); AuthenticationManagementResource resource = new AuthenticationManagementResource(realm, session, auth, adminEvent);

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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<String, Object> eventRep = new HashMap<>();
eventRep.put("action", action);
eventRep.put("result", syncResult);
adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).representation(eventRep).success();
return syncResult;
}
}

View file

@ -50,6 +50,7 @@ import org.keycloak.representations.info.ServerInfoRepresentation;
import org.keycloak.representations.info.SpiInfoRepresentation; import org.keycloak.representations.info.SpiInfoRepresentation;
import org.keycloak.representations.info.SystemInfoRepresentation; import org.keycloak.representations.info.SystemInfoRepresentation;
import org.keycloak.representations.info.ThemeInfoRepresentation; import org.keycloak.representations.info.ThemeInfoRepresentation;
import org.keycloak.storage.user.ImportSynchronization;
import org.keycloak.theme.Theme; import org.keycloak.theme.Theme;
import org.keycloak.theme.ThemeProvider; import org.keycloak.theme.ThemeProvider;
@ -135,6 +136,9 @@ public class ServerInfoAdminResource {
List<ProviderConfigProperty> configProperties = configured.getConfigProperties(); List<ProviderConfigProperty> configProperties = configured.getConfigProperties();
if (configProperties == null) configProperties = Collections.EMPTY_LIST; if (configProperties == null) configProperties = Collections.EMPTY_LIST;
rep.setProperties(ModelToRepresentation.toRepresentation(configProperties)); rep.setProperties(ModelToRepresentation.toRepresentation(configProperties));
if (pi instanceof ImportSynchronization) {
rep.getMetadata().put("synchronizable", true);
}
List<ComponentTypeRepresentation> reps = info.getComponentTypes().get(spi.getProviderClass().getName()); List<ComponentTypeRepresentation> reps = info.getComponentTypes().get(spi.getProviderClass().getName());
if (reps == null) { if (reps == null) {
reps = new LinkedList<>(); reps = new LinkedList<>();

View file

@ -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) { protected UserModel importValidation(RealmModel realm, UserModel user) {
if (user == null || user.getFederationLink() == null) return user; if (user == null || user.getFederationLink() == null) return user;
UserStorageProvider provider = getStorageProvider(session, realm, user.getFederationLink()); UserStorageProvider provider = getStorageProvider(session, realm, user.getFederationLink());

View file

@ -22,8 +22,12 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.storage.UserStorageProviderFactory; 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.io.IOException;
import java.util.Date;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
@ -32,7 +36,7 @@ import java.util.Properties;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class UserPropertyFileStorageFactory implements UserStorageProviderFactory<UserPropertyFileStorage> { public class UserPropertyFileStorageFactory implements UserStorageProviderFactory<UserPropertyFileStorage>, ImportSynchronization {
public static final String PROVIDER_ID = "user-password-props"; public static final String PROVIDER_ID = "user-password-props";
@ -80,4 +84,14 @@ public class UserPropertyFileStorageFactory implements UserStorageProviderFactor
public void close() { 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();
}
} }

View file

@ -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('GenericUserStorageCtrl');
console.log('providerId: ' + providerId); console.log('providerId: ' + providerId);
$scope.create = !instance.providerId; $scope.create = !instance.providerId;
@ -719,6 +720,7 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica
} }
$scope.provider = instance; $scope.provider = instance;
$scope.showSync = false;
console.log("providerFactory: " + providerFactory.id); console.log("providerFactory: " + providerFactory.id);
@ -733,6 +735,12 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica
}; };
instance.config['priority'] = ["0"]; 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) { if (providerFactory.properties) {
for (var i = 0; i < providerFactory.properties.length; i++) { for (var i = 0; i < providerFactory.properties.length; i++) {
@ -747,6 +755,20 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica
} }
} else { } 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('Manage instance');
console.log(instance.name); 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; $scope.changed = false;
} }
@ -773,6 +802,25 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica
}, true); }, 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.save = function() {
$scope.changed = false; $scope.changed = false;
if ($scope.create) { if ($scope.create) {
@ -814,6 +862,27 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica
$route.reload(); $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");
});
}
}); });

View file

@ -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'
});
});

View file

@ -35,6 +35,39 @@
</fieldset> </fieldset>
<fieldset ng-show="showSync">
<legend><span class="text">{{:: 'sync-settings' | translate}}</span></legend>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="fullSyncEnabled">{{:: 'periodic-full-sync' | translate}}</label>
<div class="col-md-6">
<input ng-model="fullSyncEnabled" name="fullSyncEnabled" id="fullSyncEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'periodic-full-sync.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-show="fullSyncEnabled">
<label class="col-md-2 control-label" for="fullSyncPeriod">{{:: 'full-sync-period' | translate}}</label>
<div class="col-md-6">
<input class="form-control" type="text" ng-model="instance.config['fullSyncPeriod'][0]" id="fullSyncPeriod" />
</div>
<kc-tooltip>{{:: 'full-sync-period.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="changedSyncEnabled">{{:: 'periodic-changed-users-sync' | translate}}</label>
<div class="col-md-6">
<input ng-model="changedSyncEnabled" name="changedSyncEnabled" id="changedSyncEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'periodic-changed-users-sync.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-show="changedSyncEnabled">
<label class="col-md-2 control-label" for="changedSyncPeriod">{{:: 'changed-users-sync-period' | translate}}</label>
<div class="col-md-6">
<input class="form-control" type="text" ng-model="instance.config['changedSyncPeriod'][0]" id="changedSyncPeriod" />
</div>
<kc-tooltip>{{:: 'changed-users-sync-period.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<div class="form-group"> <div class="form-group">
<div class="col-md-10 col-md-offset-2" data-ng-show="create && access.manageUsers"> <div class="col-md-10 col-md-offset-2" data-ng-show="create && access.manageUsers">
<button kc-save>{{:: 'save' | translate}}</button> <button kc-save>{{:: 'save' | translate}}</button>
@ -46,6 +79,8 @@
<div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageUsers"> <div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageUsers">
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button> <button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button> <button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
<button class="btn btn-primary" data-ng-click="triggerChangedUsersSync()" data-ng-hide="changed || !showSync">{{:: 'synchronize-changed-users' | translate}}</button>
<button class="btn btn-primary" data-ng-click="triggerFullSync()" data-ng-hide="changed || !showSync">{{:: 'synchronize-all-users' | translate}}</button>
</div> </div>
</div> </div>
</form> </form>

View file

@ -33,7 +33,7 @@
</div> </div>
<div class="col-md-6" data-ng-show="option.type == 'Script'"> <div class="col-md-6" data-ng-show="option.type == 'Script'">
<div ng-model="config[option.name]" placeholder="Enter your script..." ui-ace="{ useWrapMode: true, showGutter: true, theme:'github', mode: 'javascript'}"> <div ng-model="config[option.name][0]" placeholder="Enter your script..." ui-ace="{ useWrapMode: true, showGutter: true, theme:'github', mode: 'javascript'}">
{{config[option.name]}} {{config[option.name]}}
</div> </div>
</div> </div>