Preparation for moving User Storage SPI

- Introduction of new AdminRealmResource SPI
- Moving handler of /realm/{realm}/user-storage into model/legacy-service
- session.users() and userStorageManager() moved refers legacy module
  IMPORTANT: Broken as UserStorageSyncManager is not yet moved
This commit is contained in:
Hynek Mlnarik 2022-04-24 11:05:08 +02:00 committed by Hynek Mlnařík
parent 36f76a37ad
commit 703e868a51
37 changed files with 622 additions and 150 deletions

View file

@ -56,6 +56,8 @@ import org.keycloak.models.utils.ReadOnlyUserModelDelegate;
import org.keycloak.policy.PasswordPolicyManagerProvider; import org.keycloak.policy.PasswordPolicyManagerProvider;
import org.keycloak.policy.PolicyError; import org.keycloak.policy.PolicyError;
import org.keycloak.models.cache.UserCache; import org.keycloak.models.cache.UserCache;
import org.keycloak.storage.DatastoreProvider;
import org.keycloak.storage.LegacyStoreManagers;
import org.keycloak.storage.ReadOnlyException; import org.keycloak.storage.ReadOnlyException;
import org.keycloak.storage.StorageId; import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProvider;
@ -179,7 +181,8 @@ public class LDAPStorageProvider implements UserStorageProvider,
// We need to avoid having CachedUserModel as cache is upper-layer then LDAP. Hence having CachedUserModel here may cause StackOverflowError // We need to avoid having CachedUserModel as cache is upper-layer then LDAP. Hence having CachedUserModel here may cause StackOverflowError
if (local instanceof CachedUserModel) { if (local instanceof CachedUserModel) {
local = session.userStorageManager().getUserById(realm, local.getId()); LegacyStoreManagers datastoreProvider = (LegacyStoreManagers) session.getProvider(DatastoreProvider.class);
local = datastoreProvider.userStorageManager().getUserById(realm, local.getId());
existing = userManager.getManagedProxiedUser(local.getId()); existing = userManager.getManagedProxiedUser(local.getId());
if (existing != null) { if (existing != null) {

View file

@ -27,9 +27,9 @@ import org.keycloak.models.cache.infinispan.entities.*;
import org.keycloak.models.cache.infinispan.events.*; import org.keycloak.models.cache.infinispan.events.*;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.storage.DatastoreProvider; import org.keycloak.storage.DatastoreProvider;
import org.keycloak.storage.LegacyStoreManagers;
import org.keycloak.storage.StorageId; import org.keycloak.storage.StorageId;
import org.keycloak.storage.client.ClientStorageProviderModel; import org.keycloak.storage.client.ClientStorageProviderModel;
import org.keycloak.storage.datastore.LegacyDatastoreProvider;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -121,13 +121,13 @@ public class RealmCacheSession implements CacheRealmProvider {
protected boolean clearAll; protected boolean clearAll;
protected final long startupRevision; protected final long startupRevision;
private final LegacyDatastoreProvider datastoreProvider; private final LegacyStoreManagers datastoreProvider;
public RealmCacheSession(RealmCacheManager cache, KeycloakSession session) { public RealmCacheSession(RealmCacheManager cache, KeycloakSession session) {
this.cache = cache; this.cache = cache;
this.session = session; this.session = session;
this.startupRevision = cache.getCurrentCounter(); this.startupRevision = cache.getCurrentCounter();
this.datastoreProvider = (LegacyDatastoreProvider) session.getProvider(DatastoreProvider.class); this.datastoreProvider = (LegacyStoreManagers) session.getProvider(DatastoreProvider.class);
session.getTransactionManager().enlistPrepare(getPrepareTransaction()); session.getTransactionManager().enlistPrepare(getPrepareTransaction());
session.getTransactionManager().enlistAfterCompletion(getAfterTransaction()); session.getTransactionManager().enlistAfterCompletion(getAfterTransaction());
} }

View file

@ -53,6 +53,10 @@ import org.keycloak.models.cache.infinispan.stream.InIdentityProviderPredicate;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ReadOnlyUserModelDelegate; import org.keycloak.models.utils.ReadOnlyUserModelDelegate;
import org.keycloak.storage.CacheableStorageProviderModel; import org.keycloak.storage.CacheableStorageProviderModel;
import org.keycloak.storage.DatastoreProvider;
import org.keycloak.storage.LegacyStoreManagers;
import org.keycloak.storage.OnCreateComponent;
import org.keycloak.storage.OnUpdateComponent;
import org.keycloak.storage.StorageId; import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel; import org.keycloak.storage.UserStorageProviderModel;
@ -71,7 +75,7 @@ import java.util.stream.Stream;
* @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 UserCacheSession implements UserCache.Streams { public class UserCacheSession implements UserCache.Streams, OnCreateComponent, OnUpdateComponent {
protected static final Logger logger = Logger.getLogger(UserCacheSession.class); protected static final Logger logger = Logger.getLogger(UserCacheSession.class);
protected UserCacheManager cache; protected UserCacheManager cache;
protected KeycloakSession session; protected KeycloakSession session;
@ -85,11 +89,13 @@ public class UserCacheSession implements UserCache.Streams {
protected Set<String> realmInvalidations = new HashSet<>(); protected Set<String> realmInvalidations = new HashSet<>();
protected Set<InvalidationEvent> invalidationEvents = new HashSet<>(); // Events to be sent across cluster protected Set<InvalidationEvent> invalidationEvents = new HashSet<>(); // Events to be sent across cluster
protected Map<String, UserModel> managedUsers = new HashMap<>(); protected Map<String, UserModel> managedUsers = new HashMap<>();
private LegacyStoreManagers datastoreProvider;
public UserCacheSession(UserCacheManager cache, KeycloakSession session) { public UserCacheSession(UserCacheManager cache, KeycloakSession session) {
this.cache = cache; this.cache = cache;
this.session = session; this.session = session;
this.startupRevision = cache.getCurrentCounter(); this.startupRevision = cache.getCurrentCounter();
this.datastoreProvider = (LegacyStoreManagers) session.getProvider(DatastoreProvider.class);
session.getTransactionManager().enlistAfterCompletion(getTransaction()); session.getTransactionManager().enlistAfterCompletion(getTransaction());
} }
@ -103,7 +109,7 @@ public class UserCacheSession implements UserCache.Streams {
public UserProvider getDelegate() { public UserProvider getDelegate() {
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction"); if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
if (delegate != null) return delegate; if (delegate != null) return delegate;
delegate = session.userStorageManager(); delegate = this.datastoreProvider.userStorageManager();
return delegate; return delegate;
} }
@ -906,4 +912,17 @@ public class UserCacheSession implements UserCache.Streams {
invalidationEvents.add(UserCacheRealmInvalidationEvent.create(realmId)); invalidationEvents.add(UserCacheRealmInvalidationEvent.create(realmId));
} }
@Override
public void onUpdate(KeycloakSession session, RealmModel realm, ComponentModel oldModel, ComponentModel newModel) {
if (getDelegate() instanceof OnUpdateComponent) {
((OnUpdateComponent) getDelegate()).onUpdate(session, realm, oldModel, newModel);
}
}
@Override
public void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) {
if (getDelegate() instanceof OnCreateComponent) {
((OnCreateComponent) getDelegate()).onCreate(session, realm, model);
}
}
} }

View file

@ -0,0 +1,32 @@
/*
* Copyright 2017 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.scheduled;
import org.keycloak.models.KeycloakSession;
import org.keycloak.timer.ScheduledTask;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClearExpiredClientInitialAccessTokens implements ScheduledTask {
@Override
public void run(KeycloakSession session) {
session.realms().removeExpiredClientInitialAccess();
}
}

View file

@ -0,0 +1,46 @@
/*
* 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.scheduled;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.events.EventStoreProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.timer.ScheduledTask;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClearExpiredEvents implements ScheduledTask {
protected static final Logger logger = Logger.getLogger(ClearExpiredEvents.class);
@Override
public void run(KeycloakSession session) {
long currentTimeMillis = Time.currentTimeMillis();
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
if (eventStore != null) {
eventStore.clearExpiredEvents();
}
long took = Time.currentTimeMillis() - currentTimeMillis;
logger.debugf("ClearExpiredEvents finished in %d ms", took);
}
}

View file

@ -0,0 +1,45 @@
/*
* 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.scheduled;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.models.KeycloakSession;
import org.keycloak.timer.ScheduledTask;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClearExpiredUserSessions implements ScheduledTask {
protected static final Logger logger = Logger.getLogger(ClearExpiredUserSessions.class);
public static final String TASK_NAME = "ClearExpiredUserSessions";
@Override
public void run(KeycloakSession session) {
long currentTimeMillis = Time.currentTimeMillis();
session.authenticationSessions().removeAllExpired();
session.sessions().removeAllExpired();
long took = Time.currentTimeMillis() - currentTimeMillis;
logger.debugf("ClearExpiredUserSessions finished in %d ms", took);
}
}

View file

@ -17,6 +17,18 @@
package org.keycloak.storage; package org.keycloak.storage;
import static org.keycloak.models.utils.KeycloakModelUtils.runJobInTransaction;
import static org.keycloak.utils.StreamsUtil.distinctByKey;
import static org.keycloak.utils.StreamsUtil.paginatedStream;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.component.ComponentFactory; import org.keycloak.component.ComponentFactory;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
@ -39,27 +51,15 @@ import org.keycloak.models.cache.OnUserCache;
import org.keycloak.models.cache.UserCache; import org.keycloak.models.cache.UserCache;
import org.keycloak.models.utils.ComponentUtil; import org.keycloak.models.utils.ComponentUtil;
import org.keycloak.models.utils.ReadOnlyUserModelDelegate; import org.keycloak.models.utils.ReadOnlyUserModelDelegate;
import org.keycloak.services.managers.UserStorageSyncManager;
import org.keycloak.storage.client.ClientStorageProvider; import org.keycloak.storage.client.ClientStorageProvider;
import org.keycloak.storage.federated.UserFederatedStorageProvider; import org.keycloak.storage.federated.UserFederatedStorageProvider;
import org.keycloak.storage.managers.UserStorageSyncManager;
import org.keycloak.storage.user.ImportedUserValidation; import org.keycloak.storage.user.ImportedUserValidation;
import org.keycloak.storage.user.UserBulkUpdateProvider; import org.keycloak.storage.user.UserBulkUpdateProvider;
import org.keycloak.storage.user.UserLookupProvider; import org.keycloak.storage.user.UserLookupProvider;
import org.keycloak.storage.user.UserQueryProvider; import org.keycloak.storage.user.UserQueryProvider;
import org.keycloak.storage.user.UserRegistrationProvider; import org.keycloak.storage.user.UserRegistrationProvider;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
import static org.keycloak.models.utils.KeycloakModelUtils.runJobInTransaction;
import static org.keycloak.utils.StreamsUtil.distinctByKey;
import static org.keycloak.utils.StreamsUtil.paginatedStream;
/** /**
* @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 $

View file

@ -6,14 +6,18 @@ import org.keycloak.models.GroupProvider;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmProvider; import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleProvider; import org.keycloak.models.RoleProvider;
import org.keycloak.models.UserProvider;
import org.keycloak.models.cache.CacheRealmProvider; import org.keycloak.models.cache.CacheRealmProvider;
import org.keycloak.models.cache.UserCache;
import org.keycloak.storage.ClientScopeStorageManager; import org.keycloak.storage.ClientScopeStorageManager;
import org.keycloak.storage.ClientStorageManager; import org.keycloak.storage.ClientStorageManager;
import org.keycloak.storage.DatastoreProvider; import org.keycloak.storage.DatastoreProvider;
import org.keycloak.storage.GroupStorageManager; import org.keycloak.storage.GroupStorageManager;
import org.keycloak.storage.LegacyStoreManagers;
import org.keycloak.storage.RoleStorageManager; import org.keycloak.storage.RoleStorageManager;
import org.keycloak.storage.UserStorageManager;
public class LegacyDatastoreProvider implements DatastoreProvider { public class LegacyDatastoreProvider implements DatastoreProvider, LegacyStoreManagers {
private final LegacyDatastoreProviderFactory factory; private final LegacyDatastoreProviderFactory factory;
private final KeycloakSession session; private final KeycloakSession session;
@ -23,11 +27,13 @@ public class LegacyDatastoreProvider implements DatastoreProvider {
private GroupProvider groupProvider; private GroupProvider groupProvider;
private RealmProvider realmProvider; private RealmProvider realmProvider;
private RoleProvider roleProvider; private RoleProvider roleProvider;
private UserProvider userProvider;
private ClientScopeStorageManager clientScopeStorageManager; private ClientScopeStorageManager clientScopeStorageManager;
private RoleStorageManager roleStorageManager; private RoleStorageManager roleStorageManager;
private GroupStorageManager groupStorageManager; private GroupStorageManager groupStorageManager;
private ClientStorageManager clientStorageManager; private ClientStorageManager clientStorageManager;
private UserProvider userStorageManager;
public LegacyDatastoreProvider(LegacyDatastoreProviderFactory factory, KeycloakSession session) { public LegacyDatastoreProvider(LegacyDatastoreProviderFactory factory, KeycloakSession session) {
this.factory = factory; this.factory = factory;
@ -66,6 +72,13 @@ public class LegacyDatastoreProvider implements DatastoreProvider {
return groupStorageManager; return groupStorageManager;
} }
public UserProvider userStorageManager() {
if (userStorageManager == null) {
userStorageManager = new UserStorageManager(session);
}
return userStorageManager;
}
private ClientProvider getClientProvider() { private ClientProvider getClientProvider() {
// TODO: Extract ClientProvider from CacheRealmProvider and use that instead // TODO: Extract ClientProvider from CacheRealmProvider and use that instead
ClientProvider cache = session.getProvider(CacheRealmProvider.class); ClientProvider cache = session.getProvider(CacheRealmProvider.class);
@ -115,6 +128,15 @@ public class LegacyDatastoreProvider implements DatastoreProvider {
} }
} }
private UserProvider getUserProvider() {
UserCache cache = session.getProvider(UserCache.class);
if (cache != null) {
return cache;
} else {
return userStorageManager();
}
}
@Override @Override
public ClientProvider clients() { public ClientProvider clients() {
if (clientProvider == null) { if (clientProvider == null) {
@ -154,4 +176,12 @@ public class LegacyDatastoreProvider implements DatastoreProvider {
} }
return roleProvider; return roleProvider;
} }
@Override
public UserProvider users() {
if (userProvider == null) {
userProvider = getUserProvider();
}
return userProvider;
}
} }

View file

@ -0,0 +1,90 @@
package org.keycloak.storage.datastore;
import org.keycloak.Config;
import org.keycloak.Config.Scope;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.utils.PostMigrationEvent;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.provider.ProviderEventListener;
import org.keycloak.services.managers.UserStorageSyncManager;
import org.keycloak.services.scheduled.ClearExpiredClientInitialAccessTokens;
import org.keycloak.services.scheduled.ClearExpiredEvents;
import org.keycloak.services.scheduled.ClearExpiredUserSessions;
import org.keycloak.services.scheduled.ClusterAwareScheduledTaskRunner;
import org.keycloak.services.scheduled.ScheduledTaskRunner;
import org.keycloak.storage.DatastoreProvider;
import org.keycloak.storage.DatastoreProviderFactory;
import org.keycloak.storage.LegacyStoreSyncEvent;
import org.keycloak.timer.TimerProvider;
public class LegacyDatastoreProviderFactory implements DatastoreProviderFactory, ProviderEventListener {
private static final String PROVIDER_ID = "legacy";
private long clientStorageProviderTimeout;
private long roleStorageProviderTimeout;
private Runnable onClose;
@Override
public DatastoreProvider create(KeycloakSession session) {
return new LegacyDatastoreProvider(this, session);
}
@Override
public void init(Scope config) {
clientStorageProviderTimeout = Config.scope("client").getLong("storageProviderTimeout", 3000L);
roleStorageProviderTimeout = Config.scope("role").getLong("storageProviderTimeout", 3000L);
}
@Override
public void postInit(KeycloakSessionFactory factory) {
factory.register(this);
onClose = () -> factory.unregister(this);
}
@Override
public void close() {
onClose.run();
}
@Override
public String getId() {
return PROVIDER_ID;
}
public long getClientStorageProviderTimeout() {
return clientStorageProviderTimeout;
}
public long getRoleStorageProviderTimeout() {
return roleStorageProviderTimeout;
}
@Override
public void onEvent(ProviderEvent event) {
if (event instanceof PostMigrationEvent) {
setupScheduledTasks(((PostMigrationEvent) event).getFactory());
} else if (event instanceof LegacyStoreSyncEvent) {
LegacyStoreSyncEvent ev = (LegacyStoreSyncEvent) event;
UserStorageSyncManager.notifyToRefreshPeriodicSyncAll(ev.getSession(), ev.getRealm(), ev.getRemoved());
}
}
public static void setupScheduledTasks(final KeycloakSessionFactory sessionFactory) {
long interval = Config.scope("scheduled").getLong("interval", 900L) * 1000;
KeycloakSession session = sessionFactory.create();
try {
TimerProvider timer = session.getProvider(TimerProvider.class);
if (timer != null) {
timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredEvents(), interval), interval, "ClearExpiredEvents");
timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredClientInitialAccessTokens(), interval), interval, "ClearExpiredClientInitialAccessTokens");
timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions()), interval, ClearExpiredUserSessions.TASK_NAME);
UserStorageSyncManager.bootstrapPeriodic(sessionFactory, timer);
}
} finally {
session.close();
}
}
}

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.keycloak.services.managers; package org.keycloak.storage.managers;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.cluster.ClusterEvent; import org.keycloak.cluster.ClusterEvent;
@ -22,12 +22,12 @@ import org.keycloak.cluster.ClusterListener;
import org.keycloak.cluster.ClusterProvider; import org.keycloak.cluster.ClusterProvider;
import org.keycloak.cluster.ExecutionResult; import org.keycloak.cluster.ExecutionResult;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakSessionTask; import org.keycloak.models.KeycloakSessionTask;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.services.ServicesLogger;
import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderFactory; import org.keycloak.storage.UserStorageProviderFactory;
import org.keycloak.storage.UserStorageProviderModel; import org.keycloak.storage.UserStorageProviderModel;
@ -54,7 +54,7 @@ public class UserStorageSyncManager {
* @param sessionFactory * @param sessionFactory
* @param timer * @param timer
*/ */
public void bootstrapPeriodic(final KeycloakSessionFactory sessionFactory, final TimerProvider timer) { public static void bootstrapPeriodic(final KeycloakSessionFactory sessionFactory, final TimerProvider timer) {
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() { KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override @Override
@ -80,7 +80,7 @@ public class UserStorageSyncManager {
ExecutionResult<SynchronizationResult> result; ExecutionResult<SynchronizationResult> result;
} }
public SynchronizationResult syncAllUsers(final KeycloakSessionFactory sessionFactory, final String realmId, final UserStorageProviderModel provider) { public static SynchronizationResult syncAllUsers(final KeycloakSessionFactory sessionFactory, final String realmId, final UserStorageProviderModel provider) {
UserStorageProviderFactory factory = (UserStorageProviderFactory) sessionFactory.getProviderFactory(UserStorageProvider.class, provider.getProviderId()); UserStorageProviderFactory factory = (UserStorageProviderFactory) sessionFactory.getProviderFactory(UserStorageProvider.class, provider.getProviderId());
if (!(factory instanceof ImportSynchronization) || !provider.isImportEnabled() || !provider.isEnabled()) { if (!(factory instanceof ImportSynchronization) || !provider.isImportEnabled() || !provider.isEnabled()) {
return SynchronizationResult.ignored(); return SynchronizationResult.ignored();
@ -121,7 +121,7 @@ public class UserStorageSyncManager {
} }
} }
public SynchronizationResult syncChangedUsers(final KeycloakSessionFactory sessionFactory, final String realmId, final UserStorageProviderModel provider) { public static SynchronizationResult syncChangedUsers(final KeycloakSessionFactory sessionFactory, final String realmId, final UserStorageProviderModel provider) {
UserStorageProviderFactory factory = (UserStorageProviderFactory) sessionFactory.getProviderFactory(UserStorageProvider.class, provider.getProviderId()); UserStorageProviderFactory factory = (UserStorageProviderFactory) sessionFactory.getProviderFactory(UserStorageProvider.class, provider.getProviderId());
if (!(factory instanceof ImportSynchronization) || !provider.isImportEnabled() || !provider.isEnabled()) { if (!(factory instanceof ImportSynchronization) || !provider.isImportEnabled() || !provider.isEnabled()) {
return SynchronizationResult.ignored(); return SynchronizationResult.ignored();
@ -164,8 +164,17 @@ public class UserStorageSyncManager {
} }
public static void notifyToRefreshPeriodicSyncAll(KeycloakSession session, RealmModel realm, boolean removed) {
realm.getUserStorageProvidersStream().forEachOrdered(fedProvider ->
notifyToRefreshPeriodicSync(session, realm, fedProvider, removed));
}
public static void notifyToRefreshPeriodicSyncSingle(KeycloakSession session, RealmModel realm, ComponentModel component, boolean removed) {
notifyToRefreshPeriodicSync(session, realm, new UserStorageProviderModel(component), removed);
}
// Ensure all cluster nodes are notified // Ensure all cluster nodes are notified
public void notifyToRefreshPeriodicSync(KeycloakSession session, RealmModel realm, UserStorageProviderModel provider, boolean removed) { public static void notifyToRefreshPeriodicSync(KeycloakSession session, RealmModel realm, UserStorageProviderModel provider, boolean removed) {
UserStorageProviderFactory factory = (UserStorageProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, provider.getProviderId()); UserStorageProviderFactory factory = (UserStorageProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, provider.getProviderId());
if (!(factory instanceof ImportSynchronization) || !provider.isImportEnabled()) { if (!(factory instanceof ImportSynchronization) || !provider.isImportEnabled()) {
return; return;
@ -180,7 +189,7 @@ public class UserStorageSyncManager {
// Executed once it receives notification that some UserFederationProvider was created or updated // Executed once it receives notification that some UserFederationProvider was created or updated
protected void refreshPeriodicSyncForProvider(final KeycloakSessionFactory sessionFactory, TimerProvider timer, final UserStorageProviderModel provider, final String realmId) { protected static void refreshPeriodicSyncForProvider(final KeycloakSessionFactory sessionFactory, TimerProvider timer, final UserStorageProviderModel provider, final String realmId) {
logger.debugf("Going to refresh periodic sync for provider '%s' . Full sync period: %d , changed users sync period: %d", logger.debugf("Going to refresh periodic sync for provider '%s' . Full sync period: %d , changed users sync period: %d",
provider.getName(), provider.getFullSyncPeriod(), provider.getChangedSyncPeriod()); provider.getName(), provider.getFullSyncPeriod(), provider.getChangedSyncPeriod());
@ -198,7 +207,7 @@ public class UserStorageSyncManager {
logger.debugf("Ignored periodic full sync with storage provider %s due small time since last sync", provider.getName()); logger.debugf("Ignored periodic full sync with storage provider %s due small time since last sync", provider.getName());
} }
} catch (Throwable t) { } catch (Throwable t) {
ServicesLogger.LOGGER.errorDuringFullUserSync(t); logger.error("Error occurred during full sync of users", t);
} }
} }
@ -221,7 +230,7 @@ public class UserStorageSyncManager {
logger.debugf("Ignored periodic changed-users sync with storage provider %s due small time since last sync", provider.getName()); logger.debugf("Ignored periodic changed-users sync with storage provider %s due small time since last sync", provider.getName());
} }
} catch (Throwable t) { } catch (Throwable t) {
ServicesLogger.LOGGER.errorDuringChangedUserSync(t); logger.error("Error occurred during sync of changed users", t);
} }
} }
@ -233,7 +242,7 @@ public class UserStorageSyncManager {
} }
// Skip syncing if there is short time since last sync time. // Skip syncing if there is short time since last sync time.
private boolean shouldPerformNewPeriodicSync(int lastSyncTime, int period) { private static boolean shouldPerformNewPeriodicSync(int lastSyncTime, int period) {
if (lastSyncTime <= 0) { if (lastSyncTime <= 0) {
return true; return true;
} }
@ -245,14 +254,14 @@ public class UserStorageSyncManager {
} }
// Executed once it receives notification that some UserFederationProvider was removed // Executed once it receives notification that some UserFederationProvider was removed
protected void removePeriodicSyncForProvider(TimerProvider timer, UserStorageProviderModel fedProvider) { protected static void removePeriodicSyncForProvider(TimerProvider timer, UserStorageProviderModel fedProvider) {
logger.debugf("Removing periodic sync for provider %s", fedProvider.getName()); logger.debugf("Removing periodic sync for provider %s", fedProvider.getName());
timer.cancelTask(fedProvider.getId() + "-FULL"); timer.cancelTask(fedProvider.getId() + "-FULL");
timer.cancelTask(fedProvider.getId() + "-CHANGED"); timer.cancelTask(fedProvider.getId() + "-CHANGED");
} }
// Update interval of last sync for given UserFederationProviderModel. Do it in separate transaction // Update interval of last sync for given UserFederationProviderModel. Do it in separate transaction
private void updateLastSyncInterval(final KeycloakSessionFactory sessionFactory, UserStorageProviderModel provider, final String realmId) { private static void updateLastSyncInterval(final KeycloakSessionFactory sessionFactory, UserStorageProviderModel provider, final String realmId) {
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() { KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override @Override
@ -274,7 +283,7 @@ public class UserStorageSyncManager {
} }
private class UserStorageClusterListener implements ClusterListener { private static class UserStorageClusterListener implements ClusterListener {
private final KeycloakSessionFactory sessionFactory; private final KeycloakSessionFactory sessionFactory;

View file

@ -0,0 +1,40 @@
package org.keycloak.services.resources.admin;
import org.keycloak.Config.Scope;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.services.resources.admin.ext.AdminRealmResourceProvider;
import org.keycloak.services.resources.admin.ext.AdminRealmResourceProviderFactory;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
public class UserStorageProviderRealmAdminProvider implements AdminRealmResourceProviderFactory, AdminRealmResourceProvider {
@Override
public AdminRealmResourceProvider create(KeycloakSession session) {
return this;
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "user-storage";
}
@Override
public Object getResource(KeycloakSession session, RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
return new UserStorageProviderResource(realm, auth, adminEvent);
}
}

View file

@ -0,0 +1 @@
org.keycloak.services.resources.admin.UserStorageProviderRealmAdminProvider

View file

@ -0,0 +1,20 @@
package org.keycloak.storage;
import org.keycloak.models.ClientProvider;
import org.keycloak.models.ClientScopeProvider;
import org.keycloak.models.GroupProvider;
import org.keycloak.models.RoleProvider;
import org.keycloak.models.UserProvider;
public interface LegacyStoreManagers {
ClientProvider clientStorageManager();
ClientScopeProvider clientScopeStorageManager();
RoleProvider roleStorageManager();
GroupProvider groupStorageManager();
UserProvider userStorageManager();
}

View file

@ -1,48 +0,0 @@
package org.keycloak.storage.datastore;
import org.keycloak.Config;
import org.keycloak.Config.Scope;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.storage.DatastoreProvider;
import org.keycloak.storage.DatastoreProviderFactory;
public class LegacyDatastoreProviderFactory implements DatastoreProviderFactory {
private static final String PROVIDER_ID = "legacy";
private long clientStorageProviderTimeout;
private long roleStorageProviderTimeout;
@Override
public DatastoreProvider create(KeycloakSession session) {
return new LegacyDatastoreProvider(this, session);
}
@Override
public void init(Scope config) {
clientStorageProviderTimeout = Config.scope("client").getLong("storageProviderTimeout", 3000L);
roleStorageProviderTimeout = Config.scope("role").getLong("storageProviderTimeout", 3000L);
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
public long getClientStorageProviderTimeout() {
return clientStorageProviderTimeout;
}
public long getRoleStorageProviderTimeout() {
return roleStorageProviderTimeout;
}
}

View file

@ -0,0 +1,18 @@
#
# 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.
#
org.keycloak.storage.UserStorageProviderSpi

View file

@ -22,13 +22,13 @@ import org.keycloak.component.ComponentFactory;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserProvider;
import org.keycloak.provider.Provider; import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.storage.OnCreateComponent; import org.keycloak.storage.OnCreateComponent;
import org.keycloak.storage.OnUpdateComponent; import org.keycloak.storage.OnUpdateComponent;
import org.keycloak.storage.UserStorageProviderFactory;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -94,15 +94,17 @@ public class ComponentUtil {
public static void notifyCreated(KeycloakSession session, RealmModel realm, ComponentModel model) { public static void notifyCreated(KeycloakSession session, RealmModel realm, ComponentModel model) {
ComponentFactory factory = getComponentFactory(session, model); ComponentFactory factory = getComponentFactory(session, model);
factory.onCreate(session, realm, model); factory.onCreate(session, realm, model);
if (factory instanceof UserStorageProviderFactory) { UserProvider users = session.users();
((OnCreateComponent)session.userStorageManager()).onCreate(session, realm, model); if (users instanceof OnCreateComponent) {
((OnCreateComponent) users).onCreate(session, realm, model);
} }
} }
public static void notifyUpdated(KeycloakSession session, RealmModel realm, ComponentModel oldModel, ComponentModel newModel) { public static void notifyUpdated(KeycloakSession session, RealmModel realm, ComponentModel oldModel, ComponentModel newModel) {
ComponentFactory factory = getComponentFactory(session, newModel); ComponentFactory factory = getComponentFactory(session, newModel);
factory.onUpdate(session, realm, oldModel, newModel); factory.onUpdate(session, realm, oldModel, newModel);
if (factory instanceof UserStorageProviderFactory) { UserProvider users = session.users();
((OnUpdateComponent)session.userStorageManager()).onUpdate(session, realm, oldModel, newModel); if (users instanceof OnUpdateComponent) {
((OnUpdateComponent) users).onUpdate(session, realm, oldModel, newModel);
} }
} }
public static void notifyPreRemove(KeycloakSession session, RealmModel realm, ComponentModel model) { public static void notifyPreRemove(KeycloakSession session, RealmModel realm, ComponentModel model) {

View file

@ -17,6 +17,7 @@
package org.keycloak.models.utils; package org.keycloak.models.utils;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderEvent; import org.keycloak.provider.ProviderEvent;
/** /**
@ -25,4 +26,14 @@ import org.keycloak.provider.ProviderEvent;
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
public class PostMigrationEvent implements ProviderEvent { public class PostMigrationEvent implements ProviderEvent {
private final KeycloakSessionFactory factory;
public PostMigrationEvent(KeycloakSessionFactory factory) {
this.factory = factory;
}
public KeycloakSessionFactory getFactory() {
return this.factory;
}
} }

View file

@ -21,7 +21,6 @@ import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.services.ServicesLogger;
import org.keycloak.timer.ScheduledTask; import org.keycloak.timer.ScheduledTask;
/** /**
@ -54,7 +53,7 @@ public class ScheduledTaskRunner implements Runnable {
} }
runTask(session); runTask(session);
} catch (Throwable t) { } catch (Throwable t) {
ServicesLogger.LOGGER.failedToRunScheduledTask(t, task.getClass().getSimpleName()); logger.errorf(t, "Failed to run scheduled task %s", task.getClass().getSimpleName());
session.getTransactionManager().rollback(); session.getTransactionManager().rollback();
} finally { } finally {
@ -64,7 +63,7 @@ public class ScheduledTaskRunner implements Runnable {
try { try {
session.close(); session.close();
} catch (Throwable t) { } catch (Throwable t) {
ServicesLogger.LOGGER.failedToCloseProviderSession(t); logger.errorf(t, "Failed to close ProviderSession");
} }
} }
} }

View file

@ -5,6 +5,7 @@ import org.keycloak.models.ClientScopeProvider;
import org.keycloak.models.GroupProvider; import org.keycloak.models.GroupProvider;
import org.keycloak.models.RealmProvider; import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleProvider; import org.keycloak.models.RoleProvider;
import org.keycloak.models.UserProvider;
import org.keycloak.provider.Provider; import org.keycloak.provider.Provider;
public interface DatastoreProvider extends Provider { public interface DatastoreProvider extends Provider {
@ -19,4 +20,6 @@ public interface DatastoreProvider extends Provider {
public RoleProvider roles(); public RoleProvider roles();
public UserProvider users();
} }

View file

@ -0,0 +1,55 @@
/*
* Copyright 2022 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.storage;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderEvent;
/**
* Event for notifying legacy store about the need to reconfigure user providers
* sychronization.
*/
public class LegacyStoreSyncEvent implements ProviderEvent {
private final KeycloakSession session;
private final RealmModel realm;
private final boolean removed;
public LegacyStoreSyncEvent(KeycloakSession session, RealmModel realm, boolean removed) {
this.session = session;
this.realm = realm;
this.removed = removed;
}
public static void fire(KeycloakSession session, RealmModel realm, boolean removed) {
session.getKeycloakSessionFactory().publish(new LegacyStoreSyncEvent(session, realm, removed));
}
public KeycloakSession getSession() {
return session;
}
public RealmModel getRealm() {
return realm;
}
public boolean getRemoved() {
return removed;
}
}

View file

@ -17,7 +17,6 @@
org.keycloak.component.ComponentFactorySpi org.keycloak.component.ComponentFactorySpi
org.keycloak.provider.ExceptionConverterSpi org.keycloak.provider.ExceptionConverterSpi
org.keycloak.storage.UserStorageProviderSpi
org.keycloak.storage.federated.UserFederatedStorageProviderSpi org.keycloak.storage.federated.UserFederatedStorageProviderSpi
org.keycloak.models.ClientSpi org.keycloak.models.ClientSpi
org.keycloak.models.ClientScopeSpi org.keycloak.models.ClientScopeSpi

View file

@ -90,9 +90,9 @@ public class PermissionTicketService {
UserModel user = null; UserModel user = null;
if(representation.getRequester() != null) if(representation.getRequester() != null)
user = this.authorization.getKeycloakSession().userStorageManager().getUserById(this.authorization.getRealm(), representation.getRequester()); user = this.authorization.getKeycloakSession().users().getUserById(this.authorization.getRealm(), representation.getRequester());
else else
user = this.authorization.getKeycloakSession().userStorageManager().getUserByUsername(this.authorization.getRealm(), representation.getRequesterName()); user = this.authorization.getKeycloakSession().users().getUserByUsername(this.authorization.getRealm(), representation.getRequesterName());
if (user == null) if (user == null)
throw new ErrorResponseException("invalid_permission", "Requester does not exists in this server as user.", Response.Status.BAD_REQUEST); throw new ErrorResponseException("invalid_permission", "Requester does not exists in this server as user.", Response.Status.BAD_REQUEST);

View file

@ -47,7 +47,6 @@ import org.keycloak.provider.InvalidationHandler.ObjectType;
import org.keycloak.services.clientpolicy.ClientPolicyManager; import org.keycloak.services.clientpolicy.ClientPolicyManager;
import org.keycloak.sessions.AuthenticationSessionProvider; import org.keycloak.sessions.AuthenticationSessionProvider;
import org.keycloak.storage.DatastoreProvider; import org.keycloak.storage.DatastoreProvider;
import org.keycloak.storage.UserStorageManager;
import org.keycloak.storage.federated.UserFederatedStorageProvider; import org.keycloak.storage.federated.UserFederatedStorageProvider;
import org.keycloak.vault.DefaultVaultTranscriber; import org.keycloak.vault.DefaultVaultTranscriber;
import org.keycloak.vault.VaultProvider; import org.keycloak.vault.VaultProvider;
@ -77,7 +76,6 @@ public class DefaultKeycloakSession implements KeycloakSession {
private final Map<String, Object> attributes = new HashMap<>(); private final Map<String, Object> attributes = new HashMap<>();
private final Map<InvalidableObjectType, Set<Object>> invalidationMap = new HashMap<>(); private final Map<InvalidableObjectType, Set<Object>> invalidationMap = new HashMap<>();
private DatastoreProvider datastoreProvider; private DatastoreProvider datastoreProvider;
private UserStorageManager userStorageManager;
private UserCredentialStoreManager userCredentialStorageManager; private UserCredentialStoreManager userCredentialStorageManager;
private UserSessionProvider sessionProvider; private UserSessionProvider sessionProvider;
private UserLoginFailureProvider userLoginFailureProvider; private UserLoginFailureProvider userLoginFailureProvider;
@ -217,21 +215,14 @@ public class DefaultKeycloakSession implements KeycloakSession {
return groups(); return groups();
} }
@Override @Override
public UserProvider userStorageManager() { public UserProvider userStorageManager() {
if (userStorageManager == null) userStorageManager = new UserStorageManager(this); return users();
return userStorageManager;
} }
@Override @Override
public UserProvider users() { public UserProvider users() {
UserCache cache = getProvider(UserCache.class); return getDatastoreProvider().users();
if (cache != null) {
return cache;
} else {
return userStorageManager();
}
} }
@Override @Override

View file

@ -52,6 +52,7 @@ import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.sessions.AuthenticationSessionProvider; import org.keycloak.sessions.AuthenticationSessionProvider;
import org.keycloak.storage.LegacyStoreSyncEvent;
import org.keycloak.services.clientregistration.policy.DefaultClientRegistrationPolicies; import org.keycloak.services.clientregistration.policy.DefaultClientRegistrationPolicies;
import java.util.Collections; import java.util.Collections;
@ -271,10 +272,7 @@ public class RealmManager {
} }
// Refresh periodic sync tasks for configured storageProviders // Refresh periodic sync tasks for configured storageProviders
UserStorageSyncManager storageSync = new UserStorageSyncManager(); LegacyStoreSyncEvent.fire(session, realm, true);
realm.getUserStorageProvidersStream()
.forEachOrdered(provider -> storageSync.notifyToRefreshPeriodicSync(session, realm, provider, true));
} }
return removed; return removed;
} }
@ -588,9 +586,7 @@ public class RealmManager {
} }
// Refresh periodic sync tasks for configured storageProviders // Refresh periodic sync tasks for configured storageProviders
UserStorageSyncManager storageSync = new UserStorageSyncManager(); LegacyStoreSyncEvent.fire(session, realm, false);
realm.getUserStorageProvidersStream()
.forEachOrdered(provider -> storageSync.notifyToRefreshPeriodicSync(session, realm, provider, false));
setupAuthorizationServices(realm); setupAuthorizationServices(realm);
setupClientRegistrations(realm); setupClientRegistrations(realm);

View file

@ -45,15 +45,8 @@ import org.keycloak.services.error.KcUnrecognizedPropertyExceptionHandler;
import org.keycloak.services.filters.KeycloakSecurityHeadersFilter; import org.keycloak.services.filters.KeycloakSecurityHeadersFilter;
import org.keycloak.services.managers.ApplianceBootstrap; import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.UserStorageSyncManager;
import org.keycloak.services.resources.admin.AdminRoot; import org.keycloak.services.resources.admin.AdminRoot;
import org.keycloak.services.scheduled.ClearExpiredClientInitialAccessTokens;
import org.keycloak.services.scheduled.ClearExpiredEvents;
import org.keycloak.services.scheduled.ClearExpiredUserSessions;
import org.keycloak.services.scheduled.ClusterAwareScheduledTaskRunner;
import org.keycloak.services.scheduled.ScheduledTaskRunner;
import org.keycloak.services.util.ObjectMapperResolver; import org.keycloak.services.util.ObjectMapperResolver;
import org.keycloak.timer.TimerProvider;
import org.keycloak.transaction.JtaTransactionManagerLookup; import org.keycloak.transaction.JtaTransactionManagerLookup;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
@ -156,9 +149,7 @@ public class KeycloakApplication extends Application {
}); });
sessionFactory.publish(new PostMigrationEvent()); sessionFactory.publish(new PostMigrationEvent(sessionFactory));
setupScheduledTasks(sessionFactory);
} }
protected void shutdown() { protected void shutdown() {
@ -236,21 +227,6 @@ public class KeycloakApplication extends Application {
return factory; return factory;
} }
public static void setupScheduledTasks(final KeycloakSessionFactory sessionFactory) {
long interval = Config.scope("scheduled").getLong("interval", 900L) * 1000;
KeycloakSession session = sessionFactory.create();
try {
TimerProvider timer = session.getProvider(TimerProvider.class);
timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredEvents(), interval), interval, "ClearExpiredEvents");
timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredClientInitialAccessTokens(), interval), interval, "ClearExpiredClientInitialAccessTokens");
timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions()), interval, ClearExpiredUserSessions.TASK_NAME);
new UserStorageSyncManager().bootstrapPeriodic(sessionFactory, timer);
} finally {
session.close();
}
}
public static KeycloakSessionFactory getSessionFactory() { public static KeycloakSessionFactory getSessionFactory() {
return sessionFactory; return sessionFactory;
} }

View file

@ -112,10 +112,11 @@ import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.LDAPServerCapabilitiesManager; import org.keycloak.services.managers.LDAPServerCapabilitiesManager;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.managers.UserStorageSyncManager; import org.keycloak.services.resources.admin.ext.AdminRealmResourceProvider;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement; import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
import org.keycloak.services.resources.admin.permissions.AdminPermissions; import org.keycloak.services.resources.admin.permissions.AdminPermissions;
import org.keycloak.storage.LegacyStoreSyncEvent;
import org.keycloak.utils.ProfileHelper; import org.keycloak.utils.ProfileHelper;
import org.keycloak.utils.ReservedCharValidator; import org.keycloak.utils.ReservedCharValidator;
@ -436,9 +437,7 @@ public class RealmAdminResource {
RepresentationToModel.updateRealm(rep, realm, session); RepresentationToModel.updateRealm(rep, realm, session);
// Refresh periodic sync tasks for configured federationProviders // Refresh periodic sync tasks for configured federationProviders
UserStorageSyncManager usersSyncManager = new UserStorageSyncManager(); LegacyStoreSyncEvent.fire(session, realm, false);
realm.getUserStorageProvidersStream().forEachOrdered(fedProvider ->
usersSyncManager.notifyToRefreshPeriodicSync(session, realm, fedProvider, false));
// This populates the map in DefaultKeycloakContext to be used when treating the event // This populates the map in DefaultKeycloakContext to be used when treating the event
session.getContext().getUri(); session.getContext().getUri();
@ -531,12 +530,18 @@ public class RealmAdminResource {
} }
@Path("user-storage") @Path("{extension}")
public UserStorageProviderResource userStorage() { public Object extension(@PathParam("extension") String extension) {
UserStorageProviderResource fed = new UserStorageProviderResource(realm, auth, adminEvent); AdminRealmResourceProvider provider = session.getProvider(AdminRealmResourceProvider.class, extension);
ResteasyProviderFactory.getInstance().injectProperties(fed); if (provider != null) {
//resourceContext.initResource(fed); Object resource = provider.getResource(session, realm, auth, adminEvent);
return fed; if (resource != null) {
ResteasyProviderFactory.getInstance().injectProperties(resource);
return resource;
}
}
throw new NotFoundException();
} }
@Path("authentication") @Path("authentication")

View file

@ -0,0 +1,41 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2022 Red Hat, Inc., and individual 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.ext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.Provider;
import org.keycloak.services.resources.admin.AdminEventBuilder;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
/**
* <p>A {@link AdminRealmResourceProvider} creates JAX-RS <emphasis>sub-resource</emphasis> instances for paths relative
* to Realm's RESTful Admin API that could not be resolved by the server.
*/
public interface AdminRealmResourceProvider extends Provider {
/**
* <p>Returns a JAX-RS resource instance.
*
* @return a JAX-RS sub-resource instance
*/
Object getResource(KeycloakSession session, RealmModel realm, AdminPermissionEvaluator auth,
AdminEventBuilder adminEvent);
}

View file

@ -0,0 +1,28 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2022 Red Hat, Inc., and individual 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.ext;
import org.keycloak.provider.ProviderFactory;
/**
* <p>A factory that creates {@link AdminRealmResourceProvider} instances.
*/
public interface AdminRealmResourceProviderFactory extends ProviderFactory<AdminRealmResourceProvider> {
}

View file

@ -0,0 +1,52 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2022 Red Hat, Inc., and individual 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.ext;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* <p>A {@link Spi} to plug additional sub-resources to Realms' RESTful Admin API.
*
* <p>Implementors can use this {@link Spi} to provide additional services to the mentioned API and extend Keycloak capabilities by
* creating JAX-RS sub-resources for paths not known by the server.
*/
public class AdminRealmResourceSpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "admin-realm-restapi-extension";
}
@Override
public Class<? extends Provider> getProviderClass() {
return AdminRealmResourceProvider.class;
}
@Override
public Class<? extends ProviderFactory<?>> getProviderFactoryClass() {
return AdminRealmResourceProviderFactory.class;
}
}

View file

@ -27,3 +27,4 @@ org.keycloak.encoding.ResourceEncodingSpi
org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChannelSpi org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChannelSpi
org.keycloak.protocol.oidc.grants.ciba.resolvers.CIBALoginUserResolverSpi org.keycloak.protocol.oidc.grants.ciba.resolvers.CIBALoginUserResolverSpi
org.keycloak.protocol.oidc.rar.AuthorizationRequestParserSpi org.keycloak.protocol.oidc.rar.AuthorizationRequestParserSpi
org.keycloak.services.resources.admin.ext.AdminRealmResourceSpi

View file

@ -32,7 +32,8 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.services.managers.UserStorageSyncManager; import org.keycloak.storage.managers.UserStorageSyncManager;
import org.keycloak.storage.UserStoragePrivateUtil;
import org.keycloak.storage.UserStorageProviderModel; import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.ldap.LDAPStorageProvider; import org.keycloak.storage.ldap.LDAPStorageProvider;
import org.keycloak.storage.ldap.idm.model.LDAPObject; import org.keycloak.storage.ldap.idm.model.LDAPObject;

View file

@ -40,7 +40,9 @@ import org.keycloak.models.cache.UserCache;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.SynchronizationResultRepresentation; import org.keycloak.representations.idm.SynchronizationResultRepresentation;
import org.keycloak.services.managers.UserStorageSyncManager; import org.keycloak.storage.managers.UserStorageSyncManager;
import org.keycloak.storage.UserStoragePrivateUtil;
import org.keycloak.storage.UserStorageUtil;
import org.keycloak.storage.ldap.LDAPStorageProvider; import org.keycloak.storage.ldap.LDAPStorageProvider;
import org.keycloak.storage.ldap.LDAPStorageProviderFactory; import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
import org.keycloak.storage.ldap.LDAPUtils; import org.keycloak.storage.ldap.LDAPUtils;

View file

@ -51,6 +51,8 @@ import org.keycloak.provider.ProviderManager;
import org.keycloak.provider.Spi; import org.keycloak.provider.Spi;
import org.keycloak.services.DefaultComponentFactoryProviderFactory; import org.keycloak.services.DefaultComponentFactoryProviderFactory;
import org.keycloak.services.DefaultKeycloakSessionFactory; import org.keycloak.services.DefaultKeycloakSessionFactory;
import org.keycloak.storage.DatastoreProviderFactory;
import org.keycloak.storage.DatastoreSpi;
import org.keycloak.timer.TimerSpi; import org.keycloak.timer.TimerSpi;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -227,6 +229,7 @@ public abstract class KeycloakModelTest {
.add(UserLoginFailureSpi.class) .add(UserLoginFailureSpi.class)
.add(UserSessionSpi.class) .add(UserSessionSpi.class)
.add(UserSpi.class) .add(UserSpi.class)
.add(DatastoreSpi.class)
.build(); .build();
private static final Set<Class<? extends ProviderFactory>> ALLOWED_FACTORIES = ImmutableSet.<Class<? extends ProviderFactory>>builder() private static final Set<Class<? extends ProviderFactory>> ALLOWED_FACTORIES = ImmutableSet.<Class<? extends ProviderFactory>>builder()
@ -234,6 +237,7 @@ public abstract class KeycloakModelTest {
.add(DefaultAuthorizationProviderFactory.class) .add(DefaultAuthorizationProviderFactory.class)
.add(DefaultExecutorsProviderFactory.class) .add(DefaultExecutorsProviderFactory.class)
.add(DeploymentStateProviderFactory.class) .add(DeploymentStateProviderFactory.class)
.add(DatastoreProviderFactory.class)
.build(); .build();
protected static final List<KeycloakModelParameters> MODEL_PARAMETERS; protected static final List<KeycloakModelParameters> MODEL_PARAMETERS;
@ -312,7 +316,7 @@ public abstract class KeycloakModelTest {
} }
}; };
res.init(); res.init();
res.publish(new PostMigrationEvent()); res.publish(new PostMigrationEvent(res));
return res; return res;
} }

View file

@ -9,7 +9,8 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider; import org.keycloak.models.RealmProvider;
import org.keycloak.models.UserProvider; import org.keycloak.models.UserProvider;
import org.keycloak.services.managers.UserStorageSyncManager; import org.keycloak.storage.managers.UserStorageSyncManager;
import org.keycloak.storage.UserStoragePrivateUtil;
import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderFactory; import org.keycloak.storage.UserStorageProviderFactory;
import org.keycloak.storage.UserStorageProviderModel; import org.keycloak.storage.UserStorageProviderModel;