Admin UI: slow response time listing second user page

Fixes #44860

Signed-off-by: Pedro Ruivo <1492066+pruivo@users.noreply.github.com>
Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com>
Co-authored-by: Pedro Ruivo <1492066+pruivo@users.noreply.github.com>
Co-authored-by: Alexander Schwartz <alexander.schwartz@ibm.com>
This commit is contained in:
Pedro Ruivo 2026-01-07 12:23:28 +00:00 committed by GitHub
parent b3fa28b63f
commit f5d2f80697
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 169 additions and 151 deletions

View File

@ -95,7 +95,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
private static final char ESCAPE_BACKSLASH = '\\';
private final KeycloakSession session;
protected EntityManager em;
protected final EntityManager em;
private final JpaUserCredentialStore credentialStore;
public JpaUserProvider(KeycloakSession session, EntityManager em) {
@ -151,7 +151,6 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
}
private void removeUser(UserEntity user) {
String id = user.getId();
em.createNamedQuery("deleteUserRoleMappingsByUser").setParameter("user", user).executeUpdate();
em.createNamedQuery("deleteUserGroupMembershipsByUser").setParameter("user", user).executeUpdate();
em.createNamedQuery("deleteUserConsentClientScopesByUser").setParameter("user", user).executeUpdate();
@ -178,6 +177,9 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
@Override
public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
FederatedIdentityEntity federatedIdentity = findFederatedIdentity(federatedUser, federatedIdentityModel.getIdentityProvider(), LockModeType.PESSIMISTIC_WRITE);
if (federatedIdentity == null) {
return;
}
federatedIdentity.setUserName(federatedIdentityModel.getUserName());
federatedIdentity.setToken(federatedIdentityModel.getToken());
@ -373,7 +375,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
@Override
public void grantToAllUsers(RealmModel realm, RoleModel role) {
if (realm.equals(role.isClientRole() ? ((ClientModel)role.getContainer()).getRealm() : (RealmModel)role.getContainer())) {
if (realm.equals(role.isClientRole() ? ((ClientModel)role.getContainer()).getRealm() : role.getContainer())) {
em.createNamedQuery("grantRoleToAllUsers")
.setParameter("realmId", realm.getId())
.setParameter("roleId", role.getId())
@ -593,7 +595,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
}
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.USERS, this, realm, cb, query, root));
query.select(cb.count(root)).where(predicates.toArray(Predicate[]::new));
query.select(cb.count(root)).where(predicates);
return em.createQuery(query).getSingleResult().intValue();
}
@ -618,21 +620,13 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
CriteriaQuery<Long> queryBuilder = builder.createQuery(Long.class);
Root<UserEntity> root = queryBuilder.from(UserEntity.class);
queryBuilder.select(builder.countDistinct(root));
List<Predicate> predicates = new ArrayList<>();
predicates.add(builder.equal(root.get("realmId"), realm.getId()));
for (String stringToSearch : search.trim().split("\\s+")) {
predicates.add(builder.or(getSearchOptionPredicateArray(stringToSearch, builder, root)));
}
addSearchPredicates(search, builder, root, predicates);
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.USERS, this, realm, builder, queryBuilder, root));
queryBuilder.where(predicates.toArray(Predicate[]::new));
return em.createQuery(queryBuilder).getSingleResult().intValue();
return em.createQuery(countQuery(queryBuilder, builder, root, predicates)).getSingleResult().intValue();
}
@Override
@ -647,21 +641,13 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
Root<UserGroupMembershipEntity> groupMembership = queryBuilder.from(UserGroupMembershipEntity.class);
Join<UserGroupMembershipEntity, UserEntity> userJoin = groupMembership.join("user");
queryBuilder.select(builder.countDistinct(userJoin));
List<Predicate> predicates = new ArrayList<>();
predicates.add(builder.equal(userJoin.get("realmId"), realm.getId()));
for (String stringToSearch : search.trim().split("\\s+")) {
predicates.add(builder.or(getSearchOptionPredicateArray(stringToSearch, builder, userJoin)));
}
addSearchPredicates(search, builder, userJoin, predicates);
predicates.add(groupMembership.get("groupId").in(groupIds));
queryBuilder.where(predicates.toArray(Predicate[]::new));
return em.createQuery(queryBuilder).getSingleResult().intValue();
return em.createQuery(countQuery(queryBuilder, builder, userJoin, predicates)).getSingleResult().intValue();
}
@Override
@ -669,15 +655,12 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
CriteriaBuilder qb = em.getCriteriaBuilder();
CriteriaQuery<Long> userQuery = qb.createQuery(Long.class);
Root<UserEntity> from = userQuery.from(UserEntity.class);
Expression<Long> count = qb.countDistinct(from);
userQuery = userQuery.select(count);
List<Predicate> restrictions = predicates(params, from, Map.of());
restrictions.add(qb.equal(from.get("realmId"), realm.getId()));
restrictions.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.USERS, this, realm, qb, userQuery, from));
userQuery = userQuery.where(restrictions.toArray(Predicate[]::new));
TypedQuery<Long> query = em.createQuery(userQuery);
TypedQuery<Long> query = em.createQuery(countQuery(userQuery, qb, from, restrictions));
Long result = query.getSingleResult();
return result.intValue();
@ -703,7 +686,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
restrictions.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.USERS, this, realm, cb, countQuery, root));
countQuery.where(restrictions.toArray(Predicate[]::new));
countQuery.where(restrictions);
TypedQuery<Long> query = em.createQuery(countQuery);
Long result = query.getSingleResult();
@ -725,7 +708,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.USERS, this, realm, builder, queryBuilder, userPath));
queryBuilder.where(predicates.toArray(Predicate[]::new)).orderBy(builder.asc(userPath.get(UserModel.USERNAME)));
queryBuilder.where(predicates).orderBy(builder.asc(userPath.get(UserModel.USERNAME)));
return closing(paginateQuery(em.createQuery(queryBuilder), firstResult, maxResults).getResultStream().map(user -> new UserAdapter(session, realm, em, user)));
}
@ -769,7 +752,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.USERS, this, realm, builder, queryBuilder, userPath));
queryBuilder.where(predicates.toArray(Predicate[]::new)).orderBy(builder.asc(userPath.get(UserModel.USERNAME)));
queryBuilder.where(predicates).orderBy(builder.asc(userPath.get(UserModel.USERNAME)));
return closing(paginateQuery(em.createQuery(queryBuilder), first, max).getResultStream().map(user -> new UserAdapter(session, realm, em, user)));
}
@ -808,7 +791,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.USERS, this, realm, builder, queryBuilder, root));
queryBuilder.distinct(true).where(predicates.toArray(Predicate[]::new)).orderBy(builder.asc(root.get(UserModel.USERNAME)));
queryBuilder.distinct(true).where(predicates).orderBy(builder.asc(root.get(UserModel.USERNAME)));
TypedQuery<UserEntity> query = em.createQuery(queryBuilder);
@ -971,7 +954,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
}
}
private Predicate[] getSearchOptionPredicateArray(String value, CriteriaBuilder builder, From<?, UserEntity> from) {
private static Predicate getSearchOptionPredicate(String value, CriteriaBuilder builder, From<?, UserEntity> from) {
value = value.toLowerCase();
List<Predicate> orPredicates = new ArrayList<>();
@ -995,7 +978,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
orPredicates.add(builder.like(builder.lower(from.get(LAST_NAME)), value, ESCAPE_BACKSLASH));
}
return orPredicates.toArray(Predicate[]::new);
return builder.or(orPredicates);
}
private UserEntity userInEntityManagerContext(String id) {
@ -1021,9 +1004,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
switch (key) {
case UserModel.SEARCH:
for (String stringToSearch : value.trim().split("\\s+")) {
predicates.add(builder.or(getSearchOptionPredicateArray(stringToSearch, builder, root)));
}
addSearchPredicates(value, builder, root, predicates);
break;
case FIRST_NAME:
case LAST_NAME:
@ -1061,8 +1042,15 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
break;
case UserModel.EXACT:
break;
// All unknown attributes will be assumed as custom attributes
case UserModel.INCLUDE_SERVICE_ACCOUNT: {
if (!attributes.containsKey(UserModel.INCLUDE_SERVICE_ACCOUNT)
|| !Boolean.parseBoolean(attributes.get(UserModel.INCLUDE_SERVICE_ACCOUNT))) {
predicates.add(root.get("serviceAccountClientLink").isNull());
}
break;
}
default:
// All unknown attributes will be assumed as custom attributes
Join<UserEntity, UserAttributeEntity> attributesJoin = root.join("attributes", JoinType.LEFT);
if (value.length() > 255) {
customLongValueSearchAttributes.put(key, value);
@ -1081,18 +1069,11 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
}
}
break;
case UserModel.INCLUDE_SERVICE_ACCOUNT: {
if (!attributes.containsKey(UserModel.INCLUDE_SERVICE_ACCOUNT)
|| !Boolean.parseBoolean(attributes.get(UserModel.INCLUDE_SERVICE_ACCOUNT))) {
predicates.add(root.get("serviceAccountClientLink").isNull());
}
break;
}
}
}
if (!attributePredicates.isEmpty()) {
predicates.add(builder.and(attributePredicates.toArray(Predicate[]::new)));
predicates.add(builder.and(attributePredicates));
}
return predicates;
@ -1107,4 +1088,21 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
public EntityManager getEntityManager() {
return em;
}
private static void addSearchPredicates(String search, CriteriaBuilder builder, From<?, UserEntity> from, List<Predicate> predicates) {
if (search == null) {
return;
}
for (String stringToSearch : search.trim().split("\\s+")) {
predicates.add(getSearchOptionPredicate(stringToSearch, builder, from));
}
}
private static CriteriaQuery<Long> countQuery(CriteriaQuery<Long> query, CriteriaBuilder builder, From<?, UserEntity> from, List<Predicate> predicates) {
// When joining multiple tables, issuing a "distinct" is required to get the correct result.
// At the same time it is more expensive than a regular count as it would need to sort/keep all keys in memory at the database for the duration of the query.
// Therefore, we use a standard count where possible.
return query.select(from.getJoins().isEmpty() ? builder.count(from) : builder.countDistinct(from))
.where(predicates);
}
}

View File

@ -27,11 +27,9 @@ 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.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.opentelemetry.api.trace.StatusCode;
@ -110,10 +108,6 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
/**
* Allows a UserStorageProvider to proxy and/or synchronize an imported user.
*
* @param realm
* @param user
* @return
*/
protected UserModel validateUser(RealmModel realm, UserModel user) {
if (user == null) {
@ -218,7 +212,8 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
private static <T> Stream<T> getCredentialProviders(KeycloakSession session, Class<T> type) {
return session.getKeycloakSessionFactory().getProviderFactoriesStream(CredentialProvider.class)
.filter(f -> Types.supports(type, f, CredentialProviderFactory.class))
.map(f -> (T) session.getProvider(CredentialProvider.class, f.getId()));
.map(f -> session.getProvider(CredentialProvider.class, f.getId()))
.map(type::cast);
}
@Override
@ -231,7 +226,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
CredentialValidationOutput result = null;
for (CredentialAuthentication credentialAuthentication : credentialAuthenticationStream
.filter(credentialAuthentication -> credentialAuthentication.supportsCredentialAuthenticationFor(input.getType()))
.collect(Collectors.toList())) {
.toList()) {
CredentialValidationOutput validationOutput = session.getProvider(TracingProvider.class).trace(credentialAuthentication.getClass(), "authenticate",
span -> {
CredentialValidationOutput output = credentialAuthentication.authenticate(realm, input);
@ -301,109 +296,133 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
}
@FunctionalInterface
interface PaginatedQuery {
protected interface PaginatedQuery {
Stream<UserModel> query(Object provider, Integer firstResult, Integer maxResults);
}
@FunctionalInterface
interface CountQuery {
protected interface CountQuery {
int query(Object provider, Integer firstResult, Integer maxResult);
}
protected Stream<UserModel> query(PaginatedQuery pagedQuery, RealmModel realm, Integer firstResult, Integer maxResults) {
return query(pagedQuery, ((provider, first, max) -> (int) pagedQuery.query(provider, first, max).count()), realm, firstResult, maxResults);
return query(pagedQuery, ((provider, first, max) -> (int) pagedQuery.query(provider, first, max).count()), realm, firstResult, maxResults, true);
}
protected Stream<UserModel> query(PaginatedQuery pagedQuery, CountQuery countQuery, RealmModel realm, Integer firstResult, Integer maxResults) {
if (maxResults != null && maxResults == 0) return Stream.empty();
Stream<Object> providersStream = Stream.concat(Stream.of((Object) localStorage()), getEnabledStorageProviders(realm, UserQueryMethodsProvider.class));
UserFederatedStorageProvider federatedStorageProvider = getFederatedStorage();
if (federatedStorageProvider != null) {
providersStream = Stream.concat(providersStream, Stream.of(federatedStorageProvider));
protected Stream<UserModel> query(PaginatedQuery pagedQuery, CountQuery countQuery, RealmModel realm, Integer firstResult, Integer maxResults, boolean requiresFederatedStorage) {
if (maxResults != null && maxResults == 0) {
return Stream.empty();
}
final AtomicInteger currentFirst;
final AtomicBoolean needsAdditionalFirstResultFiltering = new AtomicBoolean(false);
var storageProviders = getEnabledStorageProviders(realm, UserQueryMethodsProvider.class).toList();
if (firstResult == null || firstResult <= 0) { // We don't want to skip any users so we don't need to do firstResult filtering
currentFirst = new AtomicInteger(0);
} else {
// This is an optimization using count query to skip querying users if we can use count method to determine how many users can be provided by each provider
AtomicBoolean droppingProviders = new AtomicBoolean(true);
currentFirst = new AtomicInteger(firstResult);
providersStream = providersStream
.filter(provider -> { // This is basically dropWhile
if (!droppingProviders.get()) return true; // We have already gathered enough users to pass firstResult number in previous providers, we can take all following providers
if (!(provider instanceof UserCountMethodsProvider)) {
logger.tracef("We encountered a provider (%s) that does not implement count queries therefore we can't say how many users it can provide.", provider.getClass().getSimpleName());
// for this reason we need to start querying this provider and all following providers
droppingProviders.set(false);
needsAdditionalFirstResultFiltering.set(true);
return true; // don't filter out this provider because we are unable to say how many users it can provide
}
long expectedNumberOfUsersForProvider = countQueryWithGracefulDegradation(provider, countQuery, 0, currentFirst.get() + 1); // check how many users we can obtain from this provider
logger.tracef("This provider (%s) is able to return %d users.", provider.getClass().getSimpleName(), expectedNumberOfUsersForProvider);
if (expectedNumberOfUsersForProvider == currentFirst.get()) { // This provider provides exactly the amount of users we need for passing firstResult, we can set currentFirst to 0 and drop this provider
currentFirst.set(0);
droppingProviders.set(false);
return false;
}
if (expectedNumberOfUsersForProvider > currentFirst.get()) { // If we can obtain enough enough users from this provider to fulfill our need we can stop dropping providers
droppingProviders.set(false);
return true; // don't filter out this provider because we are going to return some users from it
}
logger.tracef("This provider (%s) cannot provide enough users to pass firstResult so we are going to filter it out and change "
+ "firstResult for next provider: %d - %d = %d", provider.getClass().getSimpleName(),
currentFirst.get(), expectedNumberOfUsersForProvider, currentFirst.get() - expectedNumberOfUsersForProvider);
currentFirst.set((int) (currentFirst.get() - expectedNumberOfUsersForProvider));
return false;
})
// collecting stream of providers to ensure the filtering (above) is evaluated before we move forward to actual querying
.collect(Collectors.toList()).stream();
if (firstResult == null || firstResult <= 0) {
// we don't have a first result set, so we start from the beginning and go through all providers.
var providers = Stream.concat(Stream.of(localStorage()), concatExternalWithFederated(storageProviders, 0, requiresFederatedStorage));
return queryProviders(providers, pagedQuery, 0, maxResults, false);
}
if (needsAdditionalFirstResultFiltering.get() && currentFirst.get() > 0) {
logger.tracef("In the providerStream there is a provider that does not support count queries and we need to skip some users.");
// we need to make sure, we skip firstResult users from this or the following providers
if (maxResults == null || maxResults < 0) {
return paginatedStream(providersStream
.flatMap(provider -> queryWithGracefulDegradation(provider, pagedQuery, null, null)), currentFirst.get(), null);
} else {
final AtomicInteger currentMax = new AtomicInteger(currentFirst.get() + maxResults);
return paginatedStream(providersStream
.flatMap(provider -> queryWithGracefulDegradation(provider, pagedQuery, null, currentMax.get()))
.peek(userModel -> {
currentMax.updateAndGet(i -> i > 0 ? i - 1 : i);
}), currentFirst.get(), maxResults);
if (storageProviders.isEmpty()) {
if (requiresFederatedStorage) {
// we need to count the database.
return queryLocalAndFederatedStorage(pagedQuery, countQuery, firstResult, maxResults);
}
// fast path, stream from the database only without counting anything.
return queryLocalStorage(pagedQuery, firstResult, maxResults);
}
// Actual user querying
var localStorage = localStorage();
var count = countQueryWithGracefulDegradation(localStorage, countQuery, 0, firstResult + 1);
if (count > firstResult) {
// we need some users from the database
var providers = Stream.concat(Stream.of(localStorage()), concatExternalWithFederated(storageProviders, 0, requiresFederatedStorage));
return queryProviders(providers, pagedQuery, firstResult, maxResults, false);
}
if (count == firstResult) {
// we have the exact users in the database
// we don't need to count anything else, and we start from the first external storage provider.
var providers = concatExternalWithFederated(storageProviders, 0, requiresFederatedStorage);
return queryProviders(providers, pagedQuery, 0, maxResults, false);
}
// we need to count the external providers
firstResult -= count;
int lastProviderToCount = requiresFederatedStorage ?
storageProviders.size():
storageProviders.size() - 1;
int startIdx = 0;
for (; startIdx < lastProviderToCount; ++startIdx) {
var provider = storageProviders.get(startIdx);
if (!(provider instanceof UserCountMethodsProvider)) {
assert firstResult > 0;
Stream<?> providers = concatExternalWithFederated(storageProviders, startIdx, requiresFederatedStorage);
return queryProviders(providers, pagedQuery, firstResult, maxResults, true);
}
count = countQueryWithGracefulDegradation(storageProviders.get(startIdx), countQuery, 0, firstResult + 1);
if (count > firstResult) {
// we start on this provider as we need some users from it.
break;
}
if (count == firstResult) {
// exact users required to offset, start querying the next provider.
startIdx++;
firstResult = 0;
break;
}
firstResult -= count;
}
var providers = concatExternalWithFederated(storageProviders, startIdx, requiresFederatedStorage);
return queryProviders(providers, pagedQuery, firstResult, maxResults, false);
}
private Stream<UserModel> queryLocalAndFederatedStorage(PaginatedQuery pagedQuery, CountQuery countQuery, int firstResult, Integer maxResults) {
assert firstResult > 0;
var localStorage = localStorage();
// check how many users we can obtain from the local provider
var count = countQueryWithGracefulDegradation(localStorage, countQuery, 0, firstResult + 1);
if (count <= firstResult) {
// local provider does not have enough user to skip the first users, querying the federated provider only.
return queryProviders(Stream.of(getFederatedStorage()), pagedQuery, firstResult - count, maxResults, false);
}
return queryProviders(Stream.of(localStorage, getFederatedStorage()), pagedQuery, firstResult, maxResults, false);
}
private Stream<UserModel> queryLocalStorage(PaginatedQuery pagedQuery, int firstResult, Integer maxResults) {
assert firstResult > 0;
return queryWithGracefulDegradation(localStorage(), pagedQuery, firstResult, maxResults);
}
private static Stream<UserModel> queryProviders(Stream<?> providersStream, PaginatedQuery pagedQuery, int offset, Integer maxResults, boolean useStreamSkip) {
var firstResults = useStreamSkip ? new AtomicInteger(0) : new AtomicInteger(offset);
if (maxResults == null || maxResults < 0) {
// No maxResult set, we want all users
return providersStream
.flatMap(provider -> queryWithGracefulDegradation(provider, pagedQuery, currentFirst.getAndSet(0), null));
} else {
final AtomicInteger currentMax = new AtomicInteger(maxResults);
// Query users with currentMax variable counting how many users we return
return providersStream
.filter(provider -> currentMax.get() != 0) // If we reach currentMax == 0, we can skip querying all following providers
.flatMap(provider -> queryWithGracefulDegradation(provider, pagedQuery, currentFirst.getAndSet(0), currentMax.get()))
.peek(userModel -> {
currentMax.updateAndGet(i -> i > 0 ? i - 1 : i);
});
var users = providersStream
.flatMap(provider -> queryWithGracefulDegradation(provider, pagedQuery, firstResults.getAndSet(0), null));
return useStreamSkip ? users.skip(offset) : users;
}
var currentMax = useStreamSkip ?
new AtomicInteger(offset + maxResults) :
new AtomicInteger(maxResults);
// Query users with currentMax variable counting how many users we return
var users = providersStream
.filter(provider -> currentMax.get() != 0) // If we reach currentMax == 0, we can skip querying all following providers
.flatMap(provider -> queryWithGracefulDegradation(provider, pagedQuery, firstResults.getAndSet(0), currentMax.get()))
.peek(userModel -> currentMax.updateAndGet(i -> i > 0 ? i - 1 : i));
return useStreamSkip ? users.skip(offset).limit(maxResults) : users.limit(maxResults);
}
private Stream<?> concatExternalWithFederated(List<?> externalStorage, int startIdx, boolean requiresFederatedStorage) {
Stream<?> providers = externalStorage.subList(startIdx, externalStorage.size()).stream();
return requiresFederatedStorage ?
Stream.concat(providers, Stream.of(getFederatedStorage())) :
providers;
}
/**
@ -411,7 +430,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
* If the provider throws an exception, logs the error and returns an empty stream
* to allow other providers to continue functioning.
*/
private Stream<UserModel> queryWithGracefulDegradation(Object provider, PaginatedQuery pagedQuery,
private static Stream<UserModel> queryWithGracefulDegradation(Object provider, PaginatedQuery pagedQuery,
Integer firstResult, Integer maxResults) {
try {
return pagedQuery.query(provider, firstResult, maxResults);
@ -564,7 +583,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
}
return 0;
},
realm, firstResult, maxResults);
realm, firstResult, maxResults, true);
return importValidation(realm, results);
}
@ -693,7 +712,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
}
return 0;
}
, realm, firstResult, maxResults);
, realm, firstResult, maxResults, false);
return importValidation(realm, results);
}
@ -775,7 +794,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
@Override
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) {
if (StorageId.isLocalStorage(user)) {
if (StorageId.isLocalStorage(user.getId())) {
localStorage().addFederatedIdentity(realm, user, socialLink);
} else {
getFederatedStorage().addFederatedIdentity(realm, user.getId(), socialLink);
@ -806,7 +825,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
@Override
public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
if (StorageId.isLocalStorage(federatedUser)) {
if (StorageId.isLocalStorage(federatedUser.getId())) {
localStorage().updateFederatedIdentity(realm, federatedUser, federatedIdentityModel);
} else {
getFederatedStorage().updateFederatedIdentity(realm, federatedUser.getId(), federatedIdentityModel);
@ -816,7 +835,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
@Override
public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) {
FederatedIdentityModel federatedIdentityModel;
if (StorageId.isLocalStorage(user)) {
if (StorageId.isLocalStorage(user.getId())) {
UserProvider localStorage = localStorage();
federatedIdentityModel = localStorage.getFederatedIdentity(realm, user, socialProvider);
localStorage.removeFederatedIdentity(realm, user, socialProvider);
@ -911,7 +930,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
@Override
public void setNotBeforeForUser(RealmModel realm, UserModel user, int notBefore) {
if (StorageId.isLocalStorage(user)) {
if (StorageId.isLocalStorage(user.getId())) {
localStorage().setNotBeforeForUser(realm, user, notBefore);
} else {
getFederatedStorage().setNotBeforeForUser(realm, user.getId(), notBefore);
@ -920,7 +939,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
@Override
public int getNotBeforeOfUser(RealmModel realm, UserModel user) {
if (StorageId.isLocalStorage(user)) {
if (StorageId.isLocalStorage(user.getId())) {
return localStorage().getNotBeforeOfUser(realm, user);
} else {
return getFederatedStorage().getNotBeforeOfUser(realm, user.getId());
@ -947,7 +966,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
@Override
public Stream<FederatedIdentityModel> getFederatedIdentitiesStream(RealmModel realm, UserModel user) {
if (user == null) throw new IllegalStateException("Federated user no longer valid");
Stream<FederatedIdentityModel> stream = StorageId.isLocalStorage(user) ?
Stream<FederatedIdentityModel> stream = StorageId.isLocalStorage(user.getId()) ?
localStorage().getFederatedIdentitiesStream(realm, user) : Stream.empty();
if (getFederatedStorage() != null)
stream = Stream.concat(stream, getFederatedStorage().getFederatedIdentitiesStream(user.getId(), realm));
@ -957,7 +976,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
@Override
public FederatedIdentityModel getFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) {
if (user == null) throw new IllegalStateException("Federated user no longer valid");
if (StorageId.isLocalStorage(user)) {
if (StorageId.isLocalStorage(user.getId())) {
FederatedIdentityModel model = localStorage().getFederatedIdentity(realm, user, socialProvider);
if (model != null) return model;
}
@ -1016,7 +1035,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
@Override
public void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) {
ComponentFactory factory = ComponentUtil.getComponentFactory(session, model);
ComponentFactory<?, ?> factory = ComponentUtil.getComponentFactory(session, model);
if (!(factory instanceof UserStorageProviderFactory)) return;
// enlistAfterCompletion(..) as we need to ensure that the realm is available in the system
@ -1048,12 +1067,12 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
@Override
public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
if (StorageId.isLocalStorage(user)) {
if (StorageId.isLocalStorage(user.getId())) {
if (UserStoragePrivateUtil.userLocalStorage(session) instanceof OnUserCache) {
((OnUserCache)UserStoragePrivateUtil.userLocalStorage(session)).onCache(realm, user, delegate);
}
} else {
OnUserCache provider = getStorageProviderInstance(realm, StorageId.resolveProviderId(user), OnUserCache.class);
OnUserCache provider = getStorageProviderInstance(realm, StorageId.providerId(user.getId()), OnUserCache.class);
if (provider != null ) {
provider.onCache(realm, user, delegate);
}

View File

@ -133,9 +133,10 @@ public class UserPaginationTest extends KeycloakModelTest {
assertThat(list, hasSize(3));
// https://github.com/keycloak/keycloak/issues/44860 brings an optimization where the count method is not invoked.
expectedStorageCalls(
Collections.singletonList(new UserPropertyFileStorageCall(UserPropertyFileStorage.COUNT_SEARCH_METHOD, null, null)),
Arrays.asList(new UserPropertyFileStorageCall(UserPropertyFileStorage.COUNT_SEARCH_METHOD, null, null), new UserPropertyFileStorageCall(UserPropertyFileStorage.SEARCH_METHOD, 1, 6))
List.of(new UserPropertyFileStorageCall(UserPropertyFileStorage.COUNT_SEARCH_METHOD, null, null)),
List.of(new UserPropertyFileStorageCall(UserPropertyFileStorage.SEARCH_METHOD, 1, 6))
);
}