Avoid using UserCredentialManager from user storage extensions (#43695)

closes #43694

Signed-off-by: mposolda <mposolda@gmail.com>
This commit is contained in:
Marek Posolda
2025-10-29 16:26:59 +01:00
committed by GitHub
parent 322cbcdd84
commit 2fc5419676
12 changed files with 59 additions and 10 deletions

View File

@@ -56,6 +56,25 @@ This prevents problems with client IDs or passwords that contain, for example, a
To revert to the old behavior, change the client authentication to *Client secret sent as HTTP Basic authentication without URL encoding (deprecated)* (`client_secret_basic_unencoded`).
=== Not recommended to use org.keycloak.credential.UserCredentialManager directly in your extensions
If you have user storage extension and you reference the class `org.keycloak.credential.UserCredentialManager` from your providers, it is recommended to avoid using this class directly as it might be
moved to the private modules in the future releases. The class may be typically used in the method `credentialManager()` of the implementations of `UserModel` interface. In that case,
it is recommended to replace the code like this:
```
@Override
public SubjectCredentialManager credentialManager() {
return new UserCredentialManager(keycloakSession, realmModel, this);
}
```
with the code similar to this:
```
@Override
public org.keycloak.models.UserCredentialManager credentialManager() {
return keycloakSession.users().getUserCredentialManager(this);
}
```
=== Allowing realm administrators granted with the `realm-admin` role to assign admin roles
In previous versions, realm administrators granted with the `realm-admin` role were not able to grant admin roles for delegated realm administrators.

View File

@@ -26,6 +26,7 @@ import org.keycloak.credential.CredentialInput;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.UserCredentialManager;
import org.keycloak.models.cache.infinispan.events.CacheKeyInvalidatedEvent;
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
import org.keycloak.common.constants.ServiceAccountConstants;
@@ -1038,6 +1039,11 @@ public class UserCacheSession implements UserCache, OnCreateComponent, OnUpdateC
return List.of();
}
@Override
public UserCredentialManager getUserCredentialManager(UserModel user) {
return new org.keycloak.credential.UserCredentialManager(session, session.getContext().getRealm(), user);
}
public UserCacheManager getCache() {
return cache;
}

View File

@@ -36,6 +36,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserCredentialManager;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.jpa.entities.CredentialEntity;
@@ -961,6 +962,11 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
return credentialStore.moveCredentialTo(realm, user, id, newPreviousCredentialId);
}
@Override
public UserCredentialManager getUserCredentialManager(UserModel user) {
return new org.keycloak.credential.UserCredentialManager(session, session.getContext().getRealm(), user);
}
// Could override this to provide a custom behavior.
protected void ensureEmailConstraint(List<UserEntity> users, RealmModel realm) {
UserEntity user = users.get(0);

View File

@@ -22,7 +22,6 @@ import org.keycloak.common.Profile.Feature;
import org.keycloak.common.util.CollectionUtil;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.ObjectUtil;
import org.keycloak.credential.UserCredentialManager;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.GroupModel.GroupMemberJoinEvent;
@@ -577,7 +576,7 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
@Override
public SubjectCredentialManager credentialManager() {
return new UserCredentialManager(session, realm, this);
return session.users().getUserCredentialManager(this);
}
@Override

View File

@@ -59,6 +59,7 @@ import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserCredentialManager;
import org.keycloak.models.UserManager;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
@@ -1078,6 +1079,11 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
return Collections.emptyList();
}
@Override
public UserCredentialManager getUserCredentialManager(UserModel user) {
return new org.keycloak.credential.UserCredentialManager(session, session.getContext().getRealm(), user);
}
private boolean isReadOnlyOrganizationMember(UserModel delegate) {
if (delegate == null) {
return false;

View File

@@ -16,7 +16,6 @@
*/
package org.keycloak.storage.adapter;
import org.keycloak.credential.UserCredentialManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.SubjectCredentialManager;
@@ -33,6 +32,6 @@ public class InMemoryUserAdapter extends AbstractInMemoryUserAdapter {
@Override
public SubjectCredentialManager credentialManager() {
return new UserCredentialManager(session, realm, this);
return session.users().getUserCredentialManager(this);
}
}

View File

@@ -21,7 +21,6 @@ import io.opentelemetry.api.trace.StatusCode;
import org.keycloak.common.util.reflections.Types;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.SubjectCredentialManager;
import org.keycloak.models.UserModel;
import org.keycloak.storage.AbstractStorageManager;
import org.keycloak.storage.DatastoreProvider;
@@ -42,12 +41,15 @@ import java.util.stream.Stream;
*
* @author Alexander Schwartz
*/
public class UserCredentialManager extends AbstractStorageManager<UserStorageProvider, UserStorageProviderModel> implements SubjectCredentialManager {
public class UserCredentialManager extends AbstractStorageManager<UserStorageProvider, UserStorageProviderModel> implements org.keycloak.models.UserCredentialManager {
private final UserModel user;
private final KeycloakSession session;
private final RealmModel realm;
/**
* It is not recommended to use this method directly from your user-storage providers! Please use {@link org.keycloak.models.UserProvider#getUserCredentialManager(UserModel) session.users().getUserCredentialManager(user)} instead.
*/
public UserCredentialManager(KeycloakSession session, RealmModel realm, UserModel user) {
super(session, UserStorageProviderFactory.class, UserStorageProvider.class, UserStorageProviderModel::new, "user");
this.user = user;

View File

@@ -18,7 +18,6 @@ package org.keycloak.storage.adapter;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.UserCredentialManager;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
@@ -401,7 +400,7 @@ public abstract class AbstractUserAdapterFederatedStorage extends UserModelDefau
@Override
public SubjectCredentialManager credentialManager() {
return new UserCredentialManager(session, realm, this);
return session.users().getUserCredentialManager(this);
}
@Override

View File

@@ -26,6 +26,9 @@ import java.util.stream.Stream;
/**
* Validates and manages the credentials of a known entity (for example, a user).
*
* NOTE: This class might be renamed to {@link org.keycloak.models.UserCredentialManager} in Keycloak 27. Please use the {@link org.keycloak.models.UserCredentialManager}
* already if you can
*/
public interface SubjectCredentialManager {

View File

@@ -0,0 +1,4 @@
package org.keycloak.models;
public interface UserCredentialManager extends SubjectCredentialManager {
}

View File

@@ -297,4 +297,11 @@ public interface UserProvider extends Provider,
*/
void preRemove(RealmModel realm, ComponentModel component);
/**
* Default implementation of {@link SubjectCredentialManager} suitable for most of user providers
*
* @return user credential manager
*/
UserCredentialManager getUserCredentialManager(UserModel user);
}

View File

@@ -19,7 +19,6 @@ package org.keycloak.testsuite.federation;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialInputValidator;
import org.keycloak.credential.UserCredentialManager;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@@ -155,7 +154,7 @@ public class UserPropertyFileStorage implements UserLookupProvider, UserStorageP
@Override
public SubjectCredentialManager credentialManager() {
return new UserCredentialManager(session, realm, this);
return session.users().getUserCredentialManager(this);
}
};
}