[KEYCLOAK-16232] Streamify the UserCredentialStore and UserCredentialManager interfaces

This commit is contained in:
Stefan Guilhen 2020-11-26 10:25:06 -03:00 committed by Hynek Mlnařík
parent 73d0bb34c4
commit edef93cd49
43 changed files with 447 additions and 303 deletions

View file

@ -75,7 +75,7 @@ public class SecretQuestionCredentialProvider implements CredentialProvider<Secr
@Override @Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) { public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
if (!supportsCredentialType(credentialType)) return false; if (!supportsCredentialType(credentialType)) return false;
return !getCredentialStore().getStoredCredentialsByType(realm, user, credentialType).isEmpty(); return getCredentialStore().getStoredCredentialsByTypeStream(realm, user, credentialType).count() > 0;
} }
@Override @Override

View file

@ -23,7 +23,6 @@ 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.CredentialModel;
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;
@ -41,10 +40,9 @@ import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.user.ImportedUserValidation; import org.keycloak.storage.user.ImportedUserValidation;
import org.keycloak.storage.user.UserLookupProvider; import org.keycloak.storage.user.UserLookupProvider;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.stream.Stream;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -52,7 +50,7 @@ import java.util.Set;
public class KerberosFederationProvider implements UserStorageProvider, public class KerberosFederationProvider implements UserStorageProvider,
UserLookupProvider, UserLookupProvider,
CredentialInputValidator, CredentialInputValidator,
CredentialInputUpdater, CredentialInputUpdater.Streams,
CredentialAuthentication, CredentialAuthentication,
ImportedUserValidation { ImportedUserValidation {
@ -146,8 +144,8 @@ public class KerberosFederationProvider implements UserStorageProvider,
} }
@Override @Override
public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) { public Stream<String> getDisableableCredentialTypesStream(RealmModel realm, UserModel user) {
return Collections.EMPTY_SET; return Stream.empty();
} }
@Override @Override

View file

@ -18,7 +18,6 @@
package org.keycloak.storage.ldap; package org.keycloak.storage.ldap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -88,7 +87,7 @@ import org.keycloak.storage.user.UserRegistrationProvider;
*/ */
public class LDAPStorageProvider implements UserStorageProvider, public class LDAPStorageProvider implements UserStorageProvider,
CredentialInputValidator, CredentialInputValidator,
CredentialInputUpdater, CredentialInputUpdater.Streams,
CredentialAuthentication, CredentialAuthentication,
UserLookupProvider, UserLookupProvider,
UserRegistrationProvider, UserRegistrationProvider,
@ -687,8 +686,8 @@ public class LDAPStorageProvider implements UserStorageProvider,
} }
@Override @Override
public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) { public Stream<String> getDisableableCredentialTypesStream(RealmModel realm, UserModel user) {
return Collections.EMPTY_SET; return Stream.empty();
} }
public Set<String> getSupportedCredentialTypes() { public Set<String> getSupportedCredentialTypes() {

View file

@ -21,7 +21,6 @@ import org.jboss.logging.Logger;
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.CredentialModel;
import org.keycloak.federation.sssd.api.Sssd; import org.keycloak.federation.sssd.api.Sssd;
import org.keycloak.federation.sssd.api.Sssd.User; import org.keycloak.federation.sssd.api.Sssd.User;
import org.keycloak.federation.sssd.impl.PAMAuthenticator; import org.keycloak.federation.sssd.impl.PAMAuthenticator;
@ -32,11 +31,10 @@ import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel; import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.user.ImportedUserValidation; import org.keycloak.storage.user.ImportedUserValidation;
import org.keycloak.storage.user.UserLookupProvider; import org.keycloak.storage.user.UserLookupProvider;
import sun.security.util.Password;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream;
/** /**
* SPI provider implementation to retrieve data from SSSD and authenticate * SPI provider implementation to retrieve data from SSSD and authenticate
@ -47,7 +45,7 @@ import java.util.Set;
*/ */
public class SSSDFederationProvider implements UserStorageProvider, public class SSSDFederationProvider implements UserStorageProvider,
UserLookupProvider, UserLookupProvider,
CredentialInputUpdater, CredentialInputUpdater.Streams,
CredentialInputValidator, CredentialInputValidator,
ImportedUserValidation { ImportedUserValidation {
@ -205,7 +203,7 @@ public class SSSDFederationProvider implements UserStorageProvider,
} }
@Override @Override
public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) { public Stream<String> getDisableableCredentialTypesStream(RealmModel realm, UserModel user) {
return Collections.EMPTY_SET; return Stream.empty();
} }
} }

View file

@ -30,16 +30,20 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.persistence.LockModeType; import javax.persistence.LockModeType;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.keycloak.utils.StreamsUtil.closing;
/** /**
* @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 JpaUserCredentialStore implements UserCredentialStore { public class JpaUserCredentialStore implements UserCredentialStore.Streams {
// Typical priority difference between 2 credentials // Typical priority difference between 2 credentials
public static final int PRIORITY_DIFFERENCE = 10; public static final int PRIORITY_DIFFERENCE = 10;
@ -106,31 +110,27 @@ public class JpaUserCredentialStore implements UserCredentialStore {
} }
@Override @Override
public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) { public Stream<CredentialModel> getStoredCredentialsStream(RealmModel realm, UserModel user) {
List<CredentialEntity> results = getStoredCredentialEntities(realm, user); return this.getStoredCredentialEntities(realm, user).map(this::toModel);
// list is ordered correctly by priority (lowest priority value first)
return results.stream().map(this::toModel).collect(Collectors.toList());
} }
private List<CredentialEntity> getStoredCredentialEntities(RealmModel realm, UserModel user) { private Stream<CredentialEntity> getStoredCredentialEntities(RealmModel realm, UserModel user) {
UserEntity userEntity = em.getReference(UserEntity.class, user.getId()); UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByUser", CredentialEntity.class) TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByUser", CredentialEntity.class)
.setParameter("user", userEntity); .setParameter("user", userEntity);
return query.getResultList(); return closing(query.getResultStream());
} }
@Override @Override
public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) { public Stream<CredentialModel> getStoredCredentialsByTypeStream(RealmModel realm, UserModel user, String type) {
return getStoredCredentials(realm, user).stream().filter(credential -> type.equals(credential.getType())).collect(Collectors.toList()); return getStoredCredentialsStream(realm, user).filter(credential -> Objects.equals(type, credential.getType()));
} }
@Override @Override
public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) { public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
List<CredentialModel> results = getStoredCredentials(realm, user).stream().filter(credential -> return getStoredCredentialsStream(realm, user).filter(credential ->
type.equals(credential.getType()) && name.equals(credential.getUserLabel())).collect(Collectors.toList()); Objects.equals(type, credential.getType()) && Objects.equals(name, credential.getUserLabel()))
if (results.isEmpty()) return null; .findFirst().orElse(null);
return results.get(0);
} }
@Override @Override
@ -151,7 +151,7 @@ public class JpaUserCredentialStore implements UserCredentialStore {
entity.setUser(userRef); entity.setUser(userRef);
//add in linkedlist to last position //add in linkedlist to last position
List<CredentialEntity> credentials = getStoredCredentialEntities(realm, user); List<CredentialEntity> credentials = getStoredCredentialEntities(realm, user).collect(Collectors.toList());
int priority = credentials.isEmpty() ? PRIORITY_DIFFERENCE : credentials.get(credentials.size() - 1).getPriority() + PRIORITY_DIFFERENCE; int priority = credentials.isEmpty() ? PRIORITY_DIFFERENCE : credentials.get(credentials.size() - 1).getPriority() + PRIORITY_DIFFERENCE;
entity.setPriority(priority); entity.setPriority(priority);
@ -165,14 +165,11 @@ public class JpaUserCredentialStore implements UserCredentialStore {
int currentPriority = entity.getPriority(); int currentPriority = entity.getPriority();
List<CredentialEntity> credentials = getStoredCredentialEntities(realm, user); this.getStoredCredentialEntities(realm, user).forEach(cred -> {
// Decrease priority of all credentials after our
for (CredentialEntity cred : credentials) {
if (cred.getPriority() > currentPriority) { if (cred.getPriority() > currentPriority) {
cred.setPriority(cred.getPriority() - PRIORITY_DIFFERENCE); cred.setPriority(cred.getPriority() - PRIORITY_DIFFERENCE);
} }
} });
em.remove(entity); em.remove(entity);
em.flush(); em.flush();
@ -182,11 +179,9 @@ public class JpaUserCredentialStore implements UserCredentialStore {
////Operations to handle the linked list of credentials ////Operations to handle the linked list of credentials
@Override @Override
public boolean moveCredentialTo(RealmModel realm, UserModel user, String id, String newPreviousCredentialId) { public boolean moveCredentialTo(RealmModel realm, UserModel user, String id, String newPreviousCredentialId) {
List<CredentialEntity> sortedCreds = getStoredCredentialEntities(realm, user);
// 1 - Create new list and move everything to it. // 1 - Create new list and move everything to it.
List<CredentialEntity> newList = new ArrayList<>(); List<CredentialEntity> newList = this.getStoredCredentialEntities(realm, user).collect(Collectors.toList());
newList.addAll(sortedCreds);
// 2 - Find indexes of our and newPrevious credential // 2 - Find indexes of our and newPrevious credential
int ourCredentialIndex = -1; int ourCredentialIndex = -1;

View file

@ -64,12 +64,10 @@ import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.persistence.LockModeType; import javax.persistence.LockModeType;
@ -82,7 +80,7 @@ import static org.keycloak.utils.StreamsUtil.closing;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
@SuppressWarnings("JpaQueryApiInspection") @SuppressWarnings("JpaQueryApiInspection")
public class JpaUserProvider implements UserProvider.Streams, UserCredentialStore { public class JpaUserProvider implements UserProvider.Streams, UserCredentialStore.Streams {
private static final String EMAIL = "email"; private static final String EMAIL = "email";
private static final String EMAIL_VERIFIED = "emailVerified"; private static final String EMAIL_VERIFIED = "emailVerified";
@ -1022,27 +1020,20 @@ public class JpaUserProvider implements UserProvider.Streams, UserCredentialStor
} }
@Override @Override
public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) { public Stream<CredentialModel> getStoredCredentialsStream(RealmModel realm, UserModel user) {
return credentialStore.getStoredCredentials(realm, user); return credentialStore.getStoredCredentialsStream(realm, user);
} }
@Override @Override
public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) { public Stream<CredentialModel> getStoredCredentialsByTypeStream(RealmModel realm, UserModel user, String type) {
List<CredentialEntity> results;
UserEntity userEntity = userInEntityManagerContext(user.getId()); UserEntity userEntity = userInEntityManagerContext(user.getId());
if (userEntity != null) { if (userEntity != null) {
// user already in persistence context, no need to execute a query // user already in persistence context, no need to execute a query
results = userEntity.getCredentials().stream().filter(it -> type.equals(it.getType())) return userEntity.getCredentials().stream().filter(it -> type.equals(it.getType()))
.sorted(Comparator.comparingInt(CredentialEntity::getPriority)) .sorted(Comparator.comparingInt(CredentialEntity::getPriority))
.collect(Collectors.toList()); .map(this::toModel);
List<CredentialModel> rtn = new LinkedList<>();
for (CredentialEntity entity : results) {
rtn.add(toModel(entity));
}
return rtn;
} else { } else {
return credentialStore.getStoredCredentialsByType(realm, user, type); return credentialStore.getStoredCredentialsByTypeStream(realm, user, type);
} }
} }

View file

@ -71,7 +71,7 @@ import static org.keycloak.utils.StreamsUtil.closing;
*/ */
public class JpaUserFederatedStorageProvider implements public class JpaUserFederatedStorageProvider implements
UserFederatedStorageProvider.Streams, UserFederatedStorageProvider.Streams,
UserCredentialStore { UserCredentialStore.Streams {
protected static final Logger logger = Logger.getLogger(JpaUserFederatedStorageProvider.class); protected static final Logger logger = Logger.getLogger(JpaUserFederatedStorageProvider.class);
@ -690,13 +690,13 @@ public class JpaUserFederatedStorageProvider implements
} }
@Override @Override
public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) { public Stream<CredentialModel> getStoredCredentialsStream(RealmModel realm, UserModel user) {
return getStoredCredentialsStream(realm, user.getId()).collect(Collectors.toList()); return getStoredCredentialsStream(realm, user.getId());
} }
@Override @Override
public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) { public Stream<CredentialModel> getStoredCredentialsByTypeStream(RealmModel realm, UserModel user, String type) {
return getStoredCredentialsByTypeStream(realm, user.getId(), type).collect(Collectors.toList()); return getStoredCredentialsByTypeStream(realm, user.getId(), type);
} }
@Override @Override

View file

@ -7,11 +7,13 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
public interface CredentialValidator<T extends CredentialProvider> { public interface CredentialValidator<T extends CredentialProvider> {
T getCredentialProvider(KeycloakSession session); T getCredentialProvider(KeycloakSession session);
default List<CredentialModel> getCredentials(KeycloakSession session, RealmModel realm, UserModel user) { default List<CredentialModel> getCredentials(KeycloakSession session, RealmModel realm, UserModel user) {
return session.userCredentialManager().getStoredCredentialsByType(realm, user, getCredentialProvider(session).getType()); return session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, getCredentialProvider(session).getType())
.collect(Collectors.toList());
} }
default String getType(KeycloakSession session) { default String getType(KeycloakSession session) {
return getCredentialProvider(session).getType(); return getCredentialProvider(session).getType();

View file

@ -188,7 +188,8 @@ public class ModelToRepresentation {
rep.setEnabled(user.isEnabled()); rep.setEnabled(user.isEnabled());
rep.setEmailVerified(user.isEmailVerified()); rep.setEmailVerified(user.isEmailVerified());
rep.setTotp(session.userCredentialManager().isConfiguredFor(realm, user, OTPCredentialModel.TYPE)); rep.setTotp(session.userCredentialManager().isConfiguredFor(realm, user, OTPCredentialModel.TYPE));
rep.setDisableableCredentialTypes(session.userCredentialManager().getDisableableCredentialTypes(realm, user)); rep.setDisableableCredentialTypes(session.userCredentialManager()
.getDisableableCredentialTypesStream(realm, user).collect(Collectors.toSet()));
rep.setFederationLink(user.getFederationLink()); rep.setFederationLink(user.getFederationLink());
rep.setNotBefore(session.users().getNotBeforeOfUser(realm, user)); rep.setNotBefore(session.users().getNotBeforeOfUser(realm, user));
rep.setRequiredActions(user.getRequiredActionsStream().collect(Collectors.toList())); rep.setRequiredActions(user.getRequiredActionsStream().collect(Collectors.toList()));

View file

@ -28,6 +28,7 @@ import org.keycloak.models.credential.PasswordCredentialModel;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -53,37 +54,36 @@ public class HistoryPasswordPolicyProvider implements PasswordPolicyProvider {
PasswordPolicy policy = session.getContext().getRealm().getPasswordPolicy(); PasswordPolicy policy = session.getContext().getRealm().getPasswordPolicy();
int passwordHistoryPolicyValue = policy.getPolicyConfig(PasswordPolicy.PASSWORD_HISTORY_ID); int passwordHistoryPolicyValue = policy.getPolicyConfig(PasswordPolicy.PASSWORD_HISTORY_ID);
if (passwordHistoryPolicyValue != -1) { if (passwordHistoryPolicyValue != -1) {
List<CredentialModel> storedPasswords = session.userCredentialManager().getStoredCredentialsByType(realm, user, PasswordCredentialModel.TYPE); if (session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, PasswordCredentialModel.TYPE)
for (CredentialModel cred : storedPasswords) { .map(PasswordCredentialModel::createFromCredentialModel)
PasswordCredentialModel passwordCredential = PasswordCredentialModel.createFromCredentialModel(cred); .anyMatch(passwordCredential -> {
PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, passwordCredential.getPasswordCredentialData().getAlgorithm()); PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class,
if (hash == null) continue; passwordCredential.getPasswordCredentialData().getAlgorithm());
if (hash.verify(password, passwordCredential)) { return hash != null && hash.verify(password, passwordCredential);
return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue); })) {
} return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue);
} }
if (passwordHistoryPolicyValue > 0) { if (passwordHistoryPolicyValue > 0) {
List<CredentialModel> passwordHistory = session.userCredentialManager().getStoredCredentialsByType(realm, user, PasswordCredentialModel.PASSWORD_HISTORY); if (this.getRecent(session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, PasswordCredentialModel.PASSWORD_HISTORY),
List<CredentialModel> recentPasswordHistory = getRecent(passwordHistory, passwordHistoryPolicyValue - 1); passwordHistoryPolicyValue - 1)
for (CredentialModel cred : recentPasswordHistory) { .map(PasswordCredentialModel::createFromCredentialModel)
PasswordCredentialModel passwordCredential = PasswordCredentialModel.createFromCredentialModel(cred); .anyMatch(passwordCredential -> {
PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, passwordCredential.getPasswordCredentialData().getAlgorithm()); PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class,
if (hash.verify(password, passwordCredential)) { passwordCredential.getPasswordCredentialData().getAlgorithm());
return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue); return hash.verify(password, passwordCredential);
} })) {
return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue);
} }
} }
} }
return null; return null;
} }
private List<CredentialModel> getRecent(List<CredentialModel> passwordHistory, int limit) { private Stream<CredentialModel> getRecent(Stream<CredentialModel> passwordHistory, int limit) {
return passwordHistory.stream() return passwordHistory
.sorted(CredentialModel.comparingByStartDateDesc()) .sorted(CredentialModel.comparingByStartDateDesc())
.limit(limit) .limit(limit);
.collect(Collectors.toList());
} }
@Override @Override

View file

@ -20,6 +20,8 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
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>
@ -37,6 +39,39 @@ public interface CredentialInputUpdater {
* @param realm * @param realm
* @param user * @param user
* @return * @return
* @deprecated Use {@link #getDisableableCredentialTypesStream(RealmModel, UserModel) getDisableableCredentialTypesStream}
* instead.
*/ */
@Deprecated
Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user); Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user);
/**
* Obtains the set of credential types that can be disabled via {@link #disableCredentialType(RealmModel, UserModel, String)
* disableCredentialType}.
*
* @param realm a reference to the realm.
* @param user the user whose credentials are being searched.
* @return a non-null {@link Stream} of credential types.
*/
default Stream<String> getDisableableCredentialTypesStream(RealmModel realm, UserModel user) {
Set<String> result = this.getDisableableCredentialTypes(realm, user);
return result != null ? result.stream() : Stream.empty();
}
/**
* The {@link CredentialInputUpdater.Streams} interface makes all collection-based methods in {@link CredentialInputUpdater}
* default by providing implementations that delegate to the {@link Stream}-based variants instead of the other way around.
* <p/>
* It allows for implementations to focus on the {@link Stream}-based approach for processing sets of data and benefit
* from the potential memory and performance optimizations of that approach.
*/
interface Streams extends CredentialInputUpdater {
@Override
default Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
return this.getDisableableCredentialTypesStream(realm, user).collect(Collectors.toSet());
}
@Override
Stream<String> getDisableableCredentialTypesStream(RealmModel realm, UserModel user);
}
} }

View file

@ -21,8 +21,6 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.provider.Provider; import org.keycloak.provider.Provider;
import java.util.List;
/** /**
* @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 $
@ -43,11 +41,9 @@ public interface CredentialProvider<T extends CredentialModel> extends Provider
T getCredentialFromModel(CredentialModel model); T getCredentialFromModel(CredentialModel model);
default T getDefaultCredential(KeycloakSession session, RealmModel realm, UserModel user) { default T getDefaultCredential(KeycloakSession session, RealmModel realm, UserModel user) {
List<CredentialModel> models = session.userCredentialManager().getStoredCredentialsByType(realm, user, getType()); CredentialModel model = session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, getType())
if (models.isEmpty()) { .findFirst().orElse(null);
return null; return model != null ? getCredentialFromModel(model) : null;
}
return getCredentialFromModel(models.get(0));
} }
CredentialTypeMetadata getCredentialTypeMetadata(CredentialTypeMetadataContext metadataContext); CredentialTypeMetadata getCredentialTypeMetadata(CredentialTypeMetadataContext metadataContext);

View file

@ -21,6 +21,8 @@ import org.keycloak.models.UserModel;
import org.keycloak.provider.Provider; import org.keycloak.provider.Provider;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
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>
@ -31,11 +33,72 @@ public interface UserCredentialStore extends Provider {
CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred); CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred);
boolean removeStoredCredential(RealmModel realm, UserModel user, String id); boolean removeStoredCredential(RealmModel realm, UserModel user, String id);
CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id); CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id);
/**
* @deprecated Use {@link #getStoredCredentialsStream(RealmModel, UserModel) getStoredCredentialsStream} instead.
*/
@Deprecated
List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user); List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user);
/**
* Obtains the stored credentials associated with the specified user.
*
* @param realm a reference to the realm.
* @param user the user whose credentials are being searched.
* @return a non-null {@link Stream} of credentials.
*/
default Stream<CredentialModel> getStoredCredentialsStream(RealmModel realm, UserModel user) {
List<CredentialModel> result = this.getStoredCredentials(realm, user);
return result != null ? result.stream() : Stream.empty();
}
/**
* @deprecated Use {@link #getStoredCredentialsByTypeStream(RealmModel, UserModel, String) getStoredCredentialsByTypeStream}
* instead.
*/
@Deprecated
List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type); List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type);
/**
* Obtains the stored credentials associated with the specified user that match the specified type.
*
* @param realm a reference to the realm.
* @param user the user whose credentials are being searched.
* @param type the type of credentials being searched.
* @return a non-null {@link Stream} of credentials.
*/
default Stream<CredentialModel> getStoredCredentialsByTypeStream(RealmModel realm, UserModel user, String type) {
List<CredentialModel> result = this.getStoredCredentialsByType(realm, user, type);
return result != null ? result.stream() : Stream.empty();
}
CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type); CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type);
//list operations //list operations
boolean moveCredentialTo(RealmModel realm, UserModel user, String id, String newPreviousCredentialId); boolean moveCredentialTo(RealmModel realm, UserModel user, String id, String newPreviousCredentialId);
/**
* The {@link UserCredentialStore.Streams} interface makes all collection-based methods in {@link UserCredentialStore}
* default by providing implementations that delegate to the {@link Stream}-based variants instead of the other way around.
* <p/>
* It allows for implementations to focus on the {@link Stream}-based approach for processing sets of data and benefit
* from the potential memory and performance optimizations of that approach.
*/
interface Streams extends UserCredentialStore {
@Override
default List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
return this.getStoredCredentialsStream(realm, user).collect(Collectors.toList());
}
@Override
Stream<CredentialModel> getStoredCredentialsStream(RealmModel realm, UserModel user);
@Override
default List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
return this.getStoredCredentialsByTypeStream(realm, user, type).collect(Collectors.toList());
}
@Override
Stream<CredentialModel> getStoredCredentialsByTypeStream(RealmModel realm, UserModel user, String type);
}
} }

View file

@ -22,6 +22,8 @@ import org.keycloak.credential.UserCredentialStore;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
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>
@ -95,9 +97,25 @@ public interface UserCredentialManager extends UserCredentialStore {
* @param realm * @param realm
* @param user * @param user
* @return * @return
* @deprecated Use {@link #getDisableableCredentialTypesStream(RealmModel, UserModel) getDisableableCredentialTypesStream}
* instead.
*/ */
@Deprecated
Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user); Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user);
/**
* Obtains the credential types that can be disabled by means of the {@link #disableCredentialType(RealmModel, UserModel, String)}
* method.
*
* @param realm a reference to the realm.
* @param user the user whose credentials are being searched.
* @return a non-null {@link Stream} of credential types.
*/
default Stream<String> getDisableableCredentialTypesStream(RealmModel realm, UserModel user) {
Set<String> result = this.getDisableableCredentialTypes(realm, user);
return result != null ? result.stream() : Stream.empty();
}
/** /**
* Checks to see if user has credential type configured. Looks in UserStorageProvider or UserFederationProvider first, * Checks to see if user has credential type configured. Looks in UserStorageProvider or UserFederationProvider first,
* then loops through each CredentialProvider. * then loops through each CredentialProvider.
@ -139,6 +157,49 @@ public interface UserCredentialManager extends UserCredentialStore {
* This will always return empty list for "local" users, which are not backed by any user storage * This will always return empty list for "local" users, which are not backed by any user storage
* *
* @return * @return
* @deprecated Use {@link #getConfiguredUserStorageCredentialTypesStream(RealmModel, UserModel) getConfiguredUserStorageCredentialTypesStream}
* instead.
*/ */
@Deprecated
List<String> getConfiguredUserStorageCredentialTypes(RealmModel realm, UserModel user); List<String> getConfiguredUserStorageCredentialTypes(RealmModel realm, UserModel user);
/**
* Obtains the credential types provided by the user storage where the specified user is stored. Examples of returned
* values are "password", "otp", etc.
* <p/>
* This method will always return an empty stream for "local" users - i.e. users that are not backed by any user storage.
*
* @param realm a reference to the realm.
* @param user a reference to the user.
* @return a non-null {@link Stream} of credential types.
*/
default Stream<String> getConfiguredUserStorageCredentialTypesStream(RealmModel realm, UserModel user) {
List<String> result = this.getConfiguredUserStorageCredentialTypes(realm, user);
return result != null ? result.stream() : Stream.empty();
}
/**
* The {@link UserCredentialManager.Streams} interface makes all collection-based methods in {@link UserCredentialManager}
* default by providing implementations that delegate to the {@link Stream}-based variants instead of the other way around.
* <p/>
* It allows for implementations to focus on the {@link Stream}-based approach for processing sets of data and benefit
* from the potential memory and performance optimizations of that approach.
*/
interface Streams extends UserCredentialManager, UserCredentialStore.Streams {
@Override
default Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
return this.getDisableableCredentialTypesStream(realm, user).collect(Collectors.toSet());
}
@Override
Stream<String> getDisableableCredentialTypesStream(RealmModel realm, UserModel user);
@Override
default List<String> getConfiguredUserStorageCredentialTypes(RealmModel realm, UserModel user) {
return this.getConfiguredUserStorageCredentialTypesStream(realm, user).collect(Collectors.toList());
}
@Override
Stream<String> getConfiguredUserStorageCredentialTypesStream(RealmModel realm, UserModel user);
}
} }

View file

@ -76,13 +76,9 @@ class AuthenticationSelectionResolver {
//add credential authenticators in order //add credential authenticators in order
if (processor.getAuthenticationSession().getAuthenticatedUser() != null) { if (processor.getAuthenticationSession().getAuthenticatedUser() != null) {
List<CredentialModel> credentials = processor.getSession().userCredentialManager() authenticationSelectionList = processor.getSession().userCredentialManager()
.getStoredCredentials(processor.getRealm(), processor.getAuthenticationSession().getAuthenticatedUser()) .getStoredCredentialsStream(processor.getRealm(), processor.getAuthenticationSession().getAuthenticatedUser())
.stream()
.filter(credential -> typeAuthExecMap.containsKey(credential.getType())) .filter(credential -> typeAuthExecMap.containsKey(credential.getType()))
.collect(Collectors.toList());
authenticationSelectionList = credentials.stream()
.map(CredentialModel::getType) .map(CredentialModel::getType)
.distinct() .distinct()
.map(credentialType -> new AuthenticationSelectionOption(processor.getSession(), typeAuthExecMap.get(credentialType))) .map(credentialType -> new AuthenticationSelectionOption(processor.getSession(), typeAuthExecMap.get(credentialType)))

View file

@ -20,6 +20,7 @@ package org.keycloak.authentication.authenticators.x509;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -67,7 +68,7 @@ public abstract class UserIdentityToModelMapper {
for (int i = 1; i <_customAttributes.size(); ++i) { for (int i = 1; i <_customAttributes.size(); ++i) {
String customAttribute = _customAttributes.get(i); String customAttribute = _customAttributes.get(i);
String userIdentityValue = userIdentityValues.get(i); String userIdentityValue = userIdentityValues.get(i);
usersStream = usersStream.filter(user -> user.getFirstAttribute(customAttribute).equals(userIdentityValue)); usersStream = usersStream.filter(user -> Objects.equals(user.getFirstAttribute(customAttribute), userIdentityValue));
} }
List<UserModel> users = usersStream.collect(Collectors.toList()); List<UserModel> users = usersStream.collect(Collectors.toList());
if (users.size() > 1) { if (users.size() > 1) {

View file

@ -43,8 +43,7 @@ import org.keycloak.utils.CredentialHelper;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.util.Collections; import java.util.stream.Stream;
import java.util.List;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -96,10 +95,10 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
return; return;
} }
OTPCredentialProvider otpCredentialProvider = (OTPCredentialProvider) context.getSession().getProvider(CredentialProvider.class, "keycloak-otp"); OTPCredentialProvider otpCredentialProvider = (OTPCredentialProvider) context.getSession().getProvider(CredentialProvider.class, "keycloak-otp");
final List<CredentialModel> otpCredentials = (otpCredentialProvider.isConfiguredFor(context.getRealm(), context.getUser())) final Stream<CredentialModel> otpCredentials = (otpCredentialProvider.isConfiguredFor(context.getRealm(), context.getUser()))
? context.getSession().userCredentialManager().getStoredCredentialsByType(context.getRealm(), context.getUser(), OTPCredentialModel.TYPE) ? context.getSession().userCredentialManager().getStoredCredentialsByTypeStream(context.getRealm(), context.getUser(), OTPCredentialModel.TYPE)
: Collections.EMPTY_LIST; : Stream.empty();
if (otpCredentials.size() >= 1 && Validation.isBlank(userLabel)) { if (otpCredentials.count() >= 1 && Validation.isBlank(userLabel)) {
Response challenge = context.form() Response challenge = context.form()
.setAttribute("mode", mode) .setAttribute("mode", mode)
.addError(new FormMessage(Validation.FIELD_OTP_LABEL, Messages.MISSING_TOTP_DEVICE_NAME)) .addError(new FormMessage(Validation.FIELD_OTP_LABEL, Messages.MISSING_TOTP_DEVICE_NAME))

View file

@ -22,6 +22,7 @@ import java.util.Base64;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
@ -47,7 +48,6 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.WebAuthnPolicy; import org.keycloak.models.WebAuthnPolicy;
import com.webauthn4j.WebAuthnManager;
import com.webauthn4j.converter.util.ObjectConverter; import com.webauthn4j.converter.util.ObjectConverter;
import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData; import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData;
import com.webauthn4j.data.attestation.statement.AttestationStatement; import com.webauthn4j.data.attestation.statement.AttestationStatement;
@ -125,15 +125,11 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis
String excludeCredentialIds = ""; String excludeCredentialIds = "";
if (avoidSameAuthenticatorRegister) { if (avoidSameAuthenticatorRegister) {
List<CredentialModel> webAuthnCredentials = session.userCredentialManager().getStoredCredentialsByType(context.getRealm(), userModel, getCredentialType()); excludeCredentialIds = session.userCredentialManager().getStoredCredentialsByTypeStream(context.getRealm(), userModel, getCredentialType())
List<String> webAuthnCredentialPubKeyIds = webAuthnCredentials.stream().map(credentialModel -> { .map(credentialModel -> {
WebAuthnCredentialModel credModel = WebAuthnCredentialModel.createFromCredentialModel(credentialModel);
WebAuthnCredentialModel credModel = WebAuthnCredentialModel.createFromCredentialModel(credentialModel); return Base64Url.encodeBase64ToBase64Url(credModel.getWebAuthnCredentialData().getCredentialId());
return Base64Url.encodeBase64ToBase64Url(credModel.getWebAuthnCredentialData().getCredentialId()); }).collect(Collectors.joining(","));
}).collect(Collectors.toList());
excludeCredentialIds = stringifyExcludeCredentialIds(webAuthnCredentialPubKeyIds);
} }
String isSetRetry = context.getHttpRequest().getDecodedFormParameters().getFirst(WebAuthnConstants.IS_SET_RETRY); String isSetRetry = context.getHttpRequest().getDecodedFormParameters().getFirst(WebAuthnConstants.IS_SET_RETRY);
@ -300,15 +296,6 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis
return sb.toString(); return sb.toString();
} }
private String stringifyExcludeCredentialIds(List<String> credentialIdsList) {
if (credentialIdsList == null || credentialIdsList.isEmpty()) return "";
StringBuilder sb = new StringBuilder();
for (String s : credentialIdsList)
if (s != null && !s.isEmpty()) sb.append(s).append(",");
if (sb.lastIndexOf(",") > -1) sb.deleteCharAt(sb.lastIndexOf(","));
return sb.toString();
}
private void showInfoAfterWebAuthnApiCreate(RegistrationData response) { private void showInfoAfterWebAuthnApiCreate(RegistrationData response) {
AttestedCredentialData attestedCredentialData = response.getAttestationObject().getAuthenticatorData().getAttestedCredentialData(); AttestedCredentialData attestedCredentialData = response.getAttestationObject().getAuthenticatorData().getAttestedCredentialData();
AttestationStatement attestationStatement = response.getAttestationObject().getAttestationStatement(); AttestationStatement attestationStatement = response.getAttestationObject().getAttestationStatement();

View file

@ -92,7 +92,7 @@ public class OTPCredentialProvider implements CredentialProvider<OTPCredentialMo
@Override @Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) { public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
if (!supportsCredentialType(credentialType)) return false; if (!supportsCredentialType(credentialType)) return false;
return !getCredentialStore().getStoredCredentialsByType(realm, user, credentialType).isEmpty(); return getCredentialStore().getStoredCredentialsByTypeStream(realm, user, credentialType).count() > 0;
} }
public boolean isConfiguredFor(RealmModel realm, UserModel user){ public boolean isConfiguredFor(RealmModel realm, UserModel user){

View file

@ -32,15 +32,16 @@ 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;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.stream.Collectors;
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 PasswordCredentialProvider implements CredentialProvider<PasswordCredentialModel>, CredentialInputUpdater, CredentialInputValidator, OnUserCache { public class PasswordCredentialProvider implements CredentialProvider<PasswordCredentialModel>, CredentialInputUpdater.Streams,
CredentialInputValidator, OnUserCache {
public static final String PASSWORD_CACHE_KEY = PasswordCredentialProvider.class.getName() + "." + PasswordCredentialModel.TYPE; 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);
@ -64,7 +65,7 @@ public class PasswordCredentialProvider implements CredentialProvider<PasswordCr
} }
// if the model was marked for eviction while passwords were initialized, override it from credentialStore // if the model was marked for eviction while passwords were initialized, override it from credentialStore
if (!(user instanceof CachedUserModel) || ((CachedUserModel) user).isMarkedForEviction()) { if (!(user instanceof CachedUserModel) || ((CachedUserModel) user).isMarkedForEviction()) {
passwords = getCredentialStore().getStoredCredentialsByType(realm, user, getType()); passwords = getCredentialStore().getStoredCredentialsByTypeStream(realm, user, getType()).collect(Collectors.toList());
} }
if (passwords == null || passwords.isEmpty()) return null; if (passwords == null || passwords.isEmpty()) return null;
@ -115,14 +116,12 @@ public class PasswordCredentialProvider implements CredentialProvider<PasswordCr
} }
// 3) remove old password history items // 3) remove old password history items
List<CredentialModel> passwordHistoryList = getCredentialStore().getStoredCredentialsByType(realm, user, PasswordCredentialModel.PASSWORD_HISTORY);
final int passwordHistoryListMaxSize = Math.max(0, expiredPasswordsPolicyValue - 1); final int passwordHistoryListMaxSize = Math.max(0, expiredPasswordsPolicyValue - 1);
if (passwordHistoryList.size() > passwordHistoryListMaxSize) { getCredentialStore().getStoredCredentialsByTypeStream(realm, user, PasswordCredentialModel.PASSWORD_HISTORY)
passwordHistoryList.stream() .sorted(CredentialModel.comparingByStartDateDesc())
.sorted(CredentialModel.comparingByStartDateDesc()) .skip(passwordHistoryListMaxSize)
.skip(passwordHistoryListMaxSize) .collect(Collectors.toList())
.forEach(p -> getCredentialStore().removeStoredCredential(realm, user, p.getId())); .forEach(p -> getCredentialStore().removeStoredCredential(realm, user, p.getId()));
}
UserCache userCache = session.userCache(); UserCache userCache = session.userCache();
if (userCache != null) { if (userCache != null) {
@ -220,8 +219,8 @@ public class PasswordCredentialProvider implements CredentialProvider<PasswordCr
} }
@Override @Override
public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) { public Stream<String> getDisableableCredentialTypesStream(RealmModel realm, UserModel user) {
return Collections.emptySet(); return Stream.empty();
} }
@Override @Override
@ -282,11 +281,9 @@ public class PasswordCredentialProvider implements CredentialProvider<PasswordCr
@Override @Override
public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) { public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
List<CredentialModel> passwords = getCredentialStore().getStoredCredentialsByType(realm, user, getType()); List<CredentialModel> passwords = getCredentialStore().getStoredCredentialsByTypeStream(realm, user, getType())
if (passwords != null) { .collect(Collectors.toList());
user.getCachedWith().put(PASSWORD_CACHE_KEY, passwords); user.getCachedWith().put(PASSWORD_CACHE_KEY, passwords);
}
} }
@Override @Override

View file

@ -25,7 +25,6 @@ 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.models.cache.UserCache;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.storage.AbstractStorageManager; import org.keycloak.storage.AbstractStorageManager;
import org.keycloak.storage.StorageId; import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProvider;
@ -33,12 +32,8 @@ import org.keycloak.storage.UserStorageProviderFactory;
import org.keycloak.storage.UserStorageProviderModel; import org.keycloak.storage.UserStorageProviderModel;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -46,7 +41,8 @@ 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<UserStorageProvider, UserStorageProviderModel> implements UserCredentialManager, OnUserCache { public class UserCredentialStoreManager extends AbstractStorageManager<UserStorageProvider, UserStorageProviderModel>
implements UserCredentialManager.Streams, OnUserCache {
public UserCredentialStoreManager(KeycloakSession session) { public UserCredentialStoreManager(KeycloakSession session) {
super(session, UserStorageProviderFactory.class, UserStorageProvider.class, UserStorageProviderModel::new, "user"); super(session, UserStorageProviderFactory.class, UserStorageProvider.class, UserStorageProviderModel::new, "user");
@ -89,13 +85,13 @@ public class UserCredentialStoreManager extends AbstractStorageManager<UserStora
} }
@Override @Override
public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) { public Stream<CredentialModel> getStoredCredentialsStream(RealmModel realm, UserModel user) {
return getStoreForUser(user).getStoredCredentials(realm, user); return getStoreForUser(user).getStoredCredentialsStream(realm, user);
} }
@Override @Override
public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) { public Stream<CredentialModel> getStoredCredentialsByTypeStream(RealmModel realm, UserModel user, String type) {
return getStoreForUser(user).getStoredCredentialsByType(realm, user, type); return getStoreForUser(user).getStoredCredentialsByTypeStream(realm, user, type);
} }
@Override @Override
@ -218,23 +214,20 @@ public class UserCredentialStoreManager extends AbstractStorageManager<UserStora
} }
@Override @Override
public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) { public Stream<String> getDisableableCredentialTypesStream(RealmModel realm, UserModel user) {
Set<String> types = new HashSet<>(); Stream<String> types = Stream.empty();
String providerId = StorageId.isLocalStorage(user) ? user.getFederationLink() : StorageId.resolveProviderId(user); String providerId = StorageId.isLocalStorage(user) ? user.getFederationLink() : StorageId.resolveProviderId(user);
if (providerId != null) { if (providerId != null) {
UserStorageProviderModel model = getStorageProviderModel(realm, providerId); UserStorageProviderModel model = getStorageProviderModel(realm, providerId);
if (model == null || !model.isEnabled()) return Collections.EMPTY_SET; if (model == null || !model.isEnabled()) return types;
CredentialInputUpdater updater = getStorageProviderInstance(model, CredentialInputUpdater.class); CredentialInputUpdater updater = getStorageProviderInstance(model, CredentialInputUpdater.class);
if (updater != null) types.addAll(updater.getDisableableCredentialTypes(realm, user)); if (updater != null) types = updater.getDisableableCredentialTypesStream(realm, user);
} }
types.addAll(getCredentialProviders(session, CredentialInputUpdater.class) return Stream.concat(types, getCredentialProviders(session, CredentialInputUpdater.class)
.map(updater -> updater.getDisableableCredentialTypes(realm, user)) .flatMap(updater -> updater.getDisableableCredentialTypesStream(realm, user)))
.flatMap(Set::stream) .distinct();
.collect(Collectors.toSet()));
return types;
} }
@Override @Override
@ -298,10 +291,9 @@ public class UserCredentialStoreManager extends AbstractStorageManager<UserStora
} }
@Override @Override
public List<String> getConfiguredUserStorageCredentialTypes(RealmModel realm, UserModel user) { public Stream<String> getConfiguredUserStorageCredentialTypesStream(RealmModel realm, UserModel user) {
return getCredentialProviders(session, CredentialProvider.class).map(CredentialProvider::getType) return getCredentialProviders(session, CredentialProvider.class).map(CredentialProvider::getType)
.filter(credentialType -> UserStorageCredentialConfigured.CONFIGURED == isConfiguredThroughUserStorage(realm, user, credentialType)) .filter(credentialType -> UserStorageCredentialConfigured.CONFIGURED == isConfiguredThroughUserStorage(realm, user, credentialType));
.collect(Collectors.toList());
} }
@Override @Override

View file

@ -157,7 +157,7 @@ public class WebAuthnCredentialProvider implements CredentialProvider<WebAuthnCr
@Override @Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) { public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
if (!supportsCredentialType(credentialType)) return false; if (!supportsCredentialType(credentialType)) return false;
return !session.userCredentialManager().getStoredCredentialsByType(realm, user, credentialType).isEmpty(); return session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, credentialType).count() > 0;
} }
@ -224,9 +224,7 @@ public class WebAuthnCredentialProvider implements CredentialProvider<WebAuthnCr
private List<WebAuthnCredentialModelInput> getWebAuthnCredentialModelList(RealmModel realm, UserModel user) { private List<WebAuthnCredentialModelInput> getWebAuthnCredentialModelList(RealmModel realm, UserModel user) {
List<CredentialModel> credentialModels = session.userCredentialManager().getStoredCredentialsByType(realm, user, getType()); return session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, getType())
return credentialModels.stream()
.map(this::getCredentialInputFromCredentialModel) .map(this::getCredentialInputFromCredentialModel)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }

View file

@ -495,12 +495,8 @@ public class ExportUtils {
// Credentials - extra security, do not export credentials if service accounts // Credentials - extra security, do not export credentials if service accounts
if (internal) { if (internal) {
List<CredentialModel> creds = session.userCredentialManager().getStoredCredentials(realm, user); List<CredentialRepresentation> credReps = session.userCredentialManager().getStoredCredentialsStream(realm, user)
List<CredentialRepresentation> credReps = new ArrayList<>(); .map(ExportUtils::exportCredential).collect(Collectors.toList());
for (CredentialModel cred : creds) {
CredentialRepresentation credRep = exportCredential(cred);
credReps.add(credRep);
}
userRep.setCredentials(credReps); userRep.setCredentials(credReps);
} }

View file

@ -54,45 +54,7 @@ public class ApplicationsBean {
this.applications = this.getApplications(session, realm, user) this.applications = this.getApplications(session, realm, user)
.filter(client -> !isAdminClient(client) || AdminPermissions.realms(session, realm, user).isAdmin()) .filter(client -> !isAdminClient(client) || AdminPermissions.realms(session, realm, user).isAdmin())
.map(client -> { .map(client -> toApplicationEntry(session, realm, user, client, offlineClients))
// Construct scope parameter with all optional scopes to see all potentially available roles
Stream<ClientScopeModel> allClientScopes = Stream.concat(
client.getClientScopes(true, true).values().stream(),
client.getClientScopes(false, true).values().stream());
allClientScopes = Stream.concat(allClientScopes, Stream.of(client)).distinct();
Set<RoleModel> availableRoles = TokenManager.getAccess(user, client, allClientScopes);
// Don't show applications, which user doesn't have access into (any available roles)
// unless this is can be changed by approving/revoking consent
if (! isAdminClient(client) && availableRoles.isEmpty() && ! client.isConsentRequired()) {
return null;
}
List<RoleModel> realmRolesAvailable = new LinkedList<>();
MultivaluedHashMap<String, ClientRoleEntry> resourceRolesAvailable = new MultivaluedHashMap<>();
processRoles(availableRoles, realmRolesAvailable, resourceRolesAvailable);
List<ClientScopeModel> orderedScopes = new LinkedList<>();
if (client.isConsentRequired()) {
UserConsentModel consent = session.users().getConsentByClient(realm, user.getId(), client.getId());
if (consent != null) {
orderedScopes.addAll(consent.getGrantedClientScopes());
}
}
List<String> clientScopesGranted = orderedScopes.stream()
.sorted(OrderedModel.OrderedModelComparator.getInstance())
.map(ClientScopeModel::getConsentScreenText)
.collect(Collectors.toList());
List<String> additionalGrants = new ArrayList<>();
if (offlineClients.contains(client)) {
additionalGrants.add("${offlineToken}");
}
return new ApplicationEntry(session, realmRolesAvailable, resourceRolesAvailable, client, clientScopesGranted, additionalGrants);
})
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@ -204,4 +166,56 @@ public class ApplicationsBean {
return roleDescription; return roleDescription;
} }
} }
/**
* Constructs a {@link ApplicationEntry} from the specified parameters.
*
* @param session a reference to the {@code Keycloak} session.
* @param realm a reference to the realm.
* @param user a reference to the user.
* @param client a reference to the client that contains the applications.
* @param offlineClients a {@link Set} containing the offline clients.
* @return the constructed {@link ApplicationEntry} instance or {@code null} if the user can't access the applications
* in the specified client.
*/
private ApplicationEntry toApplicationEntry(final KeycloakSession session, final RealmModel realm, final UserModel user,
final ClientModel client, final Set<ClientModel> offlineClients) {
// Construct scope parameter with all optional scopes to see all potentially available roles
Stream<ClientScopeModel> allClientScopes = Stream.concat(
client.getClientScopes(true, true).values().stream(),
client.getClientScopes(false, true).values().stream());
allClientScopes = Stream.concat(allClientScopes, Stream.of(client)).distinct();
Set<RoleModel> availableRoles = TokenManager.getAccess(user, client, allClientScopes);
// Don't show applications, which user doesn't have access into (any available roles)
// unless this is can be changed by approving/revoking consent
if (! isAdminClient(client) && availableRoles.isEmpty() && ! client.isConsentRequired()) {
return null;
}
List<RoleModel> realmRolesAvailable = new LinkedList<>();
MultivaluedHashMap<String, ClientRoleEntry> resourceRolesAvailable = new MultivaluedHashMap<>();
processRoles(availableRoles, realmRolesAvailable, resourceRolesAvailable);
List<ClientScopeModel> orderedScopes = new LinkedList<>();
if (client.isConsentRequired()) {
UserConsentModel consent = session.users().getConsentByClient(realm, user.getId(), client.getId());
if (consent != null) {
orderedScopes.addAll(consent.getGrantedClientScopes());
}
}
List<String> clientScopesGranted = orderedScopes.stream()
.sorted(OrderedModel.OrderedModelComparator.getInstance())
.map(ClientScopeModel::getConsentScreenText)
.collect(Collectors.toList());
List<String> additionalGrants = new ArrayList<>();
if (offlineClients.contains(client)) {
additionalGrants.add("${offlineToken}");
}
return new ApplicationEntry(session, realmRolesAvailable, resourceRolesAvailable, client, clientScopesGranted, additionalGrants);
}
} }

View file

@ -18,8 +18,6 @@
package org.keycloak.forms.account.freemarker.model; package org.keycloak.forms.account.freemarker.model;
import org.keycloak.credential.CredentialModel; import org.keycloak.credential.CredentialModel;
import org.keycloak.credential.CredentialProvider;
import org.keycloak.credential.OTPCredentialProvider;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OTPPolicy; import org.keycloak.models.OTPPolicy;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -33,6 +31,7 @@ import org.keycloak.utils.TotpUtils;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import static org.keycloak.utils.CredentialHelper.createUserStorageCredentialRepresentation; import static org.keycloak.utils.CredentialHelper.createUserStorageCredentialRepresentation;
@ -54,7 +53,8 @@ public class TotpBean {
this.uriBuilder = uriBuilder; this.uriBuilder = uriBuilder;
this.enabled = session.userCredentialManager().isConfiguredFor(realm, user, OTPCredentialModel.TYPE); this.enabled = session.userCredentialManager().isConfiguredFor(realm, user, OTPCredentialModel.TYPE);
if (enabled) { if (enabled) {
List<CredentialModel> otpCredentials = session.userCredentialManager().getStoredCredentialsByType(realm, user, OTPCredentialModel.TYPE); List<CredentialModel> otpCredentials = session.userCredentialManager()
.getStoredCredentialsByTypeStream(realm, user, OTPCredentialModel.TYPE).collect(Collectors.toList());
if (otpCredentials.isEmpty()) { if (otpCredentials.isEmpty()) {
// Credential is configured on userStorage side. Create the "fake" credential similar like we do for the new account console // Credential is configured on userStorage side. Create the "fake" credential similar like we do for the new account console

View file

@ -28,6 +28,7 @@ import org.keycloak.utils.TotpUtils;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* Used for UpdateTotp required action * Used for UpdateTotp required action
@ -49,7 +50,8 @@ public class TotpBean {
this.uriBuilder = uriBuilder; this.uriBuilder = uriBuilder;
this.enabled = session.userCredentialManager().isConfiguredFor(realm, user, OTPCredentialModel.TYPE); this.enabled = session.userCredentialManager().isConfiguredFor(realm, user, OTPCredentialModel.TYPE);
if (enabled) { if (enabled) {
otpCredentials = session.userCredentialManager().getStoredCredentialsByType(realm, user, OTPCredentialModel.TYPE); otpCredentials = session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, OTPCredentialModel.TYPE)
.collect(Collectors.toList());
} else { } else {
otpCredentials = Collections.EMPTY_LIST; otpCredentials = Collections.EMPTY_LIST;
} }

View file

@ -42,10 +42,8 @@ public class TotpLoginBean {
private final List<OTPCredential> userOtpCredentials; private final List<OTPCredential> userOtpCredentials;
public TotpLoginBean(KeycloakSession session, RealmModel realm, UserModel user, String selectedCredentialId) { public TotpLoginBean(KeycloakSession session, RealmModel realm, UserModel user, String selectedCredentialId) {
List<CredentialModel> userOtpCredentials = session.userCredentialManager()
.getStoredCredentialsByType(realm, user, OTPCredentialModel.TYPE);
this.userOtpCredentials = userOtpCredentials.stream() this.userOtpCredentials = session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, OTPCredentialModel.TYPE)
.map(OTPCredential::new) .map(OTPCredential::new)
.collect(Collectors.toList()); .collect(Collectors.toList());

View file

@ -17,9 +17,9 @@ package org.keycloak.forms.login.freemarker.model;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.Base64Url;
import org.keycloak.credential.CredentialModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
@ -30,13 +30,13 @@ public class WebAuthnAuthenticatorsBean {
public WebAuthnAuthenticatorsBean(KeycloakSession session, RealmModel realm, UserModel user, String credentialType) { public WebAuthnAuthenticatorsBean(KeycloakSession session, RealmModel realm, UserModel user, String credentialType) {
// should consider multiple credentials in the future, but only single credential supported now. // should consider multiple credentials in the future, but only single credential supported now.
for (CredentialModel credential : session.userCredentialManager().getStoredCredentialsByType(realm, user, credentialType)) { this.authenticators = session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, credentialType)
WebAuthnCredentialModel webAuthnCredential = WebAuthnCredentialModel.createFromCredentialModel(credential); .map(WebAuthnCredentialModel::createFromCredentialModel)
.map(webAuthnCredential -> {
String credentialId = Base64Url.encodeBase64ToBase64Url(webAuthnCredential.getWebAuthnCredentialData().getCredentialId()); String credentialId = Base64Url.encodeBase64ToBase64Url(webAuthnCredential.getWebAuthnCredentialData().getCredentialId());
String label = (webAuthnCredential.getUserLabel()==null || webAuthnCredential.getUserLabel().isEmpty()) ? "label missing" : webAuthnCredential.getUserLabel(); String label = (webAuthnCredential.getUserLabel()==null || webAuthnCredential.getUserLabel().isEmpty()) ? "label missing" : webAuthnCredential.getUserLabel();
authenticators.add(new WebAuthnAuthenticatorBean(credentialId, label)); return new WebAuthnAuthenticatorBean(credentialId, label);
} }).collect(Collectors.toList());
} }
public List<WebAuthnAuthenticatorBean> getAuthenticators() { public List<WebAuthnAuthenticatorBean> getAuthenticators() {

View file

@ -170,14 +170,9 @@ public class AccountCredentialResource {
.collect(Collectors.toList()); .collect(Collectors.toList());
Set<String> enabledCredentialTypes = getEnabledCredentialTypes(credentialProviders); Set<String> enabledCredentialTypes = getEnabledCredentialTypes(credentialProviders);
List<CredentialModel> models = includeUserCredentials ? session.userCredentialManager().getStoredCredentials(realm, user) : null; Stream<CredentialModel> modelsStream = includeUserCredentials ? session.userCredentialManager().getStoredCredentialsStream(realm, user) : Stream.empty();
// Don't return secrets from REST endpoint // Don't return secrets from REST endpoint
if (models != null) { List<CredentialModel> models = modelsStream.peek(model -> model.setSecretData(null)).collect(Collectors.toList());
for (CredentialModel credential : models) {
credential.setSecretData(null);
}
}
Function<CredentialProvider, CredentialContainer> toCredentialContainer = (credentialProvider) -> { Function<CredentialProvider, CredentialContainer> toCredentialContainer = (credentialProvider) -> {
CredentialTypeMetadataContext ctx = CredentialTypeMetadataContext.builder() CredentialTypeMetadataContext ctx = CredentialTypeMetadataContext.builder()

View file

@ -19,7 +19,13 @@ package org.keycloak.services.resources.account;
import java.net.URI; import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.*; import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;

View file

@ -600,7 +600,7 @@ public class UserResource {
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public Stream<CredentialRepresentation> credentials(){ public Stream<CredentialRepresentation> credentials(){
auth.users().requireManage(user); auth.users().requireManage(user);
return session.userCredentialManager().getStoredCredentials(realm, user).stream() return session.userCredentialManager().getStoredCredentialsStream(realm, user)
.peek(model -> model.setSecretData(null)) .peek(model -> model.setSecretData(null))
.map(ModelToRepresentation::toRepresentation); .map(ModelToRepresentation::toRepresentation);
} }
@ -616,11 +616,11 @@ public class UserResource {
@Path("configured-user-storage-credential-types") @Path("configured-user-storage-credential-types")
@NoCache @NoCache
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public List<String> getConfiguredUserStorageCredentialTypes() { public Stream<String> getConfiguredUserStorageCredentialTypes() {
// This has "requireManage" due the compatibility with "credentials()" endpoint. Strictly said, it is reading endpoint, not writing, // This has "requireManage" due the compatibility with "credentials()" endpoint. Strictly said, it is reading endpoint, not writing,
// so may be revisited if to rather use "requireView" here in the future. // so may be revisited if to rather use "requireView" here in the future.
auth.users().requireManage(user); auth.users().requireManage(user);
return session.userCredentialManager().getConfiguredUserStorageCredentialTypes(realm, user); return session.userCredentialManager().getConfiguredUserStorageCredentialTypesStream(realm, user);
} }

View file

@ -18,10 +18,13 @@
package org.keycloak.testsuite.federation; package org.keycloak.testsuite.federation;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Collectors;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
@ -57,7 +60,7 @@ import org.keycloak.storage.user.UserRegistrationProvider;
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
public class BackwardsCompatibilityUserStorage implements UserLookupProvider, UserStorageProvider, UserRegistrationProvider, public class BackwardsCompatibilityUserStorage implements UserLookupProvider, UserStorageProvider, UserRegistrationProvider,
CredentialInputUpdater, CredentialInputValidator, UserQueryProvider.Streams { CredentialInputUpdater, CredentialInputValidator, UserQueryProvider {
private static final Logger log = Logger.getLogger(BackwardsCompatibilityUserStorage.class); private static final Logger log = Logger.getLogger(BackwardsCompatibilityUserStorage.class);
@ -82,7 +85,7 @@ public class BackwardsCompatibilityUserStorage implements UserLookupProvider, Us
} }
private UserModel createUser(RealmModel realm, String username) { private UserModel createUser(RealmModel realm, String username) {
return new AbstractUserAdapterFederatedStorage.Streams(session, realm, model) { return new AbstractUserAdapterFederatedStorage(session, realm, model) {
@Override @Override
public String getUsername() { public String getUsername() {
return username; return username;
@ -316,57 +319,58 @@ public class BackwardsCompatibilityUserStorage implements UserLookupProvider, Us
} }
@Override @Override
public Stream<UserModel> getUsersStream(RealmModel realm) { public List<UserModel> getUsers(RealmModel realm) {
return getUsersStream(realm, -1, -1); return getUsers(realm, -1, -1);
} }
@Override @Override
public Stream<UserModel> getUsersStream(RealmModel realm, int firstResult, int maxResults) { public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
return users.values() return users.values()
.stream() .stream()
.skip(firstResult).limit(maxResults) .skip(firstResult).limit(maxResults)
.map(myUser -> createUser(realm, myUser.username)); .map(myUser -> createUser(realm, myUser.username))
.collect(Collectors.toList());
} }
@Override @Override
public Stream<UserModel> searchForUserStream(String search, RealmModel realm) { public List<UserModel> searchForUser(String search, RealmModel realm) {
return searchForUserStream(search, realm, -1, -1); return searchForUser(search, realm, -1, -1);
} }
@Override @Override
public Stream<UserModel> searchForUserStream(String search, RealmModel realm, int firstResult, int maxResults) { public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
UserModel user = getUserByUsername(search, realm); UserModel user = getUserByUsername(search, realm);
return user == null ? Stream.empty() : Stream.of(user); return user == null ? Collections.emptyList() : Arrays.asList(user);
} }
@Override @Override
public Stream<UserModel> searchForUserStream(Map<String, String> params, RealmModel realm) { public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm) {
// Assume that this is not supported // Assume that this is not supported
return Stream.empty(); return Collections.emptyList();
} }
@Override @Override
public Stream<UserModel> searchForUserStream(Map<String, String> params, RealmModel realm, int firstResult, int maxResults) { public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm, int firstResult, int maxResults) {
// Assume that this is not supported // Assume that this is not supported
return Stream.empty(); return Collections.emptyList();
} }
@Override @Override
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, int firstResult, int maxResults) { public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
// Assume that this is not supported // Assume that this is not supported
return Stream.empty(); return Collections.emptyList();
} }
@Override @Override
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group) { public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
// Assume that this is not supported // Assume that this is not supported
return Stream.empty(); return Collections.emptyList();
} }
@Override @Override
public Stream<UserModel> searchForUserByUserAttributeStream(String attrName, String attrValue, RealmModel realm) { public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
// Assume that this is not supported // Assume that this is not supported
return Stream.empty(); return Collections.emptyList();
} }
@Override @Override

View file

@ -33,10 +33,8 @@ import org.keycloak.storage.user.ImportedUserValidation;
import org.keycloak.storage.user.UserLookupProvider; import org.keycloak.storage.user.UserLookupProvider;
import org.keycloak.storage.user.UserQueryProvider; import org.keycloak.storage.user.UserQueryProvider;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
@ -44,7 +42,7 @@ import java.util.stream.Stream;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class FailableHardcodedStorageProvider implements UserStorageProvider, UserLookupProvider, UserQueryProvider.Streams, public class FailableHardcodedStorageProvider implements UserStorageProvider, UserLookupProvider, UserQueryProvider.Streams,
ImportedUserValidation, CredentialInputUpdater, CredentialInputValidator { ImportedUserValidation, CredentialInputUpdater.Streams, CredentialInputValidator {
public static String username = "billb"; public static String username = "billb";
public static String password = "password"; public static String password = "password";
@ -97,9 +95,9 @@ public class FailableHardcodedStorageProvider implements UserStorageProvider, Us
} }
@Override @Override
public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) { public Stream<String> getDisableableCredentialTypesStream(RealmModel realm, UserModel user) {
checkForceFail(); checkForceFail();
return Collections.EMPTY_SET; return Stream.empty();
} }
@Override @Override

View file

@ -23,7 +23,6 @@ import org.keycloak.credential.CredentialInputValidator;
import org.keycloak.credential.CredentialModel; import org.keycloak.credential.CredentialModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
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;
import org.keycloak.storage.StorageId; import org.keycloak.storage.StorageId;
@ -32,10 +31,10 @@ import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
import org.keycloak.storage.user.UserLookupProvider; import org.keycloak.storage.user.UserLookupProvider;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -83,12 +82,9 @@ public class PassThroughFederatedUserStorageProvider implements
if (INITIAL_PASSWORD.equals(input.getChallengeResponse())) { if (INITIAL_PASSWORD.equals(input.getChallengeResponse())) {
return true; return true;
} }
Optional<CredentialModel> existing = session.userFederatedStorage() return session.userFederatedStorage().getStoredCredentialsByTypeStream(realm, user.getId(), "CLEAR_TEXT_PASSWORD")
.getStoredCredentialsByTypeStream(realm, user.getId(), "CLEAR_TEXT_PASSWORD") .map(credentialModel -> credentialModel.getSecretData())
.findFirst(); .anyMatch(Predicate.isEqual("{\"value\":\"" + input.getChallengeResponse() + "\"}"));
if (existing.isPresent())
return existing.get().getSecretData().equals("{\"value\":\"" + input.getChallengeResponse() + "\"}");
return false;
} }
return false; return false;
} }

View file

@ -39,7 +39,6 @@ import org.keycloak.storage.user.UserLookupProvider;
import org.keycloak.storage.user.UserQueryProvider; import org.keycloak.storage.user.UserQueryProvider;
import org.keycloak.storage.user.UserRegistrationProvider; import org.keycloak.storage.user.UserRegistrationProvider;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@ -54,7 +53,7 @@ import static org.keycloak.storage.UserStorageProviderModel.IMPORT_ENABLED;
* @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 UserMapStorage implements UserLookupProvider, UserStorageProvider, UserRegistrationProvider, CredentialInputUpdater, public class UserMapStorage implements UserLookupProvider, UserStorageProvider, UserRegistrationProvider, CredentialInputUpdater.Streams,
CredentialInputValidator, UserGroupMembershipFederatedStorage.Streams, UserQueryProvider.Streams, ImportedUserValidation { CredentialInputValidator, UserGroupMembershipFederatedStorage.Streams, UserQueryProvider.Streams, ImportedUserValidation {
private static final Logger log = Logger.getLogger(UserMapStorage.class); private static final Logger log = Logger.getLogger(UserMapStorage.class);
@ -174,8 +173,8 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider,
} }
@Override @Override
public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) { public Stream<String> getDisableableCredentialTypesStream(RealmModel realm, UserModel user) {
return Collections.EMPTY_SET; return Stream.empty();
} }
@Override @Override

View file

@ -9,6 +9,7 @@ import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* Created by st on 26.01.17. * Created by st on 26.01.17.
@ -55,7 +56,9 @@ public class RunHelpers {
return (FetchOnServer) session -> { return (FetchOnServer) session -> {
RealmModel realm = session.getContext().getRealm(); RealmModel realm = session.getContext().getRealm();
UserModel user = session.users().getUserByUsername(username, realm); UserModel user = session.users().getUserByUsername(username, realm);
List<CredentialModel> storedCredentialsByType = session.userCredentialManager().getStoredCredentialsByType(realm, user, CredentialRepresentation.PASSWORD); List<CredentialModel> storedCredentialsByType = session.userCredentialManager()
.getStoredCredentialsByTypeStream(realm, user, CredentialRepresentation.PASSWORD)
.collect(Collectors.toList());
System.out.println(storedCredentialsByType.size()); System.out.println(storedCredentialsByType.size());
return storedCredentialsByType.get(0); return storedCredentialsByType.get(0);
}; };

View file

@ -79,6 +79,7 @@ import java.util.Arrays;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.Assume; import org.junit.Assume;
@ -482,7 +483,8 @@ public class AccountFormServiceTest extends AbstractTestRealmKeycloakTest {
RealmModel realm = session.getContext().getRealm(); RealmModel realm = session.getContext().getRealm();
UserModel user = session.users().getUserById(uId, realm); UserModel user = session.users().getUserById(uId, realm);
assertThat(user, Matchers.notNullValue()); assertThat(user, Matchers.notNullValue());
List<CredentialModel> storedCredentials = session.userCredentialManager().getStoredCredentials(realm, user); List<CredentialModel> storedCredentials = session.userCredentialManager()
.getStoredCredentialsStream(realm, user).collect(Collectors.toList());
assertThat(storedCredentials, Matchers.hasSize(expectedNumberOfStoredCredentials)); assertThat(storedCredentials, Matchers.hasSize(expectedNumberOfStoredCredentials));
}); });
} }

View file

@ -64,6 +64,8 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.junit.Assume; import org.junit.Assume;
import org.junit.BeforeClass; import org.junit.BeforeClass;
@ -630,9 +632,9 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
testingClient.server().run(session -> { testingClient.server().run(session -> {
RealmModel realm = session.realms().getRealmByName("test"); RealmModel realm = session.realms().getRealmByName("test");
UserModel user = session.users().getUserByUsername("wburke", realm); UserModel user = session.users().getUserByUsername("wburke", realm);
for (CredentialModel c: session.userCredentialManager().getStoredCredentialsByType(realm, user, OTPCredentialModel.TYPE)){ session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, OTPCredentialModel.TYPE)
session.userCredentialManager().removeStoredCredential(realm, user, c.getId()); .collect(Collectors.toList())
} .forEach(model -> session.userCredentialManager().removeStoredCredential(realm, user, model.getId()));
}); });
} }

View file

@ -892,7 +892,10 @@ public class LDAPProvidersIntegrationTest extends AbstractLDAPTest {
UserCredentialModel cred = UserCredentialModel.password("Candycand1", true); UserCredentialModel cred = UserCredentialModel.password("Candycand1", true);
session.userCredentialManager().updateCredential(appRealm, user, cred); session.userCredentialManager().updateCredential(appRealm, user, cred);
CredentialModel userCredentialValueModel = session.userCredentialManager().getStoredCredentialsByType(appRealm, user, PasswordCredentialModel.TYPE).get(0); CredentialModel userCredentialValueModel = session.userCredentialManager()
.getStoredCredentialsByTypeStream(appRealm, user, PasswordCredentialModel.TYPE)
.findFirst().orElse(null);
Assert.assertNotNull(userCredentialValueModel);
Assert.assertEquals(PasswordCredentialModel.TYPE, userCredentialValueModel.getType()); Assert.assertEquals(PasswordCredentialModel.TYPE, userCredentialValueModel.getType());
Assert.assertTrue(session.userCredentialManager().isValid(appRealm, user, cred)); Assert.assertTrue(session.userCredentialManager().isValid(appRealm, user, cred));

View file

@ -23,6 +23,7 @@ import java.net.URISyntaxException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
@ -292,8 +293,8 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
testingClient.server().run(session -> { testingClient.server().run(session -> {
RealmModel realm1 = session.realms().getRealmByName("test"); RealmModel realm1 = session.realms().getRealmByName("test");
UserModel user1 = session.users().getUserByUsername("otp1", realm1); UserModel user1 = session.users().getUserByUsername("otp1", realm1);
List<CredentialModel> keycloakDBCredentials = session.userCredentialManager().getStoredCredentials(realm1, user1); Assert.assertEquals(0, session.userCredentialManager()
Assert.assertTrue(keycloakDBCredentials.isEmpty()); .getStoredCredentialsStream(realm1, user1).count());
}); });
} }

View file

@ -63,6 +63,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Calendar.DAY_OF_WEEK; import static java.util.Calendar.DAY_OF_WEEK;
import static java.util.Calendar.HOUR_OF_DAY; import static java.util.Calendar.HOUR_OF_DAY;
@ -879,8 +880,8 @@ public class UserStorageTest extends AbstractAuthTest {
UserModel user = currentSession.users().getUserByUsername("thor", realm); UserModel user = currentSession.users().getUserByUsername("thor", realm);
Assert.assertFalse(StorageId.isLocalStorage(user)); Assert.assertFalse(StorageId.isLocalStorage(user));
List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentials(realm, user); Stream<CredentialModel> credentials = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user);
org.keycloak.testsuite.Assert.assertEquals(0, list.size()); org.keycloak.testsuite.Assert.assertEquals(0, credentials.count());
// Create password // Create password
CredentialModel passwordCred = PasswordCredentialModel.createFromValues("my-algorithm", "theSalt".getBytes(), 22, "ABC"); CredentialModel passwordCred = PasswordCredentialModel.createFromValues("my-algorithm", "theSalt".getBytes(), 22, "ABC");
@ -902,7 +903,8 @@ public class UserStorageTest extends AbstractAuthTest {
UserModel user = currentSession.users().getUserByUsername("thor", realm); UserModel user = currentSession.users().getUserByUsername("thor", realm);
// Assert priorities: password, otp1, otp2 // Assert priorities: password, otp1, otp2
List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentials(realm, user); List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user)
.collect(Collectors.toList());
assertOrder(list, passwordId.get(), otp1Id.get(), otp2Id.get()); assertOrder(list, passwordId.get(), otp1Id.get(), otp2Id.get());
// Assert can't move password when newPreviousCredential not found // Assert can't move password when newPreviousCredential not found
@ -920,7 +922,8 @@ public class UserStorageTest extends AbstractAuthTest {
UserModel user = currentSession.users().getUserByUsername("thor", realm); UserModel user = currentSession.users().getUserByUsername("thor", realm);
// Assert priorities: password, otp2, otp1 // Assert priorities: password, otp2, otp1
List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentials(realm, user); List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user)
.collect(Collectors.toList());
assertOrder(list, passwordId.get(), otp2Id.get(), otp1Id.get()); assertOrder(list, passwordId.get(), otp2Id.get(), otp1Id.get());
// Move otp2 to the top // Move otp2 to the top
@ -932,7 +935,8 @@ public class UserStorageTest extends AbstractAuthTest {
UserModel user = currentSession.users().getUserByUsername("thor", realm); UserModel user = currentSession.users().getUserByUsername("thor", realm);
// Assert priorities: otp2, password, otp1 // Assert priorities: otp2, password, otp1
List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentials(realm, user); List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user)
.collect(Collectors.toList());
assertOrder(list, otp2Id.get(), passwordId.get(), otp1Id.get()); assertOrder(list, otp2Id.get(), passwordId.get(), otp1Id.get());
// Move password down // Move password down
@ -944,7 +948,8 @@ public class UserStorageTest extends AbstractAuthTest {
UserModel user = currentSession.users().getUserByUsername("thor", realm); UserModel user = currentSession.users().getUserByUsername("thor", realm);
// Assert priorities: otp2, otp1, password // Assert priorities: otp2, otp1, password
List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentials(realm, user); List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user)
.collect(Collectors.toList());
assertOrder(list, otp2Id.get(), otp1Id.get(), passwordId.get()); assertOrder(list, otp2Id.get(), otp1Id.get(), passwordId.get());
// Remove otp2 down two positions // Remove otp2 down two positions
@ -956,7 +961,8 @@ public class UserStorageTest extends AbstractAuthTest {
UserModel user = currentSession.users().getUserByUsername("thor", realm); UserModel user = currentSession.users().getUserByUsername("thor", realm);
// Assert priorities: otp2, otp1, password // Assert priorities: otp2, otp1, password
List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentials(realm, user); List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user)
.collect(Collectors.toList());
assertOrder(list, otp1Id.get(), passwordId.get(), otp2Id.get()); assertOrder(list, otp1Id.get(), passwordId.get(), otp2Id.get());
// Remove password // Remove password
@ -968,7 +974,8 @@ public class UserStorageTest extends AbstractAuthTest {
UserModel user = currentSession.users().getUserByUsername("thor", realm); UserModel user = currentSession.users().getUserByUsername("thor", realm);
// Assert priorities: otp2, password // Assert priorities: otp2, password
List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentials(realm, user); List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user)
.collect(Collectors.toList());
assertOrder(list, otp1Id.get(), otp2Id.get()); assertOrder(list, otp1Id.get(), otp2Id.get());
}); });
} }

View file

@ -242,7 +242,8 @@ public class PasswordHashingTest extends AbstractTestRealmKeycloakTest {
return testingClient.server("test").fetch(session -> { return testingClient.server("test").fetch(session -> {
RealmModel realm = session.getContext().getRealm(); RealmModel realm = session.getContext().getRealm();
UserModel user = session.users().getUserByUsername(username, realm); UserModel user = session.users().getUserByUsername(username, realm);
return session.userCredentialManager().getStoredCredentialsByType(realm, user, CredentialRepresentation.PASSWORD).get(0); return session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, CredentialRepresentation.PASSWORD)
.findFirst().orElse(null);
}, CredentialModel.class); }, CredentialModel.class);
} }

View file

@ -15,6 +15,7 @@ import org.keycloak.testsuite.arquillian.annotation.ModelTest;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE; import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE;
@ -41,7 +42,8 @@ public class CredentialModelTest extends AbstractTestRealmKeycloakTest {
RealmModel realm = currentSession.realms().getRealmByName("test"); RealmModel realm = currentSession.realms().getRealmByName("test");
UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm); UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm);
List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentials(realm, user); List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user)
.collect(Collectors.toList());
Assert.assertEquals(1, list.size()); Assert.assertEquals(1, list.size());
passwordId.set(list.get(0).getId()); passwordId.set(list.get(0).getId());
@ -60,7 +62,8 @@ public class CredentialModelTest extends AbstractTestRealmKeycloakTest {
UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm); UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm);
// Assert priorities: password, otp1, otp2 // Assert priorities: password, otp1, otp2
List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentials(realm, user); List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user)
.collect(Collectors.toList());
assertOrder(list, passwordId.get(), otp1Id.get(), otp2Id.get()); assertOrder(list, passwordId.get(), otp1Id.get(), otp2Id.get());
// Assert can't move password when newPreviousCredential not found // Assert can't move password when newPreviousCredential not found
@ -78,7 +81,8 @@ public class CredentialModelTest extends AbstractTestRealmKeycloakTest {
UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm); UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm);
// Assert priorities: password, otp2, otp1 // Assert priorities: password, otp2, otp1
List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentials(realm, user); List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user)
.collect(Collectors.toList());
assertOrder(list, passwordId.get(), otp2Id.get(), otp1Id.get()); assertOrder(list, passwordId.get(), otp2Id.get(), otp1Id.get());
// Move otp2 to the top // Move otp2 to the top
@ -90,7 +94,8 @@ public class CredentialModelTest extends AbstractTestRealmKeycloakTest {
UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm); UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm);
// Assert priorities: otp2, password, otp1 // Assert priorities: otp2, password, otp1
List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentials(realm, user); List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user)
.collect(Collectors.toList());
assertOrder(list, otp2Id.get(), passwordId.get(), otp1Id.get()); assertOrder(list, otp2Id.get(), passwordId.get(), otp1Id.get());
// Move password down // Move password down
@ -102,7 +107,8 @@ public class CredentialModelTest extends AbstractTestRealmKeycloakTest {
UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm); UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm);
// Assert priorities: otp2, otp1, password // Assert priorities: otp2, otp1, password
List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentials(realm, user); List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user)
.collect(Collectors.toList());
assertOrder(list, otp2Id.get(), otp1Id.get(), passwordId.get()); assertOrder(list, otp2Id.get(), otp1Id.get(), passwordId.get());
// Remove otp2 down two positions // Remove otp2 down two positions
@ -114,7 +120,8 @@ public class CredentialModelTest extends AbstractTestRealmKeycloakTest {
UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm); UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm);
// Assert priorities: otp2, otp1, password // Assert priorities: otp2, otp1, password
List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentials(realm, user); List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user)
.collect(Collectors.toList());
assertOrder(list, otp1Id.get(), passwordId.get(), otp2Id.get()); assertOrder(list, otp1Id.get(), passwordId.get(), otp2Id.get());
// Remove password // Remove password
@ -126,7 +133,8 @@ public class CredentialModelTest extends AbstractTestRealmKeycloakTest {
UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm); UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm);
// Assert priorities: otp2, password // Assert priorities: otp2, password
List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentials(realm, user); List<CredentialModel> list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user)
.collect(Collectors.toList());
assertOrder(list, otp1Id.get(), otp2Id.get()); assertOrder(list, otp1Id.get(), otp2Id.get());
}); });
} }