Credential validation shouldn't invalidate the user in the cache

Instead create a new instance of LegacyUserCredentialManager to ensure all calls are routed via the CacheAdapter and its SubjectCredentialManagerCacheAdapter.

Closes #14309
This commit is contained in:
Alexander Schwartz 2022-09-09 08:18:39 -03:00 committed by Pedro Igor
parent aa5a4e3d84
commit 621da7b803
2 changed files with 27 additions and 83 deletions

View file

@ -19,118 +19,68 @@ package org.keycloak.models.cache.infinispan;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialModel;
import org.keycloak.models.SubjectCredentialManager;
import java.util.List;
import java.util.stream.Stream;
import org.keycloak.credential.LegacyUserCredentialManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
/**
* @author Alexander Schwartz
*/
public abstract class SubjectCredentialManagerCacheAdapter implements SubjectCredentialManager {
public abstract class SubjectCredentialManagerCacheAdapter extends LegacyUserCredentialManager {
private final SubjectCredentialManager subjectCredentialManager;
protected SubjectCredentialManagerCacheAdapter(SubjectCredentialManager subjectCredentialManager) {
this.subjectCredentialManager = subjectCredentialManager;
public SubjectCredentialManagerCacheAdapter(KeycloakSession session, RealmModel realm, UserModel user) {
super(session, realm, user);
}
public abstract void invalidateCacheForEntity();
@Override
public boolean isValid(List<CredentialInput> inputs) {
// validating a password might still update its hashes, similar logic might apply to OTP logic
// instead of having each
invalidateCacheForEntity();
return subjectCredentialManager.isValid(inputs);
}
@Override
public boolean updateCredential(CredentialInput input) {
invalidateCacheForEntity();
return subjectCredentialManager.updateCredential(input);
return super.updateCredential(input);
}
@Override
public void updateStoredCredential(CredentialModel cred) {
invalidateCacheForEntity();
subjectCredentialManager.updateStoredCredential(cred);
super.updateStoredCredential(cred);
}
@Override
public CredentialModel createStoredCredential(CredentialModel cred) {
invalidateCacheForEntity();
return subjectCredentialManager.createStoredCredential(cred);
return super.createStoredCredential(cred);
}
@Override
public boolean removeStoredCredentialById(String id) {
invalidateCacheForEntity();
return subjectCredentialManager.removeStoredCredentialById(id);
}
@Override
public CredentialModel getStoredCredentialById(String id) {
return subjectCredentialManager.getStoredCredentialById(id);
}
@Override
public Stream<CredentialModel> getStoredCredentialsStream() {
return subjectCredentialManager.getStoredCredentialsStream();
}
@Override
public Stream<CredentialModel> getStoredCredentialsByTypeStream(String type) {
return subjectCredentialManager.getStoredCredentialsByTypeStream(type);
}
@Override
public CredentialModel getStoredCredentialByNameAndType(String name, String type) {
return subjectCredentialManager.getStoredCredentialByNameAndType(name, type);
return super.removeStoredCredentialById(id);
}
@Override
public boolean moveStoredCredentialTo(String id, String newPreviousCredentialId) {
invalidateCacheForEntity();
return subjectCredentialManager.moveStoredCredentialTo(id, newPreviousCredentialId);
return super.moveStoredCredentialTo(id, newPreviousCredentialId);
}
@Override
public void updateCredentialLabel(String credentialId, String userLabel) {
invalidateCacheForEntity();
subjectCredentialManager.updateCredentialLabel(credentialId, userLabel);
super.updateCredentialLabel(credentialId, userLabel);
}
@Override
public void disableCredentialType(String credentialType) {
invalidateCacheForEntity();
subjectCredentialManager.disableCredentialType(credentialType);
}
@Override
public Stream<String> getDisableableCredentialTypesStream() {
return subjectCredentialManager.getDisableableCredentialTypesStream();
}
@Override
public boolean isConfiguredFor(String type) {
return subjectCredentialManager.isConfiguredFor(type);
}
@Override
public boolean isConfiguredLocally(String type) {
return subjectCredentialManager.isConfiguredLocally(type);
}
@Override
public Stream<String> getConfiguredUserStorageCredentialTypesStream() {
return subjectCredentialManager.getConfiguredUserStorageCredentialTypesStream();
super.disableCredentialType(credentialType);
}
@Override
public CredentialModel createCredentialThroughProvider(CredentialModel model) {
invalidateCacheForEntity();
return subjectCredentialManager.createCredentialThroughProvider(model);
return super.createCredentialThroughProvider(model);
}
}

View file

@ -18,6 +18,7 @@
package org.keycloak.models.cache.infinispan;
import org.keycloak.credential.CredentialModel;
import org.keycloak.credential.LegacyUserCredentialManager;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
@ -53,7 +54,6 @@ public class UserAdapter implements CachedUserModel.Streams {
protected final KeycloakSession keycloakSession;
protected final RealmModel realm;
protected volatile UserModel updated;
private boolean userRegisteredForInvalidation;
public UserAdapter(CachedUser cached, UserCacheSession userProvider, KeycloakSession keycloakSession, RealmModel realm) {
this.cached = cached;
@ -101,12 +101,8 @@ public class UserAdapter implements CachedUserModel.Streams {
public UserModel getDelegateForUpdate() {
if (updated == null) {
userProviderCache.registerUserInvalidation(realm, cached);
userRegisteredForInvalidation = true;
updated = modelSupplier.get();
if (updated == null) throw new IllegalStateException("Not found in database");
} else if (!userRegisteredForInvalidation) {
userProviderCache.registerUserInvalidation(realm, cached);
userRegisteredForInvalidation = true;
}
return updated;
}
@ -288,14 +284,13 @@ public class UserAdapter implements CachedUserModel.Streams {
@Override
public SubjectCredentialManager credentialManager() {
if (updated == null) {
updated = modelSupplier.get();
if (updated == null) throw new IllegalStateException("Not found in database");
}
return new SubjectCredentialManagerCacheAdapter(updated.credentialManager()) {
// Instantiate a new LegacyUserCredentialManager that points to the instance that is wrapped by the cache
// this way it the cache will know if any of the credentials are modified during validation of CredentialInputs.
// This assumes that each implementation in the legacy world implements the LegacyUserCredentialManager and not something else.
return new SubjectCredentialManagerCacheAdapter(keycloakSession, realm, this) {
@Override
public CredentialModel getStoredCredentialById(String id) {
if (!userRegisteredForInvalidation) {
if (updated == null) {
return cached.getStoredCredentials(modelSupplier).stream().filter(credential ->
Objects.equals(id, credential.getId()))
.findFirst().orElse(null);
@ -305,7 +300,7 @@ public class UserAdapter implements CachedUserModel.Streams {
@Override
public Stream<CredentialModel> getStoredCredentialsStream() {
if (!userRegisteredForInvalidation) {
if (updated == null) {
return cached.getStoredCredentials(modelSupplier).stream();
}
return super.getStoredCredentialsStream();
@ -313,7 +308,7 @@ public class UserAdapter implements CachedUserModel.Streams {
@Override
public Stream<CredentialModel> getStoredCredentialsByTypeStream(String type) {
if (!userRegisteredForInvalidation) {
if (updated == null) {
return cached.getStoredCredentials(modelSupplier).stream().filter(credential -> Objects.equals(type, credential.getType()));
}
return super.getStoredCredentialsByTypeStream(type);
@ -321,7 +316,7 @@ public class UserAdapter implements CachedUserModel.Streams {
@Override
public CredentialModel getStoredCredentialByNameAndType(String name, String type) {
if (!userRegisteredForInvalidation) {
if (updated == null) {
return cached.getStoredCredentials(modelSupplier).stream().filter(credential ->
Objects.equals(type, credential.getType()) && Objects.equals(name, credential.getUserLabel()))
.findFirst().orElse(null);
@ -331,10 +326,9 @@ public class UserAdapter implements CachedUserModel.Streams {
@Override
public void invalidateCacheForEntity() {
if (!userRegisteredForInvalidation) {
userProviderCache.registerUserInvalidation(realm, cached);
userRegisteredForInvalidation = true;
}
// This implies invalidation of the cached entry,
// and all future calls in this session for the user will go to the store instead of the cache.
getDelegateForUpdate();
}
};
}