commit
1c0abbd722
20 changed files with 418 additions and 37 deletions
|
@ -17,7 +17,9 @@
|
|||
|
||||
package org.keycloak.representations.idm;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -27,6 +29,8 @@ public class ComponentTypeRepresentation {
|
|||
protected String helpText;
|
||||
protected List<ConfigPropertyRepresentation> properties;
|
||||
|
||||
protected Map<String, Object> metadata = new HashMap<>();
|
||||
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
|
@ -51,4 +55,18 @@ public class ComponentTypeRepresentation {
|
|||
public void setProperties(List<ConfigPropertyRepresentation> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2063,6 +2063,9 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
protected void setConfig(ComponentModel model, ComponentEntity c) {
|
||||
for (String key : model.getConfig().keySet()) {
|
||||
List<String> vals = model.getConfig().get(key);
|
||||
if (vals == null) {
|
||||
continue;
|
||||
}
|
||||
for (String val : vals) {
|
||||
ComponentConfigEntity config = new ComponentConfigEntity();
|
||||
config.setId(KeycloakModelUtils.generateId());
|
||||
|
|
|
@ -45,6 +45,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;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,8 @@ public interface KeycloakSessionFactory extends ProviderEventManager {
|
|||
|
||||
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, String id);
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
|
||||
package org.keycloak.provider;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
|
@ -26,5 +29,4 @@ public interface Spi {
|
|||
String getName();
|
||||
Class<? extends Provider> getProviderClass();
|
||||
Class<? extends ProviderFactory> getProviderFactoryClass();
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<CredentialInput> 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);
|
||||
} // </deprecate>
|
||||
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));
|
||||
}
|
||||
}
|
||||
// </deprecate>
|
||||
}
|
||||
|
||||
if (toValidate.isEmpty()) return true;
|
||||
|
||||
List<CredentialInputValidator> credentialProviders = getCredentialProviders(realm, CredentialInputValidator.class);
|
||||
for (CredentialInputValidator validator : credentialProviders) {
|
||||
Iterator<CredentialInput> 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<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) {
|
||||
List<T> list = new LinkedList<T>();
|
||||
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;
|
||||
}
|
||||
// </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);
|
||||
|
@ -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;
|
||||
}
|
||||
// </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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spi getSpi(Class<? extends Provider> providerClass) {
|
||||
for (Spi spi : spis) {
|
||||
if (spi.getProviderClass().equals(providerClass)) return spi;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz) {
|
||||
return getProviderFactory(clazz, provider.get(clazz));
|
||||
|
|
|
@ -49,6 +49,7 @@ import org.keycloak.representations.idm.OAuthClientRepresentation;
|
|||
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
@ -491,6 +492,13 @@ public class RealmManager implements RealmImporter {
|
|||
usersSyncManager.notifyToRefreshPeriodicSync(session, realm, fedProvider, false);
|
||||
}
|
||||
|
||||
// Refresh periodic sync tasks for configured storageProviders
|
||||
List<UserStorageProviderModel> storageProviders = realm.getUserStorageProviders();
|
||||
UserStorageSyncManager storageSync = new UserStorageSyncManager();
|
||||
for (UserStorageProviderModel provider : storageProviders) {
|
||||
storageSync.notifyToRefreshPeriodicSync(session, realm, provider, false);
|
||||
}
|
||||
|
||||
setupAuthorizationServices(realm);
|
||||
fireRealmPostCreate(realm);
|
||||
|
||||
|
|
|
@ -165,6 +165,11 @@ public class UserStorageSyncManager {
|
|||
|
||||
// Ensure all cluster nodes are notified
|
||||
public void notifyToRefreshPeriodicSync(KeycloakSession session, RealmModel realm, UserStorageProviderModel provider, boolean removed) {
|
||||
UserStorageProviderFactory factory = (UserStorageProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, provider.getProviderId());
|
||||
if (!(factory instanceof ImportSynchronization) || !provider.isImportEnabled()) {
|
||||
return;
|
||||
|
||||
}
|
||||
UserStorageProviderClusterEvent event = UserStorageProviderClusterEvent.createEvent(removed, realm.getId(), provider);
|
||||
session.getProvider(ClusterProvider.class).notify(USER_STORAGE_TASK_KEY, event);
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.keycloak.services.ServicesLogger;
|
|||
import org.keycloak.services.filters.KeycloakTransactionCommitter;
|
||||
import org.keycloak.services.managers.ApplianceBootstrap;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.managers.UserStorageSyncManager;
|
||||
import org.keycloak.services.managers.UsersSyncManager;
|
||||
import org.keycloak.services.resources.admin.AdminRoot;
|
||||
import org.keycloak.services.scheduled.ClearExpiredEvents;
|
||||
|
@ -319,6 +320,7 @@ public class KeycloakApplication extends Application {
|
|||
timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredEvents(), interval), interval, "ClearExpiredEvents");
|
||||
timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions(), interval), interval, "ClearExpiredUserSessions");
|
||||
new UsersSyncManager().bootstrapPeriodic(sessionFactory, timer);
|
||||
new UserStorageSyncManager().bootstrapPeriodic(sessionFactory, timer);
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.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<ProviderConfigProperty> 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<ComponentTypeRepresentation> reps = info.getComponentTypes().get(spi.getProviderClass().getName());
|
||||
if (reps == null) {
|
||||
reps = new LinkedList<>();
|
||||
|
|
|
@ -38,6 +38,8 @@ import org.keycloak.models.cache.CachedUserModel;
|
|||
import org.keycloak.models.cache.OnUserCache;
|
||||
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||
import org.keycloak.credential.CredentialAuthentication;
|
||||
import org.keycloak.storage.user.ImportSynchronization;
|
||||
import org.keycloak.storage.user.ImportedUserValidation;
|
||||
import org.keycloak.storage.user.UserLookupProvider;
|
||||
import org.keycloak.storage.user.UserQueryProvider;
|
||||
import org.keycloak.storage.user.UserRegistrationProvider;
|
||||
|
@ -225,11 +227,38 @@ 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());
|
||||
if (provider != null && provider instanceof ImportedUserValidation) {
|
||||
return ((ImportedUserValidation)provider).validate(realm, user);
|
||||
} else {
|
||||
return user;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected List<UserModel> importValidation(RealmModel realm, List<UserModel> users) {
|
||||
List<UserModel> tmp = new LinkedList<>();
|
||||
for (UserModel user : users) {
|
||||
tmp.add(importValidation(realm, user));
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserById(String id, RealmModel realm) {
|
||||
StorageId storageId = new StorageId(id);
|
||||
if (storageId.getProviderId() == null) {
|
||||
return localStorage().getUserById(id, realm);
|
||||
UserModel user = localStorage().getUserById(id, realm);
|
||||
return importValidation(realm, user);
|
||||
}
|
||||
UserLookupProvider provider = (UserLookupProvider)getStorageProvider(session, realm, storageId.getProviderId());
|
||||
return provider.getUserById(id, realm);
|
||||
|
@ -243,7 +272,9 @@ public class UserStorageManager implements UserProvider, OnUserCache {
|
|||
@Override
|
||||
public UserModel getUserByUsername(String username, RealmModel realm) {
|
||||
UserModel user = localStorage().getUserByUsername(username, realm);
|
||||
if (user != null) return user;
|
||||
if (user != null) {
|
||||
return importValidation(realm, user);
|
||||
}
|
||||
for (UserLookupProvider provider : getStorageProviders(session, realm, UserLookupProvider.class)) {
|
||||
user = provider.getUserByUsername(username, realm);
|
||||
if (user != null) return user;
|
||||
|
@ -257,7 +288,9 @@ public class UserStorageManager implements UserProvider, OnUserCache {
|
|||
if (user != null) return user;
|
||||
for (UserLookupProvider provider : getStorageProviders(session, realm, UserLookupProvider.class)) {
|
||||
user = provider.getUserByEmail(email, realm);
|
||||
if (user != null) return user;
|
||||
if (user != null) {
|
||||
return importValidation(realm, user);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -266,7 +299,7 @@ public class UserStorageManager implements UserProvider, OnUserCache {
|
|||
public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) {
|
||||
UserModel user = localStorage().getUserByFederatedIdentity(socialLink, realm);
|
||||
if (user != null) {
|
||||
return user;
|
||||
return importValidation(realm, user);
|
||||
}
|
||||
if (getFederatedStorage() == null) return null;
|
||||
String id = getFederatedStorage().getUserByFederatedIdentity(socialLink, realm);
|
||||
|
@ -354,7 +387,7 @@ public class UserStorageManager implements UserProvider, OnUserCache {
|
|||
|
||||
@Override
|
||||
public List<UserModel> getUsers(final RealmModel realm, int firstResult, int maxResults, final boolean includeServiceAccounts) {
|
||||
return query((provider, first, max) -> {
|
||||
List<UserModel> results = query((provider, first, max) -> {
|
||||
if (provider instanceof UserProvider) { // it is local storage
|
||||
return ((UserProvider) provider).getUsers(realm, first, max, includeServiceAccounts);
|
||||
} else if (provider instanceof UserQueryProvider) {
|
||||
|
@ -364,6 +397,7 @@ public class UserStorageManager implements UserProvider, OnUserCache {
|
|||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
, realm, firstResult, maxResults);
|
||||
return importValidation(realm, results);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -373,23 +407,26 @@ public class UserStorageManager implements UserProvider, OnUserCache {
|
|||
|
||||
@Override
|
||||
public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
|
||||
return query((provider, first, max) -> {
|
||||
List<UserModel> results = query((provider, first, max) -> {
|
||||
if (provider instanceof UserQueryProvider) {
|
||||
return ((UserQueryProvider)provider).searchForUser(search, realm, first, max);
|
||||
|
||||
}
|
||||
return Collections.EMPTY_LIST;
|
||||
}, realm, firstResult, maxResults);
|
||||
return importValidation(realm, results);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm) {
|
||||
return searchForUser(attributes, realm, 0, Integer.MAX_VALUE - 1);
|
||||
List<UserModel> results = searchForUser(attributes, realm, 0, Integer.MAX_VALUE - 1);
|
||||
return importValidation(realm, results);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||
return query((provider, first, max) -> {
|
||||
List<UserModel> results = query((provider, first, max) -> {
|
||||
if (provider instanceof UserQueryProvider) {
|
||||
return ((UserQueryProvider)provider).searchForUser(attributes, realm, first, max);
|
||||
|
||||
|
@ -397,6 +434,8 @@ public class UserStorageManager implements UserProvider, OnUserCache {
|
|||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
, realm, firstResult, maxResults);
|
||||
return importValidation(realm, results);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -417,7 +456,7 @@ public class UserStorageManager implements UserProvider, OnUserCache {
|
|||
}
|
||||
return Collections.EMPTY_LIST;
|
||||
}, realm,0, Integer.MAX_VALUE - 1);
|
||||
return results;
|
||||
return importValidation(realm, results);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -472,7 +511,7 @@ public class UserStorageManager implements UserProvider, OnUserCache {
|
|||
}
|
||||
return Collections.EMPTY_LIST;
|
||||
}, realm, firstResult, maxResults);
|
||||
return results;
|
||||
return importValidation(realm, results);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @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";
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -1667,4 +1667,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 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="col-md-10 col-md-offset-2" data-ng-show="create && access.manageUsers">
|
||||
<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">
|
||||
<button kc-save data-ng-disabled="!changed">{{:: 'save' | 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>
|
||||
</form>
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
</div>
|
||||
|
||||
<div class="col-md-6" data-ng-if="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]}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue