sync and import
This commit is contained in:
parent
fbaa731dfa
commit
0938390654
18 changed files with 369 additions and 31 deletions
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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<>();
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue