SingleUserCredentialManager moving in
- UserStorageManager now handles authentication for old Kerberos+LDAP style - new getUserByCredential method in MapUserProvider would eventually do the same.
This commit is contained in:
parent
82094d113e
commit
bc8fd21dc6
42 changed files with 1500 additions and 638 deletions
|
@ -24,6 +24,7 @@ import org.keycloak.credential.CredentialAuthentication;
|
||||||
import org.keycloak.credential.CredentialInput;
|
import org.keycloak.credential.CredentialInput;
|
||||||
import org.keycloak.credential.CredentialInputUpdater;
|
import org.keycloak.credential.CredentialInputUpdater;
|
||||||
import org.keycloak.credential.CredentialInputValidator;
|
import org.keycloak.credential.CredentialInputValidator;
|
||||||
|
import org.keycloak.credential.LegacySingleUserCredentialManager;
|
||||||
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
|
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
|
||||||
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
||||||
import org.keycloak.models.CredentialValidationOutput;
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
|
@ -167,7 +168,7 @@ public class KerberosFederationProvider implements UserStorageProvider,
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
|
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
|
||||||
if (!(input instanceof UserCredentialModel)) return false;
|
if (!(input instanceof UserCredentialModel)) return false;
|
||||||
if (input.getType().equals(PasswordCredentialModel.TYPE) && !session.userCredentialManager().isConfiguredLocally(realm, user, PasswordCredentialModel.TYPE)) {
|
if (input.getType().equals(PasswordCredentialModel.TYPE) && !((LegacySingleUserCredentialManager) user.getUserCredentialManager()).isConfiguredLocally(PasswordCredentialModel.TYPE)) {
|
||||||
return validPassword(user.getUsername(), input.getChallengeResponse());
|
return validPassword(user.getUsername(), input.getChallengeResponse());
|
||||||
} else {
|
} else {
|
||||||
return false; // invalid cred type
|
return false; // invalid cred type
|
||||||
|
|
|
@ -75,6 +75,11 @@
|
||||||
<artifactId>jboss-transaction-api_1.3_spec</artifactId>
|
<artifactId>jboss-transaction-api_1.3_spec</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<!-- needed for InMemoryUserAdapter -->
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-model-legacy-private</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -36,6 +36,7 @@ import org.keycloak.credential.CredentialAuthentication;
|
||||||
import org.keycloak.credential.CredentialInput;
|
import org.keycloak.credential.CredentialInput;
|
||||||
import org.keycloak.credential.CredentialInputUpdater;
|
import org.keycloak.credential.CredentialInputUpdater;
|
||||||
import org.keycloak.credential.CredentialInputValidator;
|
import org.keycloak.credential.CredentialInputValidator;
|
||||||
|
import org.keycloak.credential.LegacySingleUserCredentialManager;
|
||||||
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
|
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
|
||||||
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
||||||
import org.keycloak.models.CredentialValidationOutput;
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
|
@ -712,7 +713,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
|
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
|
||||||
if (!(input instanceof UserCredentialModel)) return false;
|
if (!(input instanceof UserCredentialModel)) return false;
|
||||||
if (input.getType().equals(PasswordCredentialModel.TYPE) && !session.userCredentialManager().isConfiguredLocally(realm, user, PasswordCredentialModel.TYPE)) {
|
if (input.getType().equals(PasswordCredentialModel.TYPE) && !((LegacySingleUserCredentialManager) user.getUserCredentialManager()).isConfiguredLocally(PasswordCredentialModel.TYPE)) {
|
||||||
return validPassword(realm, user, input.getChallengeResponse());
|
return validPassword(realm, user, input.getChallengeResponse());
|
||||||
} else {
|
} else {
|
||||||
return false; // invalid cred type
|
return false; // invalid cred type
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
package org.keycloak.models.cache.infinispan;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
|
import org.keycloak.credential.CredentialModel;
|
||||||
|
import org.keycloak.models.SingleUserCredentialManager;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Alexander Schwartz
|
||||||
|
*/
|
||||||
|
public abstract class SingleUserCredentialManagerCacheAdapter implements SingleUserCredentialManager {
|
||||||
|
|
||||||
|
private final SingleUserCredentialManager singleUserCredentialManager;
|
||||||
|
|
||||||
|
protected SingleUserCredentialManagerCacheAdapter(SingleUserCredentialManager singleUserCredentialManager) {
|
||||||
|
this.singleUserCredentialManager = singleUserCredentialManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void invalidateCacheForUser();
|
||||||
|
|
||||||
|
@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
|
||||||
|
invalidateCacheForUser();
|
||||||
|
return singleUserCredentialManager.isValid(inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateCredential(CredentialInput input) {
|
||||||
|
invalidateCacheForUser();
|
||||||
|
return singleUserCredentialManager.updateCredential(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateStoredCredential(CredentialModel cred) {
|
||||||
|
invalidateCacheForUser();
|
||||||
|
singleUserCredentialManager.updateStoredCredential(cred);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel createStoredCredential(CredentialModel cred) {
|
||||||
|
invalidateCacheForUser();
|
||||||
|
return singleUserCredentialManager.createStoredCredential(cred);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeStoredCredentialById(String id) {
|
||||||
|
invalidateCacheForUser();
|
||||||
|
return singleUserCredentialManager.removeStoredCredentialById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel getStoredCredentialById(String id) {
|
||||||
|
return singleUserCredentialManager.getStoredCredentialById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<CredentialModel> getStoredCredentialsStream() {
|
||||||
|
return singleUserCredentialManager.getStoredCredentialsStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<CredentialModel> getStoredCredentialsByTypeStream(String type) {
|
||||||
|
return singleUserCredentialManager.getStoredCredentialsByTypeStream(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel getStoredCredentialByNameAndType(String name, String type) {
|
||||||
|
return singleUserCredentialManager.getStoredCredentialByNameAndType(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean moveStoredCredentialTo(String id, String newPreviousCredentialId) {
|
||||||
|
invalidateCacheForUser();
|
||||||
|
return singleUserCredentialManager.moveStoredCredentialTo(id, newPreviousCredentialId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateCredentialLabel(String credentialId, String userLabel) {
|
||||||
|
invalidateCacheForUser();
|
||||||
|
singleUserCredentialManager.updateCredentialLabel(credentialId, userLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disableCredentialType(String credentialType) {
|
||||||
|
invalidateCacheForUser();
|
||||||
|
singleUserCredentialManager.disableCredentialType(credentialType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<String> getDisableableCredentialTypesStream() {
|
||||||
|
return singleUserCredentialManager.getDisableableCredentialTypesStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConfiguredFor(String type) {
|
||||||
|
return singleUserCredentialManager.isConfiguredFor(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConfiguredLocally(String type) {
|
||||||
|
return singleUserCredentialManager.isConfiguredLocally(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<String> getConfiguredUserStorageCredentialTypesStream(UserModel user) {
|
||||||
|
return singleUserCredentialManager.getConfiguredUserStorageCredentialTypesStream(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel createCredentialThroughProvider(CredentialModel model) {
|
||||||
|
invalidateCacheForUser();
|
||||||
|
return singleUserCredentialManager.createCredentialThroughProvider(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,11 +17,13 @@
|
||||||
|
|
||||||
package org.keycloak.models.cache.infinispan;
|
package org.keycloak.models.cache.infinispan;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.SingleUserCredentialManager;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.cache.CachedUserModel;
|
import org.keycloak.models.cache.CachedUserModel;
|
||||||
import org.keycloak.models.cache.infinispan.entities.CachedUser;
|
import org.keycloak.models.cache.infinispan.entities.CachedUser;
|
||||||
|
@ -33,6 +35,7 @@ import java.util.HashSet;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
@ -50,6 +53,7 @@ public class UserAdapter implements CachedUserModel.Streams {
|
||||||
protected final KeycloakSession keycloakSession;
|
protected final KeycloakSession keycloakSession;
|
||||||
protected final RealmModel realm;
|
protected final RealmModel realm;
|
||||||
protected volatile UserModel updated;
|
protected volatile UserModel updated;
|
||||||
|
private boolean userRegisteredForInvalidation;
|
||||||
|
|
||||||
public UserAdapter(CachedUser cached, UserCacheSession userProvider, KeycloakSession keycloakSession, RealmModel realm) {
|
public UserAdapter(CachedUser cached, UserCacheSession userProvider, KeycloakSession keycloakSession, RealmModel realm) {
|
||||||
this.cached = cached;
|
this.cached = cached;
|
||||||
|
@ -97,8 +101,12 @@ public class UserAdapter implements CachedUserModel.Streams {
|
||||||
public UserModel getDelegateForUpdate() {
|
public UserModel getDelegateForUpdate() {
|
||||||
if (updated == null) {
|
if (updated == null) {
|
||||||
userProviderCache.registerUserInvalidation(realm, cached);
|
userProviderCache.registerUserInvalidation(realm, cached);
|
||||||
|
userRegisteredForInvalidation = true;
|
||||||
updated = modelSupplier.get();
|
updated = modelSupplier.get();
|
||||||
if (updated == null) throw new IllegalStateException("Not found in database");
|
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||||
|
} else if (!userRegisteredForInvalidation) {
|
||||||
|
userProviderCache.registerUserInvalidation(realm, cached);
|
||||||
|
userRegisteredForInvalidation = true;
|
||||||
}
|
}
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
@ -278,6 +286,59 @@ public class UserAdapter implements CachedUserModel.Streams {
|
||||||
updated.setServiceAccountClientLink(clientInternalId);
|
updated.setServiceAccountClientLink(clientInternalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SingleUserCredentialManager getUserCredentialManager() {
|
||||||
|
if (updated == null) {
|
||||||
|
updated = modelSupplier.get();
|
||||||
|
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||||
|
}
|
||||||
|
return new SingleUserCredentialManagerCacheAdapter(updated.getUserCredentialManager()) {
|
||||||
|
@Override
|
||||||
|
public CredentialModel getStoredCredentialById(String id) {
|
||||||
|
if (!userRegisteredForInvalidation) {
|
||||||
|
return cached.getStoredCredentials(modelSupplier).stream().filter(credential ->
|
||||||
|
Objects.equals(id, credential.getId()))
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
return super.getStoredCredentialById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<CredentialModel> getStoredCredentialsStream() {
|
||||||
|
if (!userRegisteredForInvalidation) {
|
||||||
|
return cached.getStoredCredentials(modelSupplier).stream();
|
||||||
|
}
|
||||||
|
return super.getStoredCredentialsStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<CredentialModel> getStoredCredentialsByTypeStream(String type) {
|
||||||
|
if (!userRegisteredForInvalidation) {
|
||||||
|
return cached.getStoredCredentials(modelSupplier).stream().filter(credential -> Objects.equals(type, credential.getType()));
|
||||||
|
}
|
||||||
|
return super.getStoredCredentialsByTypeStream(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel getStoredCredentialByNameAndType(String name, String type) {
|
||||||
|
if (!userRegisteredForInvalidation) {
|
||||||
|
return cached.getStoredCredentials(modelSupplier).stream().filter(credential ->
|
||||||
|
Objects.equals(type, credential.getType()) && Objects.equals(name, credential.getUserLabel()))
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
return super.getStoredCredentialByNameAndType(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateCacheForUser() {
|
||||||
|
if (!userRegisteredForInvalidation) {
|
||||||
|
userProviderCache.registerUserInvalidation(realm, cached);
|
||||||
|
userRegisteredForInvalidation = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RoleModel> getRealmRoleMappingsStream() {
|
public Stream<RoleModel> getRealmRoleMappingsStream() {
|
||||||
if (updated != null) return updated.getRealmRoleMappingsStream();
|
if (updated != null) return updated.getRealmRoleMappingsStream();
|
||||||
|
@ -392,4 +453,5 @@ public class UserAdapter implements CachedUserModel.Streams {
|
||||||
private UserModel getUserModel() {
|
private UserModel getUserModel() {
|
||||||
return userProviderCache.getDelegate().getUserById(realm, cached.getId());
|
return userProviderCache.getDelegate().getUserById(realm, cached.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,9 @@ package org.keycloak.models.cache.infinispan;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.cluster.ClusterProvider;
|
import org.keycloak.cluster.ClusterProvider;
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
import org.keycloak.models.ClientScopeModel;
|
import org.keycloak.models.ClientScopeModel;
|
||||||
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
||||||
import org.keycloak.common.constants.ServiceAccountConstants;
|
import org.keycloak.common.constants.ServiceAccountConstants;
|
||||||
|
@ -546,6 +548,10 @@ public class UserCacheSession implements UserCache.Streams, OnCreateComponent, O
|
||||||
return getDelegate().getUsersStream(realm, includeServiceAccounts);
|
return getDelegate().getUsersStream(realm, includeServiceAccounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialValidationOutput getUserByCredential(RealmModel realm, CredentialInput input) {
|
||||||
|
return getDelegate().getUserByCredential(realm, input);
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public int getUsersCount(RealmModel realm, boolean includeServiceAccount) {
|
public int getUsersCount(RealmModel realm, boolean includeServiceAccount) {
|
||||||
return getDelegate().getUsersCount(realm, includeServiceAccount);
|
return getDelegate().getUsersCount(realm, includeServiceAccount);
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.models.cache.infinispan.entities;
|
package org.keycloak.models.cache.infinispan.entities;
|
||||||
|
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
|
import org.keycloak.credential.CredentialModel;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
@ -27,6 +28,8 @@ import org.keycloak.models.cache.infinispan.LazyLoader;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -50,6 +53,7 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
|
||||||
private final LazyLoader<UserModel, MultivaluedHashMap<String, String>> attributes;
|
private final LazyLoader<UserModel, MultivaluedHashMap<String, String>> attributes;
|
||||||
private final LazyLoader<UserModel, Set<String>> roleMappings;
|
private final LazyLoader<UserModel, Set<String>> roleMappings;
|
||||||
private final LazyLoader<UserModel, Set<String>> groups;
|
private final LazyLoader<UserModel, Set<String>> groups;
|
||||||
|
private final LazyLoader<UserModel, List<CredentialModel>> storedCredentials;
|
||||||
|
|
||||||
public CachedUser(Long revision, RealmModel realm, UserModel user, int notBefore) {
|
public CachedUser(Long revision, RealmModel realm, UserModel user, int notBefore) {
|
||||||
super(revision, user.getId());
|
super(revision, user.getId());
|
||||||
|
@ -66,6 +70,7 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
|
||||||
this.attributes = new DefaultLazyLoader<>(userModel -> new MultivaluedHashMap<>(userModel.getAttributes()), MultivaluedHashMap::new);
|
this.attributes = new DefaultLazyLoader<>(userModel -> new MultivaluedHashMap<>(userModel.getAttributes()), MultivaluedHashMap::new);
|
||||||
this.roleMappings = new DefaultLazyLoader<>(userModel -> userModel.getRoleMappingsStream().map(RoleModel::getId).collect(Collectors.toSet()), Collections::emptySet);
|
this.roleMappings = new DefaultLazyLoader<>(userModel -> userModel.getRoleMappingsStream().map(RoleModel::getId).collect(Collectors.toSet()), Collections::emptySet);
|
||||||
this.groups = new DefaultLazyLoader<>(userModel -> userModel.getGroupsStream().map(GroupModel::getId).collect(Collectors.toCollection(LinkedHashSet::new)), LinkedHashSet::new);
|
this.groups = new DefaultLazyLoader<>(userModel -> userModel.getGroupsStream().map(GroupModel::getId).collect(Collectors.toCollection(LinkedHashSet::new)), LinkedHashSet::new);
|
||||||
|
this.storedCredentials = new DefaultLazyLoader<>(userModel -> userModel.getUserCredentialManager().getStoredCredentialsStream().collect(Collectors.toCollection(LinkedList::new)), LinkedList::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRealm() {
|
public String getRealm() {
|
||||||
|
@ -119,4 +124,10 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
|
||||||
public int getNotBefore() {
|
public int getNotBefore() {
|
||||||
return notBefore;
|
return notBefore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<CredentialModel> getStoredCredentials(Supplier<UserModel> userModel) {
|
||||||
|
// clone the credential model before returning it, so that modifications don't pollute the cache
|
||||||
|
return storedCredentials.get(userModel).stream().map(CredentialModel::shallowClone).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,13 @@ package org.keycloak.models.jpa;
|
||||||
|
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
import org.keycloak.common.util.ObjectUtil;
|
import org.keycloak.common.util.ObjectUtil;
|
||||||
|
import org.keycloak.credential.LegacySingleUserCredentialManager;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.SingleUserCredentialManager;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.jpa.entities.UserAttributeEntity;
|
import org.keycloak.models.jpa.entities.UserAttributeEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserEntity;
|
import org.keycloak.models.jpa.entities.UserEntity;
|
||||||
|
@ -34,6 +36,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.RoleUtils;
|
import org.keycloak.models.utils.RoleUtils;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.LockModeType;
|
||||||
import javax.persistence.Query;
|
import javax.persistence.Query;
|
||||||
import javax.persistence.TypedQuery;
|
import javax.persistence.TypedQuery;
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
import javax.persistence.criteria.CriteriaBuilder;
|
||||||
|
@ -46,7 +49,6 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import javax.persistence.LockModeType;
|
|
||||||
|
|
||||||
import static org.keycloak.utils.StreamsUtil.closing;
|
import static org.keycloak.utils.StreamsUtil.closing;
|
||||||
|
|
||||||
|
@ -515,6 +517,11 @@ public class UserAdapter implements UserModel.Streams, JpaModel<UserEntity> {
|
||||||
user.setServiceAccountClientLink(clientInternalId);
|
user.setServiceAccountClientLink(clientInternalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SingleUserCredentialManager getUserCredentialManager() {
|
||||||
|
return new LegacySingleUserCredentialManager(session, realm, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
|
|
|
@ -1,247 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020 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.jboss.logging.Logger;
|
|
||||||
import org.keycloak.Config;
|
|
||||||
import org.keycloak.common.util.reflections.Types;
|
|
||||||
import org.keycloak.component.ComponentFactory;
|
|
||||||
import org.keycloak.component.ComponentModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.provider.Provider;
|
|
||||||
import org.keycloak.provider.ProviderFactory;
|
|
||||||
import org.keycloak.utils.ServicesUtils;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param <ProviderType> This type will be used for looking for factories that produce instances of desired providers
|
|
||||||
* @param <StorageProviderModelType> Type of model used for creating provider, it needs to extend
|
|
||||||
* CacheableStorageProviderModel as it has {@code isEnabled()} method and also extend
|
|
||||||
* PrioritizedComponentModel which is required for sorting providers based on its
|
|
||||||
* priorities
|
|
||||||
*/
|
|
||||||
public abstract class AbstractStorageManager<ProviderType extends Provider,
|
|
||||||
StorageProviderModelType extends CacheableStorageProviderModel> {
|
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(AbstractStorageManager.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Timeouts are used as time boundary for obtaining models from an external storage. Default value is set
|
|
||||||
* to 3000 milliseconds and it's configurable.
|
|
||||||
*/
|
|
||||||
private static final Long STORAGE_PROVIDER_DEFAULT_TIMEOUT = 3000L;
|
|
||||||
protected final KeycloakSession session;
|
|
||||||
private final Class<ProviderType> providerTypeClass;
|
|
||||||
private final Class<? extends ProviderFactory> factoryTypeClass;
|
|
||||||
private final Function<ComponentModel, StorageProviderModelType> toStorageProviderModelTypeFunction;
|
|
||||||
private final String configScope;
|
|
||||||
private Long storageProviderTimeout;
|
|
||||||
|
|
||||||
public AbstractStorageManager(KeycloakSession session, Class<? extends ProviderFactory> factoryTypeClass, Class<ProviderType> providerTypeClass, Function<ComponentModel, StorageProviderModelType> toStorageProviderModelTypeFunction, String configScope) {
|
|
||||||
this.session = session;
|
|
||||||
this.providerTypeClass = providerTypeClass;
|
|
||||||
this.factoryTypeClass = factoryTypeClass;
|
|
||||||
this.toStorageProviderModelTypeFunction = toStorageProviderModelTypeFunction;
|
|
||||||
this.configScope = configScope;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Long getStorageProviderTimeout() {
|
|
||||||
if (storageProviderTimeout == null) {
|
|
||||||
storageProviderTimeout = Config.scope(configScope).getLong("storageProviderTimeout", STORAGE_PROVIDER_DEFAULT_TIMEOUT);
|
|
||||||
}
|
|
||||||
return storageProviderTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a factory with the providerId, which produce instances of type CreatedProviderType
|
|
||||||
* @param providerId id of factory that produce desired instances
|
|
||||||
* @return A factory that implements {@code ComponentFactory<CreatedProviderType, ProviderType>}
|
|
||||||
*/
|
|
||||||
protected <T extends ProviderType> ComponentFactory<T, ProviderType> getStorageProviderFactory(String providerId) {
|
|
||||||
return (ComponentFactory<T, ProviderType>) session.getKeycloakSessionFactory()
|
|
||||||
.getProviderFactory(providerTypeClass, providerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns stream of all storageProviders within the realm that implements the capabilityInterface.
|
|
||||||
*
|
|
||||||
* @param realm realm
|
|
||||||
* @param capabilityInterface class of desired capabilityInterface.
|
|
||||||
* For example, {@code GroupLookupProvider} or {@code UserQueryProvider}
|
|
||||||
* @return enabled storage providers for realm and @{code getProviderTypeClass()}
|
|
||||||
*/
|
|
||||||
protected <T> Stream<T> getEnabledStorageProviders(RealmModel realm, Class<T> capabilityInterface) {
|
|
||||||
return getStorageProviderModels(realm, providerTypeClass)
|
|
||||||
.map(toStorageProviderModelTypeFunction)
|
|
||||||
.filter(StorageProviderModelType::isEnabled)
|
|
||||||
.sorted(StorageProviderModelType.comparator)
|
|
||||||
.map(storageProviderModelType -> getStorageProviderInstance(storageProviderModelType, capabilityInterface, false))
|
|
||||||
.filter(Objects::nonNull);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets all enabled StorageProviders that implements the capabilityInterface, applies applyFunction on each of
|
|
||||||
* them and then join the results together.
|
|
||||||
*
|
|
||||||
* !! Each StorageProvider has a limited time to respond, if it fails to do it, empty stream is returned !!
|
|
||||||
*
|
|
||||||
* @param realm realm
|
|
||||||
* @param capabilityInterface class of desired capabilityInterface.
|
|
||||||
* For example, {@code GroupLookupProvider} or {@code UserQueryProvider}
|
|
||||||
* @param applyFunction function that is applied on StorageProviders
|
|
||||||
* @param <R> result of applyFunction
|
|
||||||
* @return a stream with all results from all StorageProviders
|
|
||||||
*/
|
|
||||||
protected <R, T> Stream<R> flatMapEnabledStorageProvidersWithTimeout(RealmModel realm, Class<T> capabilityInterface, Function<T, ? extends Stream<R>> applyFunction) {
|
|
||||||
return getEnabledStorageProviders(realm, capabilityInterface)
|
|
||||||
.flatMap(ServicesUtils.timeBound(session, getStorageProviderTimeout(), applyFunction));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets all enabled StorageProviders that implements the capabilityInterface, applies applyFunction on each of
|
|
||||||
* them and returns the stream.
|
|
||||||
*
|
|
||||||
* !! Each StorageProvider has a limited time to respond, if it fails to do it, null is returned !!
|
|
||||||
*
|
|
||||||
* @param realm realm
|
|
||||||
* @param capabilityInterface class of desired capabilityInterface.
|
|
||||||
* For example, {@code GroupLookupProvider} or {@code UserQueryProvider}
|
|
||||||
* @param applyFunction function that is applied on StorageProviders
|
|
||||||
* @param <R> Result of applyFunction
|
|
||||||
* @return First result from StorageProviders
|
|
||||||
*/
|
|
||||||
protected <R, T> Stream<R> mapEnabledStorageProvidersWithTimeout(RealmModel realm, Class<T> capabilityInterface, Function<T, R> applyFunction) {
|
|
||||||
return getEnabledStorageProviders(realm, capabilityInterface)
|
|
||||||
.map(ServicesUtils.timeBoundOne(session, getStorageProviderTimeout(), applyFunction))
|
|
||||||
.filter(Objects::nonNull);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets all enabled StorageProviders that implements the capabilityInterface and call applyFunction on each
|
|
||||||
*
|
|
||||||
* !! Each StorageProvider has a limited time for consuming !!
|
|
||||||
*
|
|
||||||
* @param realm realm
|
|
||||||
* @param capabilityInterface class of desired capabilityInterface.
|
|
||||||
* For example, {@code GroupLookupProvider} or {@code UserQueryProvider}
|
|
||||||
* @param consumer function that is applied on StorageProviders
|
|
||||||
*/
|
|
||||||
protected <T> void consumeEnabledStorageProvidersWithTimeout(RealmModel realm, Class<T> capabilityInterface, Consumer<T> consumer) {
|
|
||||||
getEnabledStorageProviders(realm, capabilityInterface)
|
|
||||||
.forEachOrdered(ServicesUtils.consumeWithTimeBound(session, getStorageProviderTimeout(), consumer));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected <T> T getStorageProviderInstance(RealmModel realm, String providerId, Class<T> capabilityInterface) {
|
|
||||||
return getStorageProviderInstance(realm, providerId, capabilityInterface, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an instance of provider with the providerId within the realm or null if storage provider with providerId
|
|
||||||
* doesn't implement capabilityInterface.
|
|
||||||
*
|
|
||||||
* @param realm realm
|
|
||||||
* @param providerId id of ComponentModel within database/storage
|
|
||||||
* @param capabilityInterface class of desired capabilityInterface.
|
|
||||||
* For example, {@code GroupLookupProvider} or {@code UserQueryProvider}
|
|
||||||
* @return an instance of type CreatedProviderType or null if storage provider with providerId doesn't implement capabilityInterface
|
|
||||||
*/
|
|
||||||
protected <T> T getStorageProviderInstance(RealmModel realm, String providerId, Class<T> capabilityInterface, boolean includeDisabled) {
|
|
||||||
if (providerId == null || capabilityInterface == null) return null;
|
|
||||||
return getStorageProviderInstance(getStorageProviderModel(realm, providerId), capabilityInterface, includeDisabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an instance of StorageProvider model corresponding realm and providerId
|
|
||||||
* @param realm Realm.
|
|
||||||
* @param providerId Id of desired provider.
|
|
||||||
* @return An instance of type StorageProviderModelType
|
|
||||||
*/
|
|
||||||
protected StorageProviderModelType getStorageProviderModel(RealmModel realm, String providerId) {
|
|
||||||
ComponentModel componentModel = realm.getComponent(providerId);
|
|
||||||
if (componentModel == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return toStorageProviderModelTypeFunction.apply(componentModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an instance of provider for the model or null if storage provider based on the model doesn't implement capabilityInterface.
|
|
||||||
*
|
|
||||||
* @param model StorageProviderModel obtained from database/storage
|
|
||||||
* @param capabilityInterface class of desired capabilityInterface.
|
|
||||||
* For example, {@code GroupLookupProvider} or {@code UserQueryProvider}
|
|
||||||
* @param <T> Required capability interface type
|
|
||||||
* @return an instance of type T or null if storage provider based on the model doesn't exist or doesn't implement the capabilityInterface.
|
|
||||||
*/
|
|
||||||
protected <T> T getStorageProviderInstance(StorageProviderModelType model, Class<T> capabilityInterface) {
|
|
||||||
return getStorageProviderInstance(model, capabilityInterface, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an instance of provider for the model or null if storage provider based on the model doesn't implement capabilityInterface.
|
|
||||||
*
|
|
||||||
* @param model StorageProviderModel obtained from database/storage
|
|
||||||
* @param capabilityInterface class of desired capabilityInterface.
|
|
||||||
* For example, {@code GroupLookupProvider} or {@code UserQueryProvider}
|
|
||||||
* @param includeDisabled If set to true, the method will return also disabled providers.
|
|
||||||
* @return an instance of type T or null if storage provider based on the model doesn't exist or doesn't implement the capabilityInterface.
|
|
||||||
*/
|
|
||||||
protected <T> T getStorageProviderInstance(StorageProviderModelType model, Class<T> capabilityInterface, boolean includeDisabled) {
|
|
||||||
if (model == null || (!model.isEnabled() && !includeDisabled) || capabilityInterface == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
ProviderType instance = (ProviderType) session.getAttribute(model.getId());
|
|
||||||
if (instance != null && capabilityInterface.isAssignableFrom(instance.getClass())) return capabilityInterface.cast(instance);
|
|
||||||
|
|
||||||
ComponentFactory<? extends ProviderType, ProviderType> factory = getStorageProviderFactory(model.getProviderId());
|
|
||||||
if (factory == null) {
|
|
||||||
LOG.warnv("Configured StorageProvider {0} of provider id {1} does not exist", model.getName(), model.getProviderId());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!Types.supports(capabilityInterface, factory, factoryTypeClass)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
instance = factory.create(session, model);
|
|
||||||
if (instance == null) {
|
|
||||||
throw new IllegalStateException("StorageProvideFactory (of type " + factory.getClass().getName() + ") produced a null instance");
|
|
||||||
}
|
|
||||||
session.enlistForClose(instance);
|
|
||||||
session.setAttribute(model.getId(), instance);
|
|
||||||
return capabilityInterface.cast(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stream of ComponentModels of storageType.
|
|
||||||
* @param realm Realm.
|
|
||||||
* @param storageType Type.
|
|
||||||
* @return Stream of ComponentModels
|
|
||||||
*/
|
|
||||||
public static Stream<ComponentModel> getStorageProviderModels(RealmModel realm, Class<? extends Provider> storageType) {
|
|
||||||
return realm.getStorageProviders(storageType);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -30,10 +30,16 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.common.util.reflections.Types;
|
||||||
import org.keycloak.component.ComponentFactory;
|
import org.keycloak.component.ComponentFactory;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.credential.CredentialAuthentication;
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
|
import org.keycloak.credential.CredentialProvider;
|
||||||
|
import org.keycloak.credential.CredentialProviderFactory;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientScopeModel;
|
import org.keycloak.models.ClientScopeModel;
|
||||||
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
@ -52,6 +58,7 @@ 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.storage.client.ClientStorageProvider;
|
import org.keycloak.storage.client.ClientStorageProvider;
|
||||||
|
import org.keycloak.storage.datastore.LegacyDatastoreProvider;
|
||||||
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||||
import org.keycloak.storage.managers.UserStorageSyncManager;
|
import org.keycloak.storage.managers.UserStorageSyncManager;
|
||||||
import org.keycloak.storage.user.ImportedUserValidation;
|
import org.keycloak.storage.user.ImportedUserValidation;
|
||||||
|
@ -76,7 +83,7 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
|
||||||
}
|
}
|
||||||
|
|
||||||
protected UserProvider localStorage() {
|
protected UserProvider localStorage() {
|
||||||
return session.userLocalStorage();
|
return ((LegacyDatastoreProvider) session.getProvider(DatastoreProvider.class)).userLocalStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserFederatedStorageProvider getFederatedStorage() {
|
private UserFederatedStorageProvider getFederatedStorage() {
|
||||||
|
@ -122,6 +129,26 @@ 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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialValidationOutput getUserByCredential(RealmModel realm, CredentialInput input) {
|
||||||
|
Stream<CredentialAuthentication> credentialAuthenticationStream = getEnabledStorageProviders(realm, CredentialAuthentication.class);
|
||||||
|
|
||||||
|
credentialAuthenticationStream = Stream.concat(credentialAuthenticationStream,
|
||||||
|
getCredentialProviders(session, CredentialAuthentication.class));
|
||||||
|
|
||||||
|
return credentialAuthenticationStream
|
||||||
|
.filter(credentialAuthentication -> credentialAuthentication.supportsCredentialAuthenticationFor(input.getType()))
|
||||||
|
.map(credentialAuthentication -> credentialAuthentication.authenticate(realm, input))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
protected void deleteInvalidUser(final RealmModel realm, final UserModel user) {
|
protected void deleteInvalidUser(final RealmModel realm, final UserModel user) {
|
||||||
String userId = user.getId();
|
String userId = user.getId();
|
||||||
String userName = user.getUsername();
|
String userName = user.getUsername();
|
||||||
|
|
|
@ -18,11 +18,13 @@ package org.keycloak.storage.adapter;
|
||||||
|
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.credential.LegacySingleUserCredentialManager;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.SingleUserCredentialManager;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserModelDefaultMethods;
|
import org.keycloak.models.UserModelDefaultMethods;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
@ -254,6 +256,11 @@ public class InMemoryUserAdapter extends UserModelDefaultMethods.Streams {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SingleUserCredentialManager getUserCredentialManager() {
|
||||||
|
return new LegacySingleUserCredentialManager(session, realm, this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RoleModel> getRealmRoleMappingsStream() {
|
public Stream<RoleModel> getRealmRoleMappingsStream() {
|
||||||
return getRoleMappingsStream().filter(RoleUtils::isRealmRole);
|
return getRoleMappingsStream().filter(RoleUtils::isRealmRole);
|
|
@ -18,6 +18,7 @@ import org.keycloak.storage.LegacyStoreManagers;
|
||||||
import org.keycloak.storage.MigrationManager;
|
import org.keycloak.storage.MigrationManager;
|
||||||
import org.keycloak.storage.RoleStorageManager;
|
import org.keycloak.storage.RoleStorageManager;
|
||||||
import org.keycloak.storage.UserStorageManager;
|
import org.keycloak.storage.UserStorageManager;
|
||||||
|
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||||
|
|
||||||
public class LegacyDatastoreProvider implements DatastoreProvider, LegacyStoreManagers {
|
public class LegacyDatastoreProvider implements DatastoreProvider, LegacyStoreManagers {
|
||||||
private final LegacyDatastoreProviderFactory factory;
|
private final LegacyDatastoreProviderFactory factory;
|
||||||
|
@ -35,6 +36,7 @@ public class LegacyDatastoreProvider implements DatastoreProvider, LegacyStoreMa
|
||||||
private GroupStorageManager groupStorageManager;
|
private GroupStorageManager groupStorageManager;
|
||||||
private ClientStorageManager clientStorageManager;
|
private ClientStorageManager clientStorageManager;
|
||||||
private UserProvider userStorageManager;
|
private UserProvider userStorageManager;
|
||||||
|
private UserFederatedStorageProvider userFederatedStorageProvider;
|
||||||
|
|
||||||
public LegacyDatastoreProvider(LegacyDatastoreProviderFactory factory, KeycloakSession session) {
|
public LegacyDatastoreProvider(LegacyDatastoreProviderFactory factory, KeycloakSession session) {
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
|
@ -80,6 +82,19 @@ public class LegacyDatastoreProvider implements DatastoreProvider, LegacyStoreMa
|
||||||
return userStorageManager;
|
return userStorageManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserProvider userLocalStorage() {
|
||||||
|
return session.getProvider(UserProvider.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserFederatedStorageProvider userFederatedStorage() {
|
||||||
|
if (userFederatedStorageProvider == null) {
|
||||||
|
userFederatedStorageProvider = session.getProvider(UserFederatedStorageProvider.class);
|
||||||
|
}
|
||||||
|
return userFederatedStorageProvider;
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
@ -192,6 +207,7 @@ public class LegacyDatastoreProvider implements DatastoreProvider, LegacyStoreMa
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public MigrationManager getMigrationManager() {
|
public MigrationManager getMigrationManager() {
|
||||||
return new LegacyMigrationManager(session);
|
return new LegacyMigrationManager(session);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,283 @@
|
||||||
|
/*
|
||||||
|
* 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.credential;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.reflections.Types;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.SingleUserCredentialManager;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.storage.AbstractStorageManager;
|
||||||
|
import org.keycloak.storage.StorageId;
|
||||||
|
import org.keycloak.storage.UserStorageProvider;
|
||||||
|
import org.keycloak.storage.UserStorageProviderFactory;
|
||||||
|
import org.keycloak.storage.UserStorageProviderModel;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handling credentials for a given user.
|
||||||
|
*
|
||||||
|
* This serves as a wrapper to specific strategies. The wrapping code implements the logic for {@link CredentialInputUpdater}s
|
||||||
|
* and {@link CredentialInputValidator}s. Storage specific strategies can be added like for example in
|
||||||
|
* org.keycloak.models.map.credential.MapSingleUserCredentialManagerStrategy.
|
||||||
|
*
|
||||||
|
* I tried to extract the federation specific parts to the {@link LegacySingleUserCredentialManagerStrategy} but the control
|
||||||
|
* flow in the existing logic: if <code>model == null || !model.isEnabled()</code>, the code will directly return, while
|
||||||
|
* the behavior of the strategy is to continue if it returns false and it will then try other providers.
|
||||||
|
*
|
||||||
|
* @author Alexander Schwartz
|
||||||
|
*/
|
||||||
|
public class LegacySingleUserCredentialManager extends AbstractStorageManager<UserStorageProvider, UserStorageProviderModel> implements SingleUserCredentialManager {
|
||||||
|
|
||||||
|
private final UserModel user;
|
||||||
|
private final KeycloakSession session;
|
||||||
|
private final RealmModel realm;
|
||||||
|
private final LegacySingleUserCredentialManagerStrategy strategy;
|
||||||
|
|
||||||
|
public LegacySingleUserCredentialManager(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
super(session, UserStorageProviderFactory.class, UserStorageProvider.class, UserStorageProviderModel::new, "user");
|
||||||
|
this.user = user;
|
||||||
|
this.session = session;
|
||||||
|
this.realm = realm;
|
||||||
|
this.strategy = new LegacySingleUserCredentialManagerStrategy(session, realm, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(List<CredentialInput> inputs) {
|
||||||
|
if (!isValid(user)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CredentialInput> toValidate = new LinkedList<>(inputs);
|
||||||
|
|
||||||
|
String providerId = StorageId.isLocalStorage(user.getId()) ? user.getFederationLink() : StorageId.providerId(user.getId());
|
||||||
|
if (providerId != null) {
|
||||||
|
UserStorageProviderModel model = getStorageProviderModel(realm, providerId);
|
||||||
|
if (model == null || !model.isEnabled()) return false;
|
||||||
|
|
||||||
|
CredentialInputValidator validator = getStorageProviderInstance(model, CredentialInputValidator.class);
|
||||||
|
if (validator != null) {
|
||||||
|
validate(realm, user, toValidate, validator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strategy.validateCredentials(toValidate);
|
||||||
|
|
||||||
|
getCredentialProviders(session, CredentialInputValidator.class)
|
||||||
|
.forEach(validator -> validate(realm, user, toValidate, validator));
|
||||||
|
|
||||||
|
return toValidate.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateCredential(CredentialInput input) {
|
||||||
|
String providerId = StorageId.isLocalStorage(user.getId()) ? user.getFederationLink() : StorageId.providerId(user.getId());
|
||||||
|
if (!StorageId.isLocalStorage(user.getId())) throwExceptionIfInvalidUser(user);
|
||||||
|
|
||||||
|
if (providerId != null) {
|
||||||
|
UserStorageProviderModel model = getStorageProviderModel(realm, providerId);
|
||||||
|
if (model == null || !model.isEnabled()) return false;
|
||||||
|
|
||||||
|
CredentialInputUpdater updater = getStorageProviderInstance(model, CredentialInputUpdater.class);
|
||||||
|
if (updater != null && updater.supportsCredentialType(input.getType())) {
|
||||||
|
if (updater.updateCredential(realm, user, input)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strategy.updateCredential(input) ||
|
||||||
|
getCredentialProviders(session, CredentialInputUpdater.class)
|
||||||
|
.filter(updater -> updater.supportsCredentialType(input.getType()))
|
||||||
|
.anyMatch(updater -> updater.updateCredential(realm, user, input));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateStoredCredential(CredentialModel cred) {
|
||||||
|
throwExceptionIfInvalidUser(user);
|
||||||
|
strategy.updateStoredCredential(cred);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel createStoredCredential(CredentialModel cred) {
|
||||||
|
throwExceptionIfInvalidUser(user);
|
||||||
|
return strategy.createStoredCredential(cred);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeStoredCredentialById(String id) {
|
||||||
|
throwExceptionIfInvalidUser(user);
|
||||||
|
return strategy.removeStoredCredentialById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel getStoredCredentialById(String id) {
|
||||||
|
return strategy.getStoredCredentialById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<CredentialModel> getStoredCredentialsStream() {
|
||||||
|
return strategy.getStoredCredentialsStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<CredentialModel> getStoredCredentialsByTypeStream(String type) {
|
||||||
|
return strategy.getStoredCredentialsByTypeStream(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel getStoredCredentialByNameAndType(String name, String type) {
|
||||||
|
return strategy.getStoredCredentialByNameAndType(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean moveStoredCredentialTo(String id, String newPreviousCredentialId) {
|
||||||
|
throwExceptionIfInvalidUser(user);
|
||||||
|
return strategy.moveStoredCredentialTo(id, newPreviousCredentialId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateCredentialLabel(String credentialId, String userLabel) {
|
||||||
|
throwExceptionIfInvalidUser(user);
|
||||||
|
CredentialModel credential = getStoredCredentialById(credentialId);
|
||||||
|
credential.setUserLabel(userLabel);
|
||||||
|
updateStoredCredential(credential);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disableCredentialType(String credentialType) {
|
||||||
|
String providerId = StorageId.isLocalStorage(user.getId()) ? user.getFederationLink() : StorageId.providerId(user.getId());
|
||||||
|
if (!StorageId.isLocalStorage(user.getId())) throwExceptionIfInvalidUser(user);
|
||||||
|
if (providerId != null) {
|
||||||
|
UserStorageProviderModel model = getStorageProviderModel(realm, providerId);
|
||||||
|
if (model == null || !model.isEnabled()) return;
|
||||||
|
|
||||||
|
CredentialInputUpdater updater = getStorageProviderInstance(model, CredentialInputUpdater.class);
|
||||||
|
if (updater.supportsCredentialType(credentialType)) {
|
||||||
|
updater.disableCredentialType(realm, user, credentialType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getCredentialProviders(session, CredentialInputUpdater.class)
|
||||||
|
.filter(updater -> updater.supportsCredentialType(credentialType))
|
||||||
|
.forEach(updater -> updater.disableCredentialType(realm, user, credentialType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<String> getDisableableCredentialTypesStream() {
|
||||||
|
Stream<String> types = Stream.empty();
|
||||||
|
String providerId = StorageId.isLocalStorage(user) ? user.getFederationLink() : StorageId.resolveProviderId(user);
|
||||||
|
if (providerId != null) {
|
||||||
|
UserStorageProviderModel model = getStorageProviderModel(realm, providerId);
|
||||||
|
if (model == null || !model.isEnabled()) return types;
|
||||||
|
|
||||||
|
CredentialInputUpdater updater = getStorageProviderInstance(model, CredentialInputUpdater.class);
|
||||||
|
if (updater != null) types = updater.getDisableableCredentialTypesStream(realm, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stream.concat(types, getCredentialProviders(session, CredentialInputUpdater.class)
|
||||||
|
.flatMap(updater -> updater.getDisableableCredentialTypesStream(realm, user)))
|
||||||
|
.distinct();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConfiguredFor(String type) {
|
||||||
|
UserStorageCredentialConfigured userStorageConfigured = isConfiguredThroughUserStorage(realm, user, type);
|
||||||
|
|
||||||
|
// Check if we can rely just on userStorage to decide if credential is configured for the user or not
|
||||||
|
switch (userStorageConfigured) {
|
||||||
|
case CONFIGURED: return true;
|
||||||
|
case USER_STORAGE_DISABLED: return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check locally as a fallback
|
||||||
|
return isConfiguredLocally(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConfiguredLocally(String type) {
|
||||||
|
return getCredentialProviders(session, CredentialInputValidator.class)
|
||||||
|
.anyMatch(validator -> validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<String> getConfiguredUserStorageCredentialTypesStream(UserModel user) {
|
||||||
|
return getCredentialProviders(session, CredentialProvider.class).map(CredentialProvider::getType)
|
||||||
|
.filter(credentialType -> UserStorageCredentialConfigured.CONFIGURED == isConfiguredThroughUserStorage(realm, user, credentialType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel createCredentialThroughProvider(CredentialModel model) {
|
||||||
|
throwExceptionIfInvalidUser(user);
|
||||||
|
return session.getKeycloakSessionFactory()
|
||||||
|
.getProviderFactoriesStream(CredentialProvider.class)
|
||||||
|
.map(f -> session.getProvider(CredentialProvider.class, f.getId()))
|
||||||
|
.filter(provider -> Objects.equals(provider.getType(), model.getType()))
|
||||||
|
.map(cp -> cp.createCredential(realm, user, cp.getCredentialFromModel(model)))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum UserStorageCredentialConfigured {
|
||||||
|
CONFIGURED,
|
||||||
|
USER_STORAGE_DISABLED,
|
||||||
|
NOT_CONFIGURED
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserStorageCredentialConfigured isConfiguredThroughUserStorage(RealmModel realm, UserModel user, String type) {
|
||||||
|
String providerId = StorageId.isLocalStorage(user) ? user.getFederationLink() : StorageId.resolveProviderId(user);
|
||||||
|
if (providerId != null) {
|
||||||
|
UserStorageProviderModel model = getStorageProviderModel(realm, providerId);
|
||||||
|
if (model == null || !model.isEnabled()) return UserStorageCredentialConfigured.USER_STORAGE_DISABLED;
|
||||||
|
|
||||||
|
CredentialInputValidator validator = getStorageProviderInstance(model, CredentialInputValidator.class);
|
||||||
|
if (validator != null && validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) {
|
||||||
|
return UserStorageCredentialConfigured.CONFIGURED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return UserStorageCredentialConfigured.NOT_CONFIGURED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
|
private boolean isValid(UserModel user) {
|
||||||
|
Objects.requireNonNull(user);
|
||||||
|
return user.getServiceAccountClientLink() == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validate(RealmModel realm, UserModel user, List<CredentialInput> toValidate, CredentialInputValidator validator) {
|
||||||
|
toValidate.removeIf(input -> validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> Stream<T> getCredentialProviders(KeycloakSession session, Class<T> type) {
|
||||||
|
//noinspection unchecked
|
||||||
|
return session.getKeycloakSessionFactory().getProviderFactoriesStream(CredentialProvider.class)
|
||||||
|
.filter(f -> Types.supports(type, f, CredentialProviderFactory.class))
|
||||||
|
.map(f -> (T) session.getProvider(CredentialProvider.class, f.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void throwExceptionIfInvalidUser(UserModel user) {
|
||||||
|
if (!isValid(user)) {
|
||||||
|
throw new RuntimeException("You can not manage credentials for this user");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* 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.credential;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.storage.AbstractStorageManager;
|
||||||
|
import org.keycloak.storage.DatastoreProvider;
|
||||||
|
import org.keycloak.storage.LegacyStoreManagers;
|
||||||
|
import org.keycloak.storage.StorageId;
|
||||||
|
import org.keycloak.storage.UserStorageProvider;
|
||||||
|
import org.keycloak.storage.UserStorageProviderFactory;
|
||||||
|
import org.keycloak.storage.UserStorageProviderModel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strategy for {@link LegacySingleUserCredentialManager} to handle classic local storage including federation.
|
||||||
|
*
|
||||||
|
* @author Alexander Schwartz
|
||||||
|
*/
|
||||||
|
public class LegacySingleUserCredentialManagerStrategy extends AbstractStorageManager<UserStorageProvider, UserStorageProviderModel> implements SingleUserCredentialManagerStrategy {
|
||||||
|
|
||||||
|
private final UserModel user;
|
||||||
|
private final RealmModel realm;
|
||||||
|
|
||||||
|
public LegacySingleUserCredentialManagerStrategy(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
super(session, UserStorageProviderFactory.class, UserStorageProvider.class, UserStorageProviderModel::new, "user");
|
||||||
|
this.user = user;
|
||||||
|
this.realm = realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateCredentials(List<CredentialInput> toValidate) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateCredential(CredentialInput input) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateStoredCredential(CredentialModel cred) {
|
||||||
|
getStoreForUser(user).updateCredential(realm, user, cred);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel createStoredCredential(CredentialModel cred) {
|
||||||
|
return getStoreForUser(user).createCredential(realm, user, cred);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean removeStoredCredentialById(String id) {
|
||||||
|
return getStoreForUser(user).removeStoredCredential(realm, user, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel getStoredCredentialById(String id) {
|
||||||
|
return getStoreForUser(user).getStoredCredentialById(realm, user, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<CredentialModel> getStoredCredentialsStream() {
|
||||||
|
return getStoreForUser(user).getStoredCredentialsStream(realm, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<CredentialModel> getStoredCredentialsByTypeStream(String type) {
|
||||||
|
return getStoreForUser(user).getStoredCredentialsByTypeStream(realm, user, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel getStoredCredentialByNameAndType(String name, String type) {
|
||||||
|
return getStoreForUser(user).getStoredCredentialByNameAndType(realm, user, name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean moveStoredCredentialTo(String id, String newPreviousCredentialId) {
|
||||||
|
return getStoreForUser(user).moveCredentialTo(realm, user, id, newPreviousCredentialId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserCredentialStore getStoreForUser(UserModel user) {
|
||||||
|
LegacyStoreManagers p = (LegacyStoreManagers) session.getProvider(DatastoreProvider.class);
|
||||||
|
if (StorageId.isLocalStorage(user.getId())) {
|
||||||
|
return (UserCredentialStore) p.userLocalStorage();
|
||||||
|
} else {
|
||||||
|
return (UserCredentialStore) p.userFederatedStorage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import org.keycloak.models.ClientScopeProvider;
|
||||||
import org.keycloak.models.GroupProvider;
|
import org.keycloak.models.GroupProvider;
|
||||||
import org.keycloak.models.RoleProvider;
|
import org.keycloak.models.RoleProvider;
|
||||||
import org.keycloak.models.UserProvider;
|
import org.keycloak.models.UserProvider;
|
||||||
|
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||||
|
|
||||||
public interface LegacyStoreManagers {
|
public interface LegacyStoreManagers {
|
||||||
|
|
||||||
|
@ -17,4 +18,8 @@ public interface LegacyStoreManagers {
|
||||||
GroupProvider groupStorageManager();
|
GroupProvider groupStorageManager();
|
||||||
|
|
||||||
UserProvider userStorageManager();
|
UserProvider userStorageManager();
|
||||||
|
|
||||||
|
UserProvider userLocalStorage();
|
||||||
|
|
||||||
|
UserFederatedStorageProvider userFederatedStorage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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.models.map.credential;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard implementation for a {@link MapSingleUserCredentialManagerEntity} where the store doesn't provide
|
||||||
|
* validation of credentials.
|
||||||
|
*
|
||||||
|
* @author Alexander Schwartz
|
||||||
|
*/
|
||||||
|
public class DefaultMapSingleUserCredentialManagerEntity implements MapSingleUserCredentialManagerEntity {
|
||||||
|
@Override
|
||||||
|
public void validateCredentials(List<CredentialInput> inputs) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateCredential(CredentialInput input) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,217 @@
|
||||||
|
/*
|
||||||
|
* 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.models.map.credential;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.reflections.Types;
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
|
import org.keycloak.credential.CredentialInputUpdater;
|
||||||
|
import org.keycloak.credential.CredentialInputValidator;
|
||||||
|
import org.keycloak.credential.CredentialModel;
|
||||||
|
import org.keycloak.credential.CredentialProvider;
|
||||||
|
import org.keycloak.credential.CredentialProviderFactory;
|
||||||
|
import org.keycloak.credential.SingleUserCredentialManagerStrategy;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.SingleUserCredentialManager;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.map.user.MapUserEntity;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handling credentials for a given user.
|
||||||
|
*
|
||||||
|
* This serves as a wrapper to specific strategies. The wrapping code implements the logic for {@link CredentialInputUpdater}s
|
||||||
|
* and {@link CredentialInputValidator}s. Storage specific strategies can be added like for example in
|
||||||
|
* org.keycloak.models.map.credential.MapSingleUserCredentialManagerStrategy.
|
||||||
|
*
|
||||||
|
* @author Alexander Schwartz
|
||||||
|
*/
|
||||||
|
public class MapSingleUserCredentialManager implements SingleUserCredentialManager {
|
||||||
|
|
||||||
|
private final UserModel user;
|
||||||
|
private final KeycloakSession session;
|
||||||
|
private final RealmModel realm;
|
||||||
|
private final SingleUserCredentialManagerStrategy strategy;
|
||||||
|
|
||||||
|
public MapSingleUserCredentialManager(KeycloakSession session, RealmModel realm, UserModel user, MapUserEntity entity) {
|
||||||
|
this.user = user;
|
||||||
|
this.session = session;
|
||||||
|
this.realm = realm;
|
||||||
|
this.strategy = new MapSingleUserCredentialManagerStrategy(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(List<CredentialInput> inputs) {
|
||||||
|
if (!isValid(user)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CredentialInput> toValidate = new LinkedList<>(inputs);
|
||||||
|
|
||||||
|
strategy.validateCredentials(toValidate);
|
||||||
|
|
||||||
|
getCredentialProviders(session, CredentialInputValidator.class)
|
||||||
|
.forEach(validator -> validate(realm, user, toValidate, validator));
|
||||||
|
|
||||||
|
return toValidate.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateCredential(CredentialInput input) {
|
||||||
|
return strategy.updateCredential(input) ||
|
||||||
|
getCredentialProviders(session, CredentialInputUpdater.class)
|
||||||
|
.filter(updater -> updater.supportsCredentialType(input.getType()))
|
||||||
|
.anyMatch(updater -> updater.updateCredential(realm, user, input));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateStoredCredential(CredentialModel cred) {
|
||||||
|
throwExceptionIfInvalidUser(user);
|
||||||
|
strategy.updateStoredCredential(cred);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel createStoredCredential(CredentialModel cred) {
|
||||||
|
throwExceptionIfInvalidUser(user);
|
||||||
|
return strategy.createStoredCredential(cred);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeStoredCredentialById(String id) {
|
||||||
|
throwExceptionIfInvalidUser(user);
|
||||||
|
return strategy.removeStoredCredentialById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel getStoredCredentialById(String id) {
|
||||||
|
return strategy.getStoredCredentialById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<CredentialModel> getStoredCredentialsStream() {
|
||||||
|
return strategy.getStoredCredentialsStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<CredentialModel> getStoredCredentialsByTypeStream(String type) {
|
||||||
|
return strategy.getStoredCredentialsByTypeStream(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel getStoredCredentialByNameAndType(String name, String type) {
|
||||||
|
return strategy.getStoredCredentialByNameAndType(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean moveStoredCredentialTo(String id, String newPreviousCredentialId) {
|
||||||
|
throwExceptionIfInvalidUser(user);
|
||||||
|
return strategy.moveStoredCredentialTo(id, newPreviousCredentialId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateCredentialLabel(String credentialId, String userLabel) {
|
||||||
|
throwExceptionIfInvalidUser(user);
|
||||||
|
CredentialModel credential = getStoredCredentialById(credentialId);
|
||||||
|
credential.setUserLabel(userLabel);
|
||||||
|
updateStoredCredential(credential);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disableCredentialType(String credentialType) {
|
||||||
|
getCredentialProviders(session, CredentialInputUpdater.class)
|
||||||
|
.filter(updater -> updater.supportsCredentialType(credentialType))
|
||||||
|
.forEach(updater -> updater.disableCredentialType(realm, user, credentialType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<String> getDisableableCredentialTypesStream() {
|
||||||
|
// TODO: ask the store
|
||||||
|
return getCredentialProviders(session, CredentialInputUpdater.class)
|
||||||
|
.flatMap(updater -> updater.getDisableableCredentialTypesStream(realm, user));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConfiguredFor(String type) {
|
||||||
|
// TODO: ask the store
|
||||||
|
return isConfiguredLocally(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConfiguredLocally(String type) {
|
||||||
|
return getCredentialProviders(session, CredentialInputValidator.class)
|
||||||
|
.anyMatch(validator -> validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<String> getConfiguredUserStorageCredentialTypesStream(UserModel user) {
|
||||||
|
// TODO ask the store
|
||||||
|
return getCredentialProviders(session, CredentialProvider.class).map(CredentialProvider::getType)
|
||||||
|
.filter(credentialType -> UserStorageCredentialConfigured.CONFIGURED == isConfiguredThroughUserStorage(realm, user, credentialType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel createCredentialThroughProvider(CredentialModel model) {
|
||||||
|
throwExceptionIfInvalidUser(user);
|
||||||
|
return session.getKeycloakSessionFactory()
|
||||||
|
.getProviderFactoriesStream(CredentialProvider.class)
|
||||||
|
.map(f -> session.getProvider(CredentialProvider.class, f.getId()))
|
||||||
|
.filter(provider -> Objects.equals(provider.getType(), model.getType()))
|
||||||
|
.map(cp -> cp.createCredential(realm, user, cp.getCredentialFromModel(model)))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum UserStorageCredentialConfigured {
|
||||||
|
CONFIGURED,
|
||||||
|
USER_STORAGE_DISABLED,
|
||||||
|
NOT_CONFIGURED
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserStorageCredentialConfigured isConfiguredThroughUserStorage(RealmModel realm, UserModel user, String type) {
|
||||||
|
return UserStorageCredentialConfigured.NOT_CONFIGURED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
|
private boolean isValid(UserModel user) {
|
||||||
|
Objects.requireNonNull(user);
|
||||||
|
return user.getServiceAccountClientLink() == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validate(RealmModel realm, UserModel user, List<CredentialInput> toValidate, CredentialInputValidator validator) {
|
||||||
|
toValidate.removeIf(input -> validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> Stream<T> getCredentialProviders(KeycloakSession session, Class<T> type) {
|
||||||
|
//noinspection unchecked
|
||||||
|
return session.getKeycloakSessionFactory().getProviderFactoriesStream(CredentialProvider.class)
|
||||||
|
.filter(f -> Types.supports(type, f, CredentialProviderFactory.class))
|
||||||
|
.map(f -> (T) session.getProvider(CredentialProvider.class, f.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void throwExceptionIfInvalidUser(UserModel user) {
|
||||||
|
if (!isValid(user)) {
|
||||||
|
throw new RuntimeException("You can not manage credentials for this user");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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.models.map.credential;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for credential management in entities in the map storage.
|
||||||
|
*
|
||||||
|
* @author Alexander Schwartz
|
||||||
|
*/
|
||||||
|
public interface MapSingleUserCredentialManagerEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the credentials of a user.
|
||||||
|
* Will remove all inputs from the list that have been successfully validated, all remaining entries
|
||||||
|
* weren't validated. An empty list signals to the caller that authentication has completed successfully.
|
||||||
|
*
|
||||||
|
* @param inputs Credential inputs as provided by a user
|
||||||
|
*/
|
||||||
|
void validateCredentials(List<CredentialInput> inputs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the credentials for a user with the input provided by the user for this store.
|
||||||
|
* @param input new credentials as provided by the user
|
||||||
|
* @return true if the credential has been updated successfully, false otherwise. False might indicate that the
|
||||||
|
* credential type isn't supported of the new credentials aren't valid.
|
||||||
|
*/
|
||||||
|
boolean updateCredential(CredentialInput input);
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* 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.models.map.credential;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
|
import org.keycloak.credential.CredentialModel;
|
||||||
|
import org.keycloak.credential.SingleUserCredentialManagerStrategy;
|
||||||
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
|
import org.keycloak.models.map.user.MapUserCredentialEntity;
|
||||||
|
import org.keycloak.models.map.user.MapUserEntity;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link SingleUserCredentialManagerStrategy} for map storages.
|
||||||
|
* Will delegate calls to the credential manager in the entity.
|
||||||
|
*
|
||||||
|
* @author Alexander Schwartz
|
||||||
|
*/
|
||||||
|
public class MapSingleUserCredentialManagerStrategy implements SingleUserCredentialManagerStrategy {
|
||||||
|
private final MapUserEntity entity;
|
||||||
|
|
||||||
|
public MapSingleUserCredentialManagerStrategy(MapUserEntity entity) {
|
||||||
|
this.entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateCredentials(List<CredentialInput> toValidate) {
|
||||||
|
entity.getUserCredentialManager().validateCredentials(toValidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateCredential(CredentialInput input) {
|
||||||
|
return entity.getUserCredentialManager().updateCredential(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateStoredCredential(CredentialModel credentialModel) {
|
||||||
|
entity.getCredential(credentialModel.getId()).ifPresent(c -> {
|
||||||
|
c.setCreatedDate(credentialModel.getCreatedDate());
|
||||||
|
c.setUserLabel(credentialModel.getUserLabel());
|
||||||
|
c.setType(credentialModel.getType());
|
||||||
|
c.setSecretData(credentialModel.getSecretData());
|
||||||
|
c.setCredentialData(credentialModel.getCredentialData());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel createStoredCredential(CredentialModel cred) {
|
||||||
|
MapUserCredentialEntity credentialEntity = MapUserCredentialEntity.fromModel(cred);
|
||||||
|
|
||||||
|
if (entity.getCredential(cred.getId()).isPresent()) {
|
||||||
|
throw new ModelDuplicateException("A CredentialModel with given id already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.addCredential(credentialEntity);
|
||||||
|
|
||||||
|
return MapUserCredentialEntity.toModel(credentialEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean removeStoredCredentialById(String id) {
|
||||||
|
return entity.removeCredential(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel getStoredCredentialById(String id) {
|
||||||
|
return entity.getCredential(id).map(MapUserCredentialEntity::toModel).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<CredentialModel> getStoredCredentialsStream() {
|
||||||
|
return Optional.ofNullable(entity.getCredentials()).orElse(Collections.emptyList()).stream()
|
||||||
|
.map(MapUserCredentialEntity::toModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<CredentialModel> getStoredCredentialsByTypeStream(String type) {
|
||||||
|
return getStoredCredentialsStream()
|
||||||
|
.filter(credential -> Objects.equals(type, credential.getType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel getStoredCredentialByNameAndType(String name, String type) {
|
||||||
|
return getStoredCredentialsStream()
|
||||||
|
.filter(credential -> Objects.equals(name, credential.getUserLabel()))
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean moveStoredCredentialTo(String id, String newPreviousCredentialId) {
|
||||||
|
return entity.moveCredential(id, newPreviousCredentialId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.keycloak.models.map.storage;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
import org.keycloak.models.map.user.MapCredentialValidationOutput;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map store transaction that can authenticate the credentials provided by a user.
|
||||||
|
*
|
||||||
|
* @author Alexander Schwartz
|
||||||
|
*/
|
||||||
|
public interface MapKeycloakTransactionWithAuth<V extends AbstractEntity, M> extends MapKeycloakTransaction<V, M> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate a user with the provided input credentials. Use this, for example, for Kerberos SPNEGO
|
||||||
|
* authentication, where the user will be determined at the end of the interaction with the client.
|
||||||
|
* @param realm realm against which to authenticate against
|
||||||
|
* @param input information provided by the user
|
||||||
|
* @return Information on how to continue the conversion with the client, or a terminal result. For a successful
|
||||||
|
* authentication, will also contain information about the user.
|
||||||
|
*/
|
||||||
|
MapCredentialValidationOutput<V> authenticate(RealmModel realm, CredentialInput input);
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package org.keycloak.models.map.storage;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
import org.keycloak.models.map.common.UpdatableEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementing this interface signals that the store can validate credentials.
|
||||||
|
* This will be implemented, for example, by a store that supports SPNEGO for Kerberos authentication.
|
||||||
|
*
|
||||||
|
* @author Alexander Schwartz
|
||||||
|
*/
|
||||||
|
public interface MapStorageWithAuth<V extends AbstractEntity & UpdatableEntity, M> extends MapStorage<V, M> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine which credential types a store supports.
|
||||||
|
* This method should be a cheap way to query the store before creating a more expensive transaction and performing an authentication.
|
||||||
|
*
|
||||||
|
* @param type supported credential type by this store, for example {@link CredentialModel#KERBEROS}.
|
||||||
|
* @return <code>true</code> if the credential type is supported by this storage
|
||||||
|
*/
|
||||||
|
boolean supportsCredentialType(String type);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
MapKeycloakTransactionWithAuth<V, M> createTransaction(KeycloakSession session);
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* 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.models.map.user;
|
||||||
|
|
||||||
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output of a credential validation.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class MapCredentialValidationOutput<V> {
|
||||||
|
|
||||||
|
private final V authenticatedUser;
|
||||||
|
private final CredentialValidationOutput.Status authStatus; // status whether user is authenticated or more steps needed
|
||||||
|
private final Map<String, String> state; // Additional state related to authentication. It can contain data to be sent back to client or data about used credentials.
|
||||||
|
|
||||||
|
public MapCredentialValidationOutput(V authenticatedUser, CredentialValidationOutput.Status authStatus, Map<String, String> state) {
|
||||||
|
this.authenticatedUser = authenticatedUser;
|
||||||
|
this.authStatus = authStatus;
|
||||||
|
this.state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MapCredentialValidationOutput<?> failed() {
|
||||||
|
return new MapCredentialValidationOutput<Void>(null, CredentialValidationOutput.Status.FAILED, Collections.emptyMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
public V getAuthenticatedUser() {
|
||||||
|
return authenticatedUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CredentialValidationOutput.Status getAuthStatus() {
|
||||||
|
return authStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State that is passed back by provider
|
||||||
|
*/
|
||||||
|
public Map<String, String> getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -24,11 +24,12 @@ import org.keycloak.models.map.common.AbstractEntity;
|
||||||
import org.keycloak.models.map.common.DeepCloner;
|
import org.keycloak.models.map.common.DeepCloner;
|
||||||
import org.keycloak.models.map.common.EntityWithAttributes;
|
import org.keycloak.models.map.common.EntityWithAttributes;
|
||||||
import org.keycloak.models.map.common.UpdatableEntity;
|
import org.keycloak.models.map.common.UpdatableEntity;
|
||||||
|
import org.keycloak.models.map.credential.DefaultMapSingleUserCredentialManagerEntity;
|
||||||
|
import org.keycloak.models.map.credential.MapSingleUserCredentialManagerEntity;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -39,7 +40,7 @@ import java.util.Set;
|
||||||
@DeepCloner.Root
|
@DeepCloner.Root
|
||||||
public interface MapUserEntity extends UpdatableEntity, AbstractEntity, EntityWithAttributes {
|
public interface MapUserEntity extends UpdatableEntity, AbstractEntity, EntityWithAttributes {
|
||||||
|
|
||||||
public abstract class AbstractUserEntity extends UpdatableEntity.Impl implements MapUserEntity {
|
abstract class AbstractUserEntity extends Impl implements MapUserEntity {
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(MapUserProvider.class);
|
private static final Logger LOG = Logger.getLogger(MapUserProvider.class);
|
||||||
private String id;
|
private String id;
|
||||||
|
@ -246,4 +247,9 @@ public interface MapUserEntity extends UpdatableEntity, AbstractEntity, EntityWi
|
||||||
|
|
||||||
Long getNotBefore();
|
Long getNotBefore();
|
||||||
void setNotBefore(Long notBefore);
|
void setNotBefore(Long notBefore);
|
||||||
|
|
||||||
|
@IgnoreForEntityImplementationGenerator
|
||||||
|
default MapSingleUserCredentialManagerEntity getUserCredentialManager() {
|
||||||
|
return new DefaultMapSingleUserCredentialManagerEntity();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,15 @@ import org.keycloak.authorization.AuthorizationProvider;
|
||||||
import org.keycloak.authorization.model.Resource;
|
import org.keycloak.authorization.model.Resource;
|
||||||
import org.keycloak.authorization.store.ResourceStore;
|
import org.keycloak.authorization.store.ResourceStore;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.common.util.reflections.Types;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.credential.CredentialModel;
|
import org.keycloak.credential.CredentialAuthentication;
|
||||||
import org.keycloak.credential.UserCredentialStore;
|
import org.keycloak.credential.CredentialInput;
|
||||||
|
import org.keycloak.credential.CredentialProvider;
|
||||||
|
import org.keycloak.credential.CredentialProviderFactory;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientScopeModel;
|
import org.keycloak.models.ClientScopeModel;
|
||||||
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
@ -37,11 +41,14 @@ import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredActionProviderModel;
|
import org.keycloak.models.RequiredActionProviderModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.SingleUserCredentialManager;
|
||||||
import org.keycloak.models.UserConsentModel;
|
import org.keycloak.models.UserConsentModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserModel.SearchableFields;
|
import org.keycloak.models.UserModel.SearchableFields;
|
||||||
import org.keycloak.models.UserProvider;
|
import org.keycloak.models.UserProvider;
|
||||||
import org.keycloak.models.map.common.TimeAdapter;
|
import org.keycloak.models.map.common.TimeAdapter;
|
||||||
|
import org.keycloak.models.map.credential.MapSingleUserCredentialManager;
|
||||||
|
import org.keycloak.models.map.storage.MapKeycloakTransactionWithAuth;
|
||||||
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||||
import org.keycloak.models.map.storage.MapStorage;
|
import org.keycloak.models.map.storage.MapStorage;
|
||||||
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
|
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
|
||||||
|
@ -77,7 +84,7 @@ import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING;
|
||||||
import static org.keycloak.models.map.storage.QueryParameters.withCriteria;
|
import static org.keycloak.models.map.storage.QueryParameters.withCriteria;
|
||||||
import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria;
|
import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria;
|
||||||
|
|
||||||
public class MapUserProvider implements UserProvider.Streams, UserCredentialStore.Streams {
|
public class MapUserProvider implements UserProvider.Streams {
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(MapUserProvider.class);
|
private static final Logger LOG = Logger.getLogger(MapUserProvider.class);
|
||||||
private final KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
|
@ -101,6 +108,11 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
|
||||||
public boolean checkUsernameUniqueness(RealmModel realm, String username) {
|
public boolean checkUsernameUniqueness(RealmModel realm, String username) {
|
||||||
return getUserByUsername(realm, username) != null;
|
return getUserByUsername(realm, username) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SingleUserCredentialManager getUserCredentialManager() {
|
||||||
|
return new MapSingleUserCredentialManager(session, realm, this, entity);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +121,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
|
||||||
return c -> false;
|
return c -> false;
|
||||||
}
|
}
|
||||||
String realmId = realm.getId();
|
String realmId = realm.getId();
|
||||||
return entity -> Objects.equals(realmId, entity.getRealmId());
|
return entity -> entity.getRealmId() == null || Objects.equals(realmId, entity.getRealmId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private ModelException userDoesntExistException() {
|
private ModelException userDoesntExistException() {
|
||||||
|
@ -747,94 +759,41 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
|
||||||
.map(entityToAdapterFunc(realm));
|
.map(entityToAdapterFunc(realm));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
|
|
||||||
getEntityById(realm, user.getId())
|
|
||||||
.ifPresent(updateCredential(cred));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Consumer<MapUserEntity> updateCredential(CredentialModel credentialModel) {
|
|
||||||
return user -> user.getCredential(credentialModel.getId()).ifPresent(c -> {
|
|
||||||
c.setCreatedDate(credentialModel.getCreatedDate());
|
|
||||||
c.setUserLabel(credentialModel.getUserLabel());
|
|
||||||
c.setType(credentialModel.getType());
|
|
||||||
c.setSecretData(credentialModel.getSecretData());
|
|
||||||
c.setCredentialData(credentialModel.getCredentialData());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
|
|
||||||
LOG.tracef("createCredential(%s, %s, %s)%s", realm, user.getId(), cred.getId(), getShortStackTrace());
|
|
||||||
MapUserEntity userEntity = getEntityByIdOrThrow(realm, user.getId());
|
|
||||||
MapUserCredentialEntity credentialEntity = MapUserCredentialEntity.fromModel(cred);
|
|
||||||
|
|
||||||
if (userEntity.getCredential(cred.getId()).isPresent()) {
|
|
||||||
throw new ModelDuplicateException("A CredentialModel with given id already exists");
|
|
||||||
}
|
|
||||||
|
|
||||||
userEntity.addCredential(credentialEntity);
|
|
||||||
|
|
||||||
return MapUserCredentialEntity.toModel(credentialEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
|
|
||||||
LOG.tracef("removeStoredCredential(%s, %s, %s)%s", realm, user.getId(), id, getShortStackTrace());
|
|
||||||
|
|
||||||
Optional<MapUserEntity> entityById = getEntityById(realm, user.getId());
|
|
||||||
if (!entityById.isPresent()) return false;
|
|
||||||
|
|
||||||
Boolean result = entityById.get().removeCredential(id);
|
|
||||||
return result == null ? true : result; // TODO: make removeStoredCredential return Boolean so the caller can correctly handle "I don't know" null answer
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
|
|
||||||
LOG.tracef("getStoredCredentialById(%s, %s, %s)%s", realm, user.getId(), id, getShortStackTrace());
|
|
||||||
return getEntityById(realm, user.getId())
|
|
||||||
.flatMap(mapUserEntity -> mapUserEntity.getCredential(id))
|
|
||||||
.map(MapUserCredentialEntity::toModel)
|
|
||||||
.orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Stream<CredentialModel> getStoredCredentialsStream(RealmModel realm, UserModel user) {
|
|
||||||
LOG.tracef("getStoredCredentialsStream(%s, %s)%s", realm, user.getId(), getShortStackTrace());
|
|
||||||
|
|
||||||
return getEntityById(realm, user.getId())
|
|
||||||
.map(MapUserEntity::getCredentials)
|
|
||||||
.map(Collection::stream)
|
|
||||||
.orElseGet(Stream::empty)
|
|
||||||
.map(MapUserCredentialEntity::toModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Stream<CredentialModel> getStoredCredentialsByTypeStream(RealmModel realm, UserModel user, String type) {
|
|
||||||
LOG.tracef("getStoredCredentialsByTypeStream(%s, %s, %s)%s", realm, user.getId(), type, getShortStackTrace());
|
|
||||||
return getStoredCredentialsStream(realm, user)
|
|
||||||
.filter(credential -> Objects.equals(type, credential.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
|
|
||||||
LOG.tracef("getStoredCredentialByNameAndType(%s, %s, %s, %s)%s", realm, user.getId(), name, type, getShortStackTrace());
|
|
||||||
return getStoredCredentialsByType(realm, user, type).stream()
|
|
||||||
.filter(credential -> Objects.equals(name, credential.getUserLabel()))
|
|
||||||
.findFirst().orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean moveCredentialTo(RealmModel realm, UserModel user, String id, String newPreviousCredentialId) {
|
|
||||||
LOG.tracef("moveCredentialTo(%s, %s, %s, %s)%s", realm, user, id, newPreviousCredentialId, getShortStackTrace());
|
|
||||||
return getEntityByIdOrThrow(realm, user.getId()).moveCredential(id, newPreviousCredentialId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public 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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialValidationOutput getUserByCredential(RealmModel realm, CredentialInput input) {
|
||||||
|
// TODO: future implementations would narrow down the stream to those provider enabled for the specific realm
|
||||||
|
Stream<CredentialAuthentication> credentialAuthenticationStream = getCredentialProviders(session, CredentialAuthentication.class);
|
||||||
|
|
||||||
|
CredentialValidationOutput r = credentialAuthenticationStream
|
||||||
|
.filter(credentialAuthentication -> credentialAuthentication.supportsCredentialAuthenticationFor(input.getType()))
|
||||||
|
.map(credentialAuthentication -> credentialAuthentication.authenticate(realm, input))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
|
||||||
|
if (r == null && tx instanceof MapKeycloakTransactionWithAuth) {
|
||||||
|
MapCredentialValidationOutput<MapUserEntity> result = ((MapKeycloakTransactionWithAuth<MapUserEntity, UserModel>) tx).authenticate(realm, input);
|
||||||
|
if (result != null) {
|
||||||
|
UserModel user = null;
|
||||||
|
if (result.getAuthenticatedUser() != null) {
|
||||||
|
user = entityToAdapterFunc(realm).apply(result.getAuthenticatedUser());
|
||||||
|
}
|
||||||
|
r = new CredentialValidationOutput(user, result.getAuthStatus(), result.getState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
private DefaultModelCriteria<UserModel> addSearchToModelCriteria(String value,
|
private DefaultModelCriteria<UserModel> addSearchToModelCriteria(String value,
|
||||||
DefaultModelCriteria<UserModel> mcb) {
|
DefaultModelCriteria<UserModel> mcb) {
|
||||||
|
|
||||||
|
|
|
@ -8,21 +8,23 @@ import org.keycloak.models.RoleProvider;
|
||||||
import org.keycloak.models.UserProvider;
|
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 {
|
||||||
|
|
||||||
public ClientScopeProvider clientScopes();
|
ClientScopeProvider clientScopes();
|
||||||
|
|
||||||
public ClientProvider clients();
|
ClientProvider clients();
|
||||||
|
|
||||||
public GroupProvider groups();
|
GroupProvider groups();
|
||||||
|
|
||||||
public RealmProvider realms();
|
RealmProvider realms();
|
||||||
|
|
||||||
public RoleProvider roles();
|
RoleProvider roles();
|
||||||
|
|
||||||
public UserProvider users();
|
UserProvider users();
|
||||||
|
|
||||||
ExportImportManager getExportImportManager();
|
ExportImportManager getExportImportManager();
|
||||||
|
|
||||||
MigrationManager getMigrationManager();
|
MigrationManager getMigrationManager();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.credential;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to implement extendable strategies for the {@link org.keycloak.models.SingleUserCredentialManager}.
|
||||||
|
*/
|
||||||
|
public interface SingleUserCredentialManagerStrategy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the credentials passed as a list. The implementation should remove all credentials that validate
|
||||||
|
* successfully from the list. An empty list signals to the caller that authentication has completed successfully.
|
||||||
|
*/
|
||||||
|
void validateCredentials(List<CredentialInput> toValidate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the credential.
|
||||||
|
* @return true is the credential was update, false otherwise
|
||||||
|
*/
|
||||||
|
boolean updateCredential(CredentialInput input);
|
||||||
|
|
||||||
|
void updateStoredCredential(CredentialModel cred);
|
||||||
|
|
||||||
|
CredentialModel createStoredCredential(CredentialModel cred) ;
|
||||||
|
|
||||||
|
Boolean removeStoredCredentialById(String id);
|
||||||
|
|
||||||
|
CredentialModel getStoredCredentialById(String id);
|
||||||
|
|
||||||
|
Stream<CredentialModel> getStoredCredentialsStream();
|
||||||
|
|
||||||
|
Stream<CredentialModel> getStoredCredentialsByTypeStream(String type);
|
||||||
|
|
||||||
|
CredentialModel getStoredCredentialByNameAndType(String name, String type);
|
||||||
|
|
||||||
|
boolean moveStoredCredentialTo(String id, String newPreviousCredentialId);
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ package org.keycloak.models;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
import org.keycloak.provider.ProviderEvent;
|
import org.keycloak.provider.ProviderEvent;
|
||||||
import org.keycloak.storage.SearchableModelField;
|
import org.keycloak.storage.SearchableModelField;
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* 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.models;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
|
import org.keycloak.credential.CredentialModel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public interface SingleUserCredentialManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates list of credentials.
|
||||||
|
*/
|
||||||
|
boolean isValid(List<CredentialInput> inputs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a credentials of the user.
|
||||||
|
*/
|
||||||
|
boolean updateCredential(CredentialInput input);
|
||||||
|
|
||||||
|
void updateStoredCredential(CredentialModel cred);
|
||||||
|
|
||||||
|
CredentialModel createStoredCredential(CredentialModel cred);
|
||||||
|
|
||||||
|
boolean removeStoredCredentialById(String id);
|
||||||
|
|
||||||
|
CredentialModel getStoredCredentialById(String id);
|
||||||
|
|
||||||
|
Stream<CredentialModel> getStoredCredentialsStream();
|
||||||
|
|
||||||
|
Stream<CredentialModel> getStoredCredentialsByTypeStream(String type);
|
||||||
|
|
||||||
|
CredentialModel getStoredCredentialByNameAndType(String name, String type);
|
||||||
|
|
||||||
|
boolean moveStoredCredentialTo(String id, String newPreviousCredentialId);
|
||||||
|
|
||||||
|
void updateCredentialLabel(String credentialId, String userLabel);
|
||||||
|
|
||||||
|
void disableCredentialType(String credentialType);
|
||||||
|
|
||||||
|
Stream<String> getDisableableCredentialTypesStream();
|
||||||
|
|
||||||
|
boolean isConfiguredFor(String type);
|
||||||
|
|
||||||
|
// TODO: not needed for new store? -> no, will be removed without replacement
|
||||||
|
@Deprecated
|
||||||
|
boolean isConfiguredLocally(String type);
|
||||||
|
|
||||||
|
Stream<String> getConfiguredUserStorageCredentialTypesStream(UserModel user);
|
||||||
|
|
||||||
|
CredentialModel createCredentialThroughProvider(CredentialModel model);
|
||||||
|
}
|
|
@ -18,8 +18,8 @@
|
||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
import org.keycloak.provider.ProviderEvent;
|
import org.keycloak.provider.ProviderEvent;
|
||||||
|
|
||||||
import org.keycloak.storage.SearchableModelField;
|
import org.keycloak.storage.SearchableModelField;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -118,7 +118,7 @@ public interface UserModel extends RoleMapperModel {
|
||||||
* Get timestamp of user creation. May be null for old users created before this feature introduction.
|
* Get timestamp of user creation. May be null for old users created before this feature introduction.
|
||||||
*/
|
*/
|
||||||
Long getCreatedTimestamp();
|
Long getCreatedTimestamp();
|
||||||
|
|
||||||
void setCreatedTimestamp(Long timestamp);
|
void setCreatedTimestamp(Long timestamp);
|
||||||
|
|
||||||
boolean isEnabled();
|
boolean isEnabled();
|
||||||
|
@ -278,7 +278,7 @@ public interface UserModel extends RoleMapperModel {
|
||||||
default long getGroupsCount() {
|
default long getGroupsCount() {
|
||||||
return getGroupsCountByNameContaining(null);
|
return getGroupsCountByNameContaining(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
default long getGroupsCountByNameContaining(String search) {
|
default long getGroupsCountByNameContaining(String search) {
|
||||||
if (search == null) {
|
if (search == null) {
|
||||||
return getGroupsStream().count();
|
return getGroupsStream().count();
|
||||||
|
@ -298,6 +298,8 @@ public interface UserModel extends RoleMapperModel {
|
||||||
String getServiceAccountClientLink();
|
String getServiceAccountClientLink();
|
||||||
void setServiceAccountClientLink(String clientInternalId);
|
void setServiceAccountClientLink(String clientInternalId);
|
||||||
|
|
||||||
|
SingleUserCredentialManager getUserCredentialManager();
|
||||||
|
|
||||||
enum RequiredAction {
|
enum RequiredAction {
|
||||||
VERIFY_EMAIL,
|
VERIFY_EMAIL,
|
||||||
UPDATE_PROFILE,
|
UPDATE_PROFILE,
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.models.utils;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.SingleUserCredentialManager;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -209,6 +210,11 @@ public class UserModelDelegate implements UserModel.Streams {
|
||||||
delegate.setServiceAccountClientLink(clientInternalId);
|
delegate.setServiceAccountClientLink(clientInternalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SingleUserCredentialManager getUserCredentialManager() {
|
||||||
|
return delegate.getUserCredentialManager();
|
||||||
|
}
|
||||||
|
|
||||||
public UserModel getDelegate() {
|
public UserModel getDelegate() {
|
||||||
return delegate;
|
return delegate;
|
||||||
}
|
}
|
||||||
|
@ -259,4 +265,5 @@ public class UserModelDelegate implements UserModel.Streams {
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return getDelegate().getId().hashCode();
|
return getDelegate().getId().hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.storage.user;
|
package org.keycloak.storage.user;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
@ -59,6 +61,11 @@ public interface UserLookupProvider {
|
||||||
default UserModel getUserByUsername(RealmModel realm, String username) {
|
default UserModel getUserByUsername(RealmModel realm, String username) {
|
||||||
return getUserByUsername(username, realm);
|
return getUserByUsername(username, realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default CredentialValidationOutput getUserByCredential(RealmModel realm, CredentialInput input) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link #getUserByUsername(RealmModel, String) getUserByUsername} instead.
|
* @deprecated Use {@link #getUserByUsername(RealmModel, String) getUserByUsername} instead.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -86,7 +86,7 @@ public class SpnegoAuthenticator extends AbstractUsernameFormAuthenticator imple
|
||||||
String spnegoToken = tokens[1];
|
String spnegoToken = tokens[1];
|
||||||
UserCredentialModel spnegoCredential = UserCredentialModel.kerberos(spnegoToken);
|
UserCredentialModel spnegoCredential = UserCredentialModel.kerberos(spnegoToken);
|
||||||
|
|
||||||
CredentialValidationOutput output = context.getSession().userCredentialManager().authenticate(context.getSession(), context.getRealm(), spnegoCredential);
|
CredentialValidationOutput output = context.getSession().users().getUserByCredential(context.getRealm(), spnegoCredential);
|
||||||
|
|
||||||
if (output == null) {
|
if (output == null) {
|
||||||
logger.warn("Received kerberos token, but there is no user storage provider that handles kerberos credentials.");
|
logger.warn("Received kerberos token, but there is no user storage provider that handles kerberos credentials.");
|
||||||
|
|
|
@ -19,16 +19,13 @@ package org.keycloak.credential;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.credential.hash.PasswordHashProvider;
|
import org.keycloak.credential.hash.PasswordHashProvider;
|
||||||
import org.keycloak.models.ModelException;
|
|
||||||
import org.keycloak.models.credential.PasswordCredentialModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.models.PasswordPolicy;
|
import org.keycloak.models.PasswordPolicy;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.cache.CachedUserModel;
|
import org.keycloak.models.credential.PasswordCredentialModel;
|
||||||
import org.keycloak.models.cache.OnUserCache;
|
|
||||||
import org.keycloak.models.cache.UserCache;
|
|
||||||
import org.keycloak.policy.PasswordPolicyManagerProvider;
|
import org.keycloak.policy.PasswordPolicyManagerProvider;
|
||||||
import org.keycloak.policy.PolicyError;
|
import org.keycloak.policy.PolicyError;
|
||||||
|
|
||||||
|
@ -41,9 +38,8 @@ import java.util.stream.Stream;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class PasswordCredentialProvider implements CredentialProvider<PasswordCredentialModel>, CredentialInputUpdater.Streams,
|
public class PasswordCredentialProvider implements CredentialProvider<PasswordCredentialModel>, CredentialInputUpdater.Streams,
|
||||||
CredentialInputValidator, OnUserCache {
|
CredentialInputValidator {
|
||||||
|
|
||||||
public static final String PASSWORD_CACHE_KEY = PasswordCredentialProvider.class.getName() + "." + PasswordCredentialModel.TYPE;
|
|
||||||
private static final Logger logger = Logger.getLogger(PasswordCredentialProvider.class);
|
private static final Logger logger = Logger.getLogger(PasswordCredentialProvider.class);
|
||||||
|
|
||||||
protected final KeycloakSession session;
|
protected final KeycloakSession session;
|
||||||
|
@ -57,18 +53,8 @@ public class PasswordCredentialProvider implements CredentialProvider<PasswordCr
|
||||||
}
|
}
|
||||||
|
|
||||||
public PasswordCredentialModel getPassword(RealmModel realm, UserModel user) {
|
public PasswordCredentialModel getPassword(RealmModel realm, UserModel user) {
|
||||||
List<CredentialModel> passwords = null;
|
List<CredentialModel> passwords = getCredentialStore().getStoredCredentialsByTypeStream(realm, user, getType()).collect(Collectors.toList());
|
||||||
if (user instanceof CachedUserModel && !((CachedUserModel) user).isMarkedForEviction()) {
|
if (passwords.isEmpty()) return null;
|
||||||
CachedUserModel cached = (CachedUserModel) user;
|
|
||||||
passwords = (List<CredentialModel>) cached.getCachedWith().get(PASSWORD_CACHE_KEY);
|
|
||||||
|
|
||||||
}
|
|
||||||
// if the model was marked for eviction while passwords were initialized, override it from credentialStore
|
|
||||||
if (!(user instanceof CachedUserModel) || ((CachedUserModel) user).isMarkedForEviction()) {
|
|
||||||
passwords = getCredentialStore().getStoredCredentialsByTypeStream(realm, user, getType()).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
if (passwords == null || passwords.isEmpty()) return null;
|
|
||||||
|
|
||||||
return PasswordCredentialModel.createFromCredentialModel(passwords.get(0));
|
return PasswordCredentialModel.createFromCredentialModel(passwords.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,10 +109,6 @@ public class PasswordCredentialProvider implements CredentialProvider<PasswordCr
|
||||||
.collect(Collectors.toList())
|
.collect(Collectors.toList())
|
||||||
.forEach(p -> getCredentialStore().removeStoredCredential(realm, user, p.getId()));
|
.forEach(p -> getCredentialStore().removeStoredCredential(realm, user, p.getId()));
|
||||||
|
|
||||||
UserCache userCache = session.userCache();
|
|
||||||
if (userCache != null) {
|
|
||||||
userCache.evict(realm, user);
|
|
||||||
}
|
|
||||||
return createdCredential;
|
return createdCredential;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,59 +132,6 @@ public class PasswordCredentialProvider implements CredentialProvider<PasswordCr
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*@Override
|
|
||||||
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
|
|
||||||
if (!supportsCredentialType(input.getType())) return false;
|
|
||||||
|
|
||||||
if (!(input instanceof UserCredentialModel)) {
|
|
||||||
logger.debug("Expected instance of UserCredentialModel for CredentialInput");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
UserCredentialModel cred = (UserCredentialModel)input;
|
|
||||||
PasswordPolicy policy = realm.getPasswordPolicy();
|
|
||||||
|
|
||||||
PolicyError error = session.getProvider(PasswordPolicyManagerProvider.class).validate(realm, user, cred.getValue());
|
|
||||||
if (error != null) throw new ModelException(error.getMessage(), error.getParameters());
|
|
||||||
|
|
||||||
|
|
||||||
PasswordHashProvider hash = getHashProvider(policy);
|
|
||||||
if (hash == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
CredentialModel oldPassword = getPassword(realm, user);
|
|
||||||
|
|
||||||
expirePassword(realm, user, policy);
|
|
||||||
CredentialModel newPassword = new CredentialModel();
|
|
||||||
newPassword.setType(CredentialModel.PASSWORD);
|
|
||||||
long createdDate = Time.currentTimeMillis();
|
|
||||||
newPassword.setCreatedDate(createdDate);
|
|
||||||
hash.encode(cred.getValue(), policy.getHashIterations(), newPassword);
|
|
||||||
getCredentialStore().createCredential(realm, user, newPassword);
|
|
||||||
UserCache userCache = session.userCache();
|
|
||||||
if (userCache != null) {
|
|
||||||
userCache.evict(realm, user);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/*@Override
|
|
||||||
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
|
|
||||||
if (!supportsCredentialType(credentialType)) return;
|
|
||||||
PasswordPolicy policy = realm.getPasswordPolicy();
|
|
||||||
expirePassword(realm, user, policy);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
|
|
||||||
if (!getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.PASSWORD).isEmpty()) {
|
|
||||||
Set<String> set = new HashSet<>();
|
|
||||||
set.add(CredentialModel.PASSWORD);
|
|
||||||
return set;
|
|
||||||
} else {
|
|
||||||
return Collections.EMPTY_SET;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsCredentialType(String credentialType) {
|
public boolean supportsCredentialType(String credentialType) {
|
||||||
return credentialType.equals(getType());
|
return credentialType.equals(getType());
|
||||||
|
@ -241,7 +170,7 @@ public class PasswordCredentialProvider implements CredentialProvider<PasswordCr
|
||||||
}
|
}
|
||||||
PasswordCredentialModel password = getPassword(realm, user);
|
PasswordCredentialModel password = getPassword(realm, user);
|
||||||
if (password == null) {
|
if (password == null) {
|
||||||
logger.debugv("No password cached or stored for user {0} ", user.getUsername());
|
logger.debugv("No password stored for user {0} ", user.getUsername());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, password.getPasswordCredentialData().getAlgorithm());
|
PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, password.getPasswordCredentialData().getAlgorithm());
|
||||||
|
@ -271,21 +200,9 @@ public class PasswordCredentialProvider implements CredentialProvider<PasswordCr
|
||||||
newPassword.setUserLabel(password.getUserLabel());
|
newPassword.setUserLabel(password.getUserLabel());
|
||||||
getCredentialStore().updateCredential(realm, user, newPassword);
|
getCredentialStore().updateCredential(realm, user, newPassword);
|
||||||
|
|
||||||
UserCache userCache = session.userCache();
|
|
||||||
if (userCache != null) {
|
|
||||||
userCache.evict(realm, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
|
|
||||||
List<CredentialModel> passwords = getCredentialStore().getStoredCredentialsByTypeStream(realm, user, getType())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
user.getCachedWith().put(PASSWORD_CACHE_KEY, passwords);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return PasswordCredentialModel.TYPE;
|
return PasswordCredentialModel.TYPE;
|
||||||
|
|
|
@ -24,15 +24,10 @@ import org.keycloak.models.UserCredentialManager;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.cache.CachedUserModel;
|
import org.keycloak.models.cache.CachedUserModel;
|
||||||
import org.keycloak.models.cache.OnUserCache;
|
import org.keycloak.models.cache.OnUserCache;
|
||||||
import org.keycloak.models.cache.UserCache;
|
import org.keycloak.storage.DatastoreProvider;
|
||||||
import org.keycloak.provider.Provider;
|
|
||||||
import org.keycloak.provider.ProviderFactory;
|
|
||||||
import org.keycloak.storage.AbstractStorageManager;
|
|
||||||
import org.keycloak.storage.CacheableStorageProviderModel;
|
|
||||||
import org.keycloak.storage.StorageId;
|
import org.keycloak.storage.StorageId;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
@ -41,131 +36,88 @@ 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 UserCredentialStoreManager extends AbstractStorageManager<Provider, CacheableStorageProviderModel>
|
public class UserCredentialStoreManager
|
||||||
implements UserCredentialManager.Streams, OnUserCache {
|
implements UserCredentialManager.Streams, OnUserCache {
|
||||||
|
|
||||||
|
private final KeycloakSession session;
|
||||||
|
|
||||||
public UserCredentialStoreManager(KeycloakSession session) {
|
public UserCredentialStoreManager(KeycloakSession session) {
|
||||||
super(session, ProviderFactory.class, Provider.class, componentModel -> null, "user");
|
this.session = session;
|
||||||
}
|
|
||||||
|
|
||||||
protected UserCredentialStore getStoreForUser(UserModel user) {
|
|
||||||
if (StorageId.isLocalStorage(user)) {
|
|
||||||
return (UserCredentialStore) session.userLocalStorage();
|
|
||||||
} else {
|
|
||||||
return (UserCredentialStore) session.userFederatedStorage();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
|
public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
|
||||||
throwExceptionIfInvalidUser(user);
|
user.getUserCredentialManager().updateStoredCredential(cred);
|
||||||
getStoreForUser(user).updateCredential(realm, user, cred);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
|
public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
|
||||||
throwExceptionIfInvalidUser(user);
|
return user.getUserCredentialManager().createStoredCredential(cred);
|
||||||
return getStoreForUser(user).createCredential(realm, user, cred);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
|
public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
|
||||||
throwExceptionIfInvalidUser(user);
|
return user.getUserCredentialManager().removeStoredCredentialById(id);
|
||||||
boolean removalResult = getStoreForUser(user).removeStoredCredential(realm, user, id);
|
|
||||||
UserCache userCache = session.userCache();
|
|
||||||
if (userCache != null) {
|
|
||||||
userCache.evict(realm, user);
|
|
||||||
}
|
|
||||||
return removalResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
|
public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
|
||||||
return getStoreForUser(user).getStoredCredentialById(realm, user, id);
|
return user.getUserCredentialManager().getStoredCredentialById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public Stream<CredentialModel> getStoredCredentialsStream(RealmModel realm, UserModel user) {
|
public Stream<CredentialModel> getStoredCredentialsStream(RealmModel realm, UserModel user) {
|
||||||
return getStoreForUser(user).getStoredCredentialsStream(realm, user);
|
return user.getUserCredentialManager().getStoredCredentialsStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public Stream<CredentialModel> getStoredCredentialsByTypeStream(RealmModel realm, UserModel user, String type) {
|
public Stream<CredentialModel> getStoredCredentialsByTypeStream(RealmModel realm, UserModel user, String type) {
|
||||||
return getStoreForUser(user).getStoredCredentialsByTypeStream(realm, user, type);
|
return user.getUserCredentialManager().getStoredCredentialsByTypeStream(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
|
public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
|
||||||
return getStoreForUser(user).getStoredCredentialByNameAndType(realm, user, name, type);
|
return user.getUserCredentialManager().getStoredCredentialByNameAndType(name, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public boolean moveCredentialTo(RealmModel realm, UserModel user, String id, String newPreviousCredentialId){
|
public boolean moveCredentialTo(RealmModel realm, UserModel user, String id, String newPreviousCredentialId){
|
||||||
throwExceptionIfInvalidUser(user);
|
return user.getUserCredentialManager().moveStoredCredentialTo(id, newPreviousCredentialId);
|
||||||
return getStoreForUser(user).moveCredentialTo(realm, user, id, newPreviousCredentialId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public boolean isValid(RealmModel realm, UserModel user, CredentialInput... inputs) {
|
public boolean isValid(RealmModel realm, UserModel user, CredentialInput... inputs) {
|
||||||
return isValid(realm, user, Arrays.asList(inputs));
|
return isValid(realm, user, Arrays.asList(inputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public CredentialModel createCredentialThroughProvider(RealmModel realm, UserModel user, CredentialModel model){
|
public CredentialModel createCredentialThroughProvider(RealmModel realm, UserModel user, CredentialModel model){
|
||||||
throwExceptionIfInvalidUser(user);
|
return user.getUserCredentialManager().createCredentialThroughProvider(model);
|
||||||
return session.getKeycloakSessionFactory()
|
|
||||||
.getProviderFactoriesStream(CredentialProvider.class)
|
|
||||||
.map(f -> session.getProvider(CredentialProvider.class, f.getId()))
|
|
||||||
.filter(provider -> Objects.equals(provider.getType(), model.getType()))
|
|
||||||
.map(cp -> cp.createCredential(realm, user, cp.getCredentialFromModel(model)))
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public void updateCredentialLabel(RealmModel realm, UserModel user, String credentialId, String userLabel){
|
public void updateCredentialLabel(RealmModel realm, UserModel user, String credentialId, String userLabel){
|
||||||
throwExceptionIfInvalidUser(user);
|
user.getUserCredentialManager().updateCredentialLabel(credentialId, userLabel);
|
||||||
CredentialModel credential = getStoredCredentialById(realm, user, credentialId);
|
|
||||||
credential.setUserLabel(userLabel);
|
|
||||||
getStoreForUser(user).updateCredential(realm, user, credential);
|
|
||||||
UserCache userCache = session.userCache();
|
|
||||||
if (userCache != null) {
|
|
||||||
userCache.evict(realm, user);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public boolean isValid(RealmModel realm, UserModel user, List<CredentialInput> inputs) {
|
public boolean isValid(RealmModel realm, UserModel user, List<CredentialInput> inputs) {
|
||||||
if (!isValid(user)) {
|
return user.getUserCredentialManager().isValid(inputs);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<CredentialInput> toValidate = new LinkedList<>(inputs);
|
|
||||||
String providerId = StorageId.isLocalStorage(user) ? user.getFederationLink() : StorageId.resolveProviderId(user);
|
|
||||||
if (providerId != null) {
|
|
||||||
/*
|
|
||||||
UserStorageProviderModel model = getStorageProviderModel(realm, providerId);
|
|
||||||
if (model == null || !model.isEnabled()) return false;
|
|
||||||
|
|
||||||
CredentialInputValidator validator = getStorageProviderInstance(model, CredentialInputValidator.class);
|
|
||||||
if (validator != null) {
|
|
||||||
validate(realm, user, toValidate, validator);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toValidate.isEmpty()) return true;
|
|
||||||
|
|
||||||
getCredentialProviders(session, CredentialInputValidator.class)
|
|
||||||
.forEach(validator -> validate(realm, user, toValidate, validator));
|
|
||||||
|
|
||||||
return toValidate.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validate(RealmModel realm, UserModel user, List<CredentialInput> toValidate, CredentialInputValidator validator) {
|
|
||||||
toValidate.removeIf(input -> validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, then inline
|
||||||
public static <T> Stream<T> getCredentialProviders(KeycloakSession session, Class<T> type) {
|
public static <T> Stream<T> getCredentialProviders(KeycloakSession session, Class<T> type) {
|
||||||
return session.getKeycloakSessionFactory().getProviderFactoriesStream(CredentialProvider.class)
|
return session.getKeycloakSessionFactory().getProviderFactoriesStream(CredentialProvider.class)
|
||||||
.filter(f -> Types.supports(type, f, CredentialProviderFactory.class))
|
.filter(f -> Types.supports(type, f, CredentialProviderFactory.class))
|
||||||
|
@ -173,134 +125,53 @@ public class UserCredentialStoreManager extends AbstractStorageManager<Provider,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
|
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
|
||||||
String providerId = StorageId.isLocalStorage(user) ? user.getFederationLink() : StorageId.resolveProviderId(user);
|
return user.getUserCredentialManager().updateCredential(input);
|
||||||
if (!StorageId.isLocalStorage(user)) throwExceptionIfInvalidUser(user);
|
|
||||||
|
|
||||||
if (providerId != null) {
|
|
||||||
/*
|
|
||||||
UserStorageProviderModel model = getStorageProviderModel(realm, providerId);
|
|
||||||
if (model == null || !model.isEnabled()) return false;
|
|
||||||
|
|
||||||
CredentialInputUpdater updater = getStorageProviderInstance(model, CredentialInputUpdater.class);
|
|
||||||
if (updater != null && updater.supportsCredentialType(input.getType())) {
|
|
||||||
if (updater.updateCredential(realm, user, input)) return true;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
return getCredentialProviders(session, CredentialInputUpdater.class)
|
|
||||||
.filter(updater -> updater.supportsCredentialType(input.getType()))
|
|
||||||
.anyMatch(updater -> updater.updateCredential(realm, user, input));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
|
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
|
||||||
String providerId = StorageId.isLocalStorage(user) ? user.getFederationLink() : StorageId.resolveProviderId(user);
|
user.getUserCredentialManager().disableCredentialType(credentialType);
|
||||||
if (!StorageId.isLocalStorage(user)) throwExceptionIfInvalidUser(user);
|
|
||||||
if (providerId != null) {
|
|
||||||
/*
|
|
||||||
UserStorageProviderModel model = getStorageProviderModel(realm, providerId);
|
|
||||||
if (model == null || !model.isEnabled()) return;
|
|
||||||
|
|
||||||
CredentialInputUpdater updater = getStorageProviderInstance(model, CredentialInputUpdater.class);
|
|
||||||
if (updater.supportsCredentialType(credentialType)) {
|
|
||||||
updater.disableCredentialType(realm, user, credentialType);
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
getCredentialProviders(session, CredentialInputUpdater.class)
|
|
||||||
.filter(updater -> updater.supportsCredentialType(credentialType))
|
|
||||||
.forEach(updater -> updater.disableCredentialType(realm, user, credentialType));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public Stream<String> getDisableableCredentialTypesStream(RealmModel realm, UserModel user) {
|
public Stream<String> getDisableableCredentialTypesStream(RealmModel realm, UserModel user) {
|
||||||
Stream<String> types = Stream.empty();
|
return user.getUserCredentialManager().getDisableableCredentialTypesStream();
|
||||||
String providerId = StorageId.isLocalStorage(user) ? user.getFederationLink() : StorageId.resolveProviderId(user);
|
|
||||||
if (providerId != null) {
|
|
||||||
/*
|
|
||||||
UserStorageProviderModel model = getStorageProviderModel(realm, providerId);
|
|
||||||
if (model == null || !model.isEnabled()) return types;
|
|
||||||
|
|
||||||
CredentialInputUpdater updater = getStorageProviderInstance(model, CredentialInputUpdater.class);
|
|
||||||
if (updater != null) types = updater.getDisableableCredentialTypesStream(realm, user);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
return Stream.concat(types, getCredentialProviders(session, CredentialInputUpdater.class)
|
|
||||||
.flatMap(updater -> updater.getDisableableCredentialTypesStream(realm, user)))
|
|
||||||
.distinct();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
|
public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
|
||||||
UserStorageCredentialConfigured userStorageConfigured = isConfiguredThroughUserStorage(realm, user, type);
|
return user.getUserCredentialManager().isConfiguredFor(type);
|
||||||
|
|
||||||
// Check if we can rely just on userStorage to decide if credential is configured for the user or not
|
|
||||||
switch (userStorageConfigured) {
|
|
||||||
case CONFIGURED: return true;
|
|
||||||
case USER_STORAGE_DISABLED: return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check locally as a fallback
|
|
||||||
return isConfiguredLocally(realm, user, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private enum UserStorageCredentialConfigured {
|
|
||||||
CONFIGURED,
|
|
||||||
USER_STORAGE_DISABLED,
|
|
||||||
NOT_CONFIGURED
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private UserStorageCredentialConfigured isConfiguredThroughUserStorage(RealmModel realm, UserModel user, String type) {
|
|
||||||
String providerId = StorageId.isLocalStorage(user) ? user.getFederationLink() : StorageId.resolveProviderId(user);
|
|
||||||
if (providerId != null) {
|
|
||||||
/*
|
|
||||||
UserStorageProviderModel model = getStorageProviderModel(realm, providerId);
|
|
||||||
if (model == null || !model.isEnabled()) return UserStorageCredentialConfigured.USER_STORAGE_DISABLED;
|
|
||||||
|
|
||||||
CredentialInputValidator validator = getStorageProviderInstance(model, CredentialInputValidator.class);
|
|
||||||
if (validator != null && validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) {
|
|
||||||
return UserStorageCredentialConfigured.CONFIGURED;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
return UserStorageCredentialConfigured.NOT_CONFIGURED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public boolean isConfiguredLocally(RealmModel realm, UserModel user, String type) {
|
public boolean isConfiguredLocally(RealmModel realm, UserModel user, String type) {
|
||||||
return getCredentialProviders(session, CredentialInputValidator.class)
|
// TODO: no longer used, can be removed
|
||||||
.anyMatch(validator -> validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type));
|
return user.getUserCredentialManager().isConfiguredLocally(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public CredentialValidationOutput authenticate(KeycloakSession session, RealmModel realm, CredentialInput input) {
|
public CredentialValidationOutput authenticate(KeycloakSession session, RealmModel realm, CredentialInput input) {
|
||||||
Stream<CredentialAuthentication> credentialAuthenticationStream = getEnabledStorageProviders(realm, CredentialAuthentication.class);
|
// TODO: no longer used, can be removed
|
||||||
credentialAuthenticationStream = Stream.concat(credentialAuthenticationStream,
|
return session.users().getUserByCredential(realm, input);
|
||||||
getCredentialProviders(session, CredentialAuthentication.class));
|
|
||||||
|
|
||||||
return credentialAuthenticationStream
|
|
||||||
.filter(credentialAuthentication -> credentialAuthentication.supportsCredentialAuthenticationFor(input.getType()))
|
|
||||||
.map(credentialAuthentication -> credentialAuthentication.authenticate(realm, input))
|
|
||||||
.findFirst().orElse(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, then remove it together with the OnUserCache class
|
||||||
public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
|
public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
|
||||||
getCredentialProviders(session, OnUserCache.class).forEach(validator -> validator.onCache(realm, user, delegate));
|
getCredentialProviders(session, OnUserCache.class).forEach(validator -> validator.onCache(realm, user, delegate));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead
|
||||||
public Stream<String> getConfiguredUserStorageCredentialTypesStream(RealmModel realm, UserModel user) {
|
public Stream<String> getConfiguredUserStorageCredentialTypesStream(RealmModel realm, UserModel user) {
|
||||||
return getCredentialProviders(session, CredentialProvider.class).map(CredentialProvider::getType)
|
return user.getUserCredentialManager().getConfiguredUserStorageCredentialTypesStream(user);
|
||||||
.filter(credentialType -> UserStorageCredentialConfigured.CONFIGURED == isConfiguredThroughUserStorage(realm, user, credentialType));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -308,14 +179,4 @@ public class UserCredentialStoreManager extends AbstractStorageManager<Provider,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValid(UserModel user) {
|
|
||||||
return user != null && user.getServiceAccountClientLink() == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void throwExceptionIfInvalidUser(UserModel user) {
|
|
||||||
if (user == null || isValid(user)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new RuntimeException("You can not manage credentials for this user");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,11 +166,14 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // put deprecated on the interface
|
||||||
public UserProvider userLocalStorage() {
|
public UserProvider userLocalStorage() {
|
||||||
|
// TODO: if we would call users() here, we could get the cache in legacy mode instead and would then loop
|
||||||
return getProvider(UserProvider.class);
|
return getProvider(UserProvider.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated // put deprecated on the interface (all *LocalStorage, all *Managers)
|
||||||
public RealmProvider realmLocalStorage() {
|
public RealmProvider realmLocalStorage() {
|
||||||
return realms();
|
return realms();
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,9 +73,9 @@ import org.keycloak.services.resources.account.AccountFormService;
|
||||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
import org.keycloak.storage.ReadOnlyException;
|
import org.keycloak.storage.ReadOnlyException;
|
||||||
import org.keycloak.userprofile.ValidationException;
|
|
||||||
import org.keycloak.userprofile.UserProfile;
|
import org.keycloak.userprofile.UserProfile;
|
||||||
import org.keycloak.userprofile.UserProfileProvider;
|
import org.keycloak.userprofile.UserProfileProvider;
|
||||||
|
import org.keycloak.userprofile.ValidationException;
|
||||||
import org.keycloak.utils.ProfileHelper;
|
import org.keycloak.utils.ProfileHelper;
|
||||||
|
|
||||||
import javax.ws.rs.BadRequestException;
|
import javax.ws.rs.BadRequestException;
|
||||||
|
@ -97,7 +97,6 @@ import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.Response.Status;
|
import javax.ws.rs.core.Response.Status;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -219,7 +218,7 @@ public class UserResource {
|
||||||
errors.add(new ErrorRepresentation(error.getFormattedMessage(new AdminMessageFormatter(session, user))));
|
errors.add(new ErrorRepresentation(error.getFormattedMessage(new AdminMessageFormatter(session, user))));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ErrorResponse.errors(errors, Response.Status.BAD_REQUEST);
|
return ErrorResponse.errors(errors, Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -640,8 +639,8 @@ public class UserResource {
|
||||||
public Stream<CredentialRepresentation> credentials(){
|
public Stream<CredentialRepresentation> credentials(){
|
||||||
auth.users().requireManage(user);
|
auth.users().requireManage(user);
|
||||||
return session.userCredentialManager().getStoredCredentialsStream(realm, user)
|
return session.userCredentialManager().getStoredCredentialsStream(realm, user)
|
||||||
.peek(model -> model.setSecretData(null))
|
.map(ModelToRepresentation::toRepresentation)
|
||||||
.map(ModelToRepresentation::toRepresentation);
|
.peek(credentialRepresentation -> credentialRepresentation.setSecretData(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -686,7 +685,7 @@ public class UserResource {
|
||||||
* Update a credential label for a user
|
* Update a credential label for a user
|
||||||
*/
|
*/
|
||||||
@PUT
|
@PUT
|
||||||
@Consumes(javax.ws.rs.core.MediaType.TEXT_PLAIN)
|
@Consumes(MediaType.TEXT_PLAIN)
|
||||||
@Path("credentials/{credentialId}/userLabel")
|
@Path("credentials/{credentialId}/userLabel")
|
||||||
public void setCredentialUserLabel(final @PathParam("credentialId") String credentialId, String userLabel) {
|
public void setCredentialUserLabel(final @PathParam("credentialId") String credentialId, String userLabel) {
|
||||||
auth.users().requireManage(user);
|
auth.users().requireManage(user);
|
||||||
|
|
|
@ -28,3 +28,4 @@ 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
|
org.keycloak.services.resources.admin.ext.AdminRealmResourceSpi
|
||||||
|
org.keycloak.credential.SingleUserCredentialManagerSpi
|
||||||
|
|
|
@ -33,12 +33,14 @@ import org.keycloak.credential.CredentialInput;
|
||||||
import org.keycloak.credential.CredentialInputUpdater;
|
import org.keycloak.credential.CredentialInputUpdater;
|
||||||
import org.keycloak.credential.CredentialInputValidator;
|
import org.keycloak.credential.CredentialInputValidator;
|
||||||
import org.keycloak.credential.CredentialModel;
|
import org.keycloak.credential.CredentialModel;
|
||||||
|
import org.keycloak.credential.LegacySingleUserCredentialManager;
|
||||||
import org.keycloak.credential.hash.PasswordHashProvider;
|
import org.keycloak.credential.hash.PasswordHashProvider;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.OTPPolicy;
|
import org.keycloak.models.OTPPolicy;
|
||||||
import org.keycloak.models.PasswordPolicy;
|
import org.keycloak.models.PasswordPolicy;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.SingleUserCredentialManager;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.cache.UserCache;
|
import org.keycloak.models.cache.UserCache;
|
||||||
|
@ -101,6 +103,10 @@ public class BackwardsCompatibilityUserStorage implements UserLookupProvider, Us
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SingleUserCredentialManager getUserCredentialManager() {
|
||||||
|
return new LegacySingleUserCredentialManager(session, realm, this);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,10 @@ import org.keycloak.credential.CredentialInput;
|
||||||
import org.keycloak.credential.CredentialInputUpdater;
|
import org.keycloak.credential.CredentialInputUpdater;
|
||||||
import org.keycloak.credential.CredentialInputValidator;
|
import org.keycloak.credential.CredentialInputValidator;
|
||||||
import org.keycloak.credential.CredentialModel;
|
import org.keycloak.credential.CredentialModel;
|
||||||
|
import org.keycloak.credential.LegacySingleUserCredentialManager;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.SingleUserCredentialManager;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.credential.PasswordCredentialModel;
|
import org.keycloak.models.credential.PasswordCredentialModel;
|
||||||
import org.keycloak.storage.StorageId;
|
import org.keycloak.storage.StorageId;
|
||||||
|
@ -162,7 +164,11 @@ public class PassThroughFederatedUserStorageProvider implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUsername(String username) {
|
public void setUsername(String username) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SingleUserCredentialManager getUserCredentialManager() {
|
||||||
|
return new LegacySingleUserCredentialManager(session, realm, this);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,20 +16,23 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.federation;
|
package org.keycloak.testsuite.federation;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.credential.CredentialInput;
|
import org.keycloak.credential.CredentialInput;
|
||||||
import org.keycloak.credential.CredentialInputUpdater;
|
import org.keycloak.credential.CredentialInputUpdater;
|
||||||
import org.keycloak.credential.CredentialInputValidator;
|
import org.keycloak.credential.CredentialInputValidator;
|
||||||
|
import org.keycloak.credential.LegacySingleUserCredentialManager;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.SingleUserCredentialManager;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.credential.PasswordCredentialModel;
|
||||||
import org.keycloak.models.credential.PasswordUserCredentialModel;
|
import org.keycloak.models.credential.PasswordUserCredentialModel;
|
||||||
import org.keycloak.storage.ReadOnlyException;
|
import org.keycloak.storage.ReadOnlyException;
|
||||||
import org.keycloak.models.credential.PasswordCredentialModel;
|
|
||||||
import org.keycloak.storage.StorageId;
|
import org.keycloak.storage.StorageId;
|
||||||
import org.keycloak.storage.UserStorageProvider;
|
import org.keycloak.storage.UserStorageProvider;
|
||||||
import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
|
import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
|
||||||
|
@ -46,7 +49,7 @@ import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.ConcurrentSkipListSet;
|
import java.util.concurrent.ConcurrentSkipListSet;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import static org.keycloak.storage.UserStorageProviderModel.IMPORT_ENABLED;
|
import static org.keycloak.storage.UserStorageProviderModel.IMPORT_ENABLED;
|
||||||
import static org.keycloak.utils.StreamsUtil.paginatedStream;
|
import static org.keycloak.utils.StreamsUtil.paginatedStream;
|
||||||
|
|
||||||
|
@ -81,9 +84,9 @@ public class UserMapStorage implements UserLookupProvider.Streams, UserStoragePr
|
||||||
|
|
||||||
String editModeString = model.getConfig().getFirst(LDAPConstants.EDIT_MODE);
|
String editModeString = model.getConfig().getFirst(LDAPConstants.EDIT_MODE);
|
||||||
if (editModeString == null) {
|
if (editModeString == null) {
|
||||||
this.editMode = UserStorageProvider.EditMode.UNSYNCED;
|
this.editMode = EditMode.UNSYNCED;
|
||||||
} else {
|
} else {
|
||||||
this.editMode = UserStorageProvider.EditMode.valueOf(editModeString);
|
this.editMode = EditMode.valueOf(editModeString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,6 +143,11 @@ public class UserMapStorage implements UserLookupProvider.Streams, UserStoragePr
|
||||||
public String getFederationLink() {
|
public String getFederationLink() {
|
||||||
return model.getId();
|
return model.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SingleUserCredentialManager getUserCredentialManager() {
|
||||||
|
return new LegacySingleUserCredentialManager(session, realm, this);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +161,7 @@ public class UserMapStorage implements UserLookupProvider.Streams, UserStoragePr
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
|
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
|
||||||
if (editMode == UserStorageProvider.EditMode.READ_ONLY) {
|
if (editMode == EditMode.READ_ONLY) {
|
||||||
throw new ReadOnlyException("Federated storage is not writable");
|
throw new ReadOnlyException("Federated storage is not writable");
|
||||||
}
|
}
|
||||||
if (!(input instanceof UserCredentialModel)) {
|
if (!(input instanceof UserCredentialModel)) {
|
||||||
|
@ -215,7 +223,7 @@ public class UserMapStorage implements UserLookupProvider.Streams, UserStoragePr
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel addUser(RealmModel realm, String username) {
|
public UserModel addUser(RealmModel realm, String username) {
|
||||||
if (editMode == UserStorageProvider.EditMode.READ_ONLY) {
|
if (editMode == EditMode.READ_ONLY) {
|
||||||
throw new ReadOnlyException("Federated storage is not writable");
|
throw new ReadOnlyException("Federated storage is not writable");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +233,7 @@ public class UserMapStorage implements UserLookupProvider.Streams, UserStoragePr
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean removeUser(RealmModel realm, UserModel user) {
|
public boolean removeUser(RealmModel realm, UserModel user) {
|
||||||
if (editMode == UserStorageProvider.EditMode.READ_ONLY || editMode == UserStorageProvider.EditMode.UNSYNCED) {
|
if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) {
|
||||||
log.warnf("User '%s' can't be deleted in LDAP as editMode is '%s'. Deleting user just from Keycloak DB, but he will be re-imported from LDAP again once searched in Keycloak", user.getUsername(), editMode.toString());
|
log.warnf("User '%s' can't be deleted in LDAP as editMode is '%s'. Deleting user just from Keycloak DB, but he will be re-imported from LDAP again once searched in Keycloak", user.getUsername(), editMode.toString());
|
||||||
userPasswords.remove(translateUserName(user.getUsername()));
|
userPasswords.remove(translateUserName(user.getUsername()));
|
||||||
return true;
|
return true;
|
||||||
|
@ -235,7 +243,7 @@ public class UserMapStorage implements UserLookupProvider.Streams, UserStoragePr
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean removeUserByName(String userName) {
|
public boolean removeUserByName(String userName) {
|
||||||
if (editMode == UserStorageProvider.EditMode.READ_ONLY || editMode == UserStorageProvider.EditMode.UNSYNCED) {
|
if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) {
|
||||||
log.warnf("User '%s' can't be deleted in LDAP as editMode is '%s'. Deleting user just from Keycloak DB, but he will be re-imported from LDAP again once searched in Keycloak", userName, editMode.toString());
|
log.warnf("User '%s' can't be deleted in LDAP as editMode is '%s'. Deleting user just from Keycloak DB, but he will be re-imported from LDAP again once searched in Keycloak", userName, editMode.toString());
|
||||||
userPasswords.remove(translateUserName(userName));
|
userPasswords.remove(translateUserName(userName));
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -19,10 +19,12 @@ package org.keycloak.testsuite.federation;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.credential.CredentialInput;
|
import org.keycloak.credential.CredentialInput;
|
||||||
import org.keycloak.credential.CredentialInputValidator;
|
import org.keycloak.credential.CredentialInputValidator;
|
||||||
|
import org.keycloak.credential.LegacySingleUserCredentialManager;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.SingleUserCredentialManager;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.credential.PasswordCredentialModel;
|
import org.keycloak.models.credential.PasswordCredentialModel;
|
||||||
|
@ -132,6 +134,11 @@ public class UserPropertyFileStorage implements UserLookupProvider.Streams, User
|
||||||
public void setUsername(String username) {
|
public void setUsername(String username) {
|
||||||
throw new RuntimeException("Unsupported");
|
throw new RuntimeException("Unsupported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SingleUserCredentialManager getUserCredentialManager() {
|
||||||
|
return new LegacySingleUserCredentialManager(session, realm, this);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return new AbstractUserAdapter.Streams(session, realm, model) {
|
return new AbstractUserAdapter.Streams(session, realm, model) {
|
||||||
|
@ -139,6 +146,11 @@ public class UserPropertyFileStorage implements UserLookupProvider.Streams, User
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SingleUserCredentialManager getUserCredentialManager() {
|
||||||
|
return new LegacySingleUserCredentialManager(session, realm, this);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue