From edef93cd491e545de642fd39d0df0a53cecee830 Mon Sep 17 00:00:00 2001 From: Stefan Guilhen Date: Thu, 26 Nov 2020 10:25:06 -0300 Subject: [PATCH] [KEYCLOAK-16232] Streamify the UserCredentialStore and UserCredentialManager interfaces --- .../SecretQuestionCredentialProvider.java | 2 +- .../kerberos/KerberosFederationProvider.java | 10 +- .../storage/ldap/LDAPStorageProvider.java | 7 +- .../sssd/SSSDFederationProvider.java | 10 +- .../models/jpa/JpaUserCredentialStore.java | 43 ++++----- .../keycloak/models/jpa/JpaUserProvider.java | 23 ++--- .../jpa/JpaUserFederatedStorageProvider.java | 10 +- .../authentication/CredentialValidator.java | 4 +- .../models/utils/ModelToRepresentation.java | 3 +- .../policy/HistoryPasswordPolicyProvider.java | 42 ++++----- .../credential/CredentialInputUpdater.java | 35 +++++++ .../credential/CredentialProvider.java | 10 +- .../credential/UserCredentialStore.java | 63 +++++++++++++ .../models/UserCredentialManager.java | 61 ++++++++++++ .../AuthenticationSelectionResolver.java | 8 +- .../x509/UserIdentityToModelMapper.java | 3 +- .../requiredactions/UpdateTotp.java | 11 +-- .../requiredactions/WebAuthnRegister.java | 25 ++--- .../credential/OTPCredentialProvider.java | 2 +- .../PasswordCredentialProvider.java | 33 +++---- .../UserCredentialStoreManager.java | 38 +++----- .../WebAuthnCredentialProvider.java | 6 +- .../exportimport/util/ExportUtils.java | 8 +- .../freemarker/model/ApplicationsBean.java | 92 +++++++++++-------- .../account/freemarker/model/TotpBean.java | 6 +- .../login/freemarker/model/TotpBean.java | 4 +- .../login/freemarker/model/TotpLoginBean.java | 4 +- .../model/WebAuthnAuthenticatorsBean.java | 16 ++-- .../account/AccountCredentialResource.java | 9 +- .../account/LinkedAccountsResource.java | 8 +- .../resources/admin/UserResource.java | 6 +- .../BackwardsCompatibilityUserStorage.java | 46 +++++----- .../FailableHardcodedStorageProvider.java | 8 +- ...ssThroughFederatedUserStorageProvider.java | 12 +-- .../testsuite/federation/UserMapStorage.java | 7 +- .../testsuite/runonserver/RunHelpers.java | 5 +- .../account/AccountFormServiceTest.java | 4 +- .../keycloak/testsuite/cli/KcinitTest.java | 8 +- .../ldap/LDAPProvidersIntegrationTest.java | 5 +- ...BackwardsCompatibilityUserStorageTest.java | 5 +- .../federation/storage/UserStorageTest.java | 23 +++-- .../testsuite/forms/PasswordHashingTest.java | 3 +- .../testsuite/model/CredentialModelTest.java | 22 +++-- 43 files changed, 447 insertions(+), 303 deletions(-) diff --git a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java index 8913fba24e..68b9211da8 100644 --- a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java +++ b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java @@ -75,7 +75,7 @@ public class SecretQuestionCredentialProvider implements CredentialProvider 0; } @Override diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java index e1556c7b13..8605d3fbce 100755 --- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java +++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java @@ -23,7 +23,6 @@ import org.keycloak.credential.CredentialAuthentication; import org.keycloak.credential.CredentialInput; import org.keycloak.credential.CredentialInputUpdater; import org.keycloak.credential.CredentialInputValidator; -import org.keycloak.credential.CredentialModel; import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator; import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator; 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.UserLookupProvider; -import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Set; +import java.util.stream.Stream; /** * @author Marek Posolda @@ -52,7 +50,7 @@ import java.util.Set; public class KerberosFederationProvider implements UserStorageProvider, UserLookupProvider, CredentialInputValidator, - CredentialInputUpdater, + CredentialInputUpdater.Streams, CredentialAuthentication, ImportedUserValidation { @@ -146,8 +144,8 @@ public class KerberosFederationProvider implements UserStorageProvider, } @Override - public Set getDisableableCredentialTypes(RealmModel realm, UserModel user) { - return Collections.EMPTY_SET; + public Stream getDisableableCredentialTypesStream(RealmModel realm, UserModel user) { + return Stream.empty(); } @Override diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java index 7e46763613..7a00eae871 100755 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java @@ -18,7 +18,6 @@ package org.keycloak.storage.ldap; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -88,7 +87,7 @@ import org.keycloak.storage.user.UserRegistrationProvider; */ public class LDAPStorageProvider implements UserStorageProvider, CredentialInputValidator, - CredentialInputUpdater, + CredentialInputUpdater.Streams, CredentialAuthentication, UserLookupProvider, UserRegistrationProvider, @@ -687,8 +686,8 @@ public class LDAPStorageProvider implements UserStorageProvider, } @Override - public Set getDisableableCredentialTypes(RealmModel realm, UserModel user) { - return Collections.EMPTY_SET; + public Stream getDisableableCredentialTypesStream(RealmModel realm, UserModel user) { + return Stream.empty(); } public Set getSupportedCredentialTypes() { diff --git a/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java b/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java index ce040f58bd..bff35c3be6 100755 --- a/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java +++ b/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java @@ -21,7 +21,6 @@ import org.jboss.logging.Logger; import org.keycloak.credential.CredentialInput; import org.keycloak.credential.CredentialInputUpdater; import org.keycloak.credential.CredentialInputValidator; -import org.keycloak.credential.CredentialModel; import org.keycloak.federation.sssd.api.Sssd; import org.keycloak.federation.sssd.api.Sssd.User; 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.user.ImportedUserValidation; import org.keycloak.storage.user.UserLookupProvider; -import sun.security.util.Password; -import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.stream.Stream; /** * SPI provider implementation to retrieve data from SSSD and authenticate @@ -47,7 +45,7 @@ import java.util.Set; */ public class SSSDFederationProvider implements UserStorageProvider, UserLookupProvider, - CredentialInputUpdater, + CredentialInputUpdater.Streams, CredentialInputValidator, ImportedUserValidation { @@ -205,7 +203,7 @@ public class SSSDFederationProvider implements UserStorageProvider, } @Override - public Set getDisableableCredentialTypes(RealmModel realm, UserModel user) { - return Collections.EMPTY_SET; + public Stream getDisableableCredentialTypesStream(RealmModel realm, UserModel user) { + return Stream.empty(); } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java index 07b6bad27d..4852912d88 100644 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java @@ -30,16 +30,20 @@ import org.keycloak.models.utils.KeycloakModelUtils; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; -import java.util.ArrayList; import java.util.List; import javax.persistence.LockModeType; + +import java.util.Objects; import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.keycloak.utils.StreamsUtil.closing; /** * @author Bill Burke * @version $Revision: 1 $ */ -public class JpaUserCredentialStore implements UserCredentialStore { +public class JpaUserCredentialStore implements UserCredentialStore.Streams { // Typical priority difference between 2 credentials public static final int PRIORITY_DIFFERENCE = 10; @@ -106,31 +110,27 @@ public class JpaUserCredentialStore implements UserCredentialStore { } @Override - public List getStoredCredentials(RealmModel realm, UserModel user) { - List results = getStoredCredentialEntities(realm, user); - - // list is ordered correctly by priority (lowest priority value first) - return results.stream().map(this::toModel).collect(Collectors.toList()); + public Stream getStoredCredentialsStream(RealmModel realm, UserModel user) { + return this.getStoredCredentialEntities(realm, user).map(this::toModel); } - private List getStoredCredentialEntities(RealmModel realm, UserModel user) { + private Stream getStoredCredentialEntities(RealmModel realm, UserModel user) { UserEntity userEntity = em.getReference(UserEntity.class, user.getId()); TypedQuery query = em.createNamedQuery("credentialByUser", CredentialEntity.class) .setParameter("user", userEntity); - return query.getResultList(); + return closing(query.getResultStream()); } @Override - public List getStoredCredentialsByType(RealmModel realm, UserModel user, String type) { - return getStoredCredentials(realm, user).stream().filter(credential -> type.equals(credential.getType())).collect(Collectors.toList()); + public Stream getStoredCredentialsByTypeStream(RealmModel realm, UserModel user, String type) { + return getStoredCredentialsStream(realm, user).filter(credential -> Objects.equals(type, credential.getType())); } @Override public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) { - List results = getStoredCredentials(realm, user).stream().filter(credential -> - type.equals(credential.getType()) && name.equals(credential.getUserLabel())).collect(Collectors.toList()); - if (results.isEmpty()) return null; - return results.get(0); + return getStoredCredentialsStream(realm, user).filter(credential -> + Objects.equals(type, credential.getType()) && Objects.equals(name, credential.getUserLabel())) + .findFirst().orElse(null); } @Override @@ -151,7 +151,7 @@ public class JpaUserCredentialStore implements UserCredentialStore { entity.setUser(userRef); //add in linkedlist to last position - List credentials = getStoredCredentialEntities(realm, user); + List credentials = getStoredCredentialEntities(realm, user).collect(Collectors.toList()); int priority = credentials.isEmpty() ? PRIORITY_DIFFERENCE : credentials.get(credentials.size() - 1).getPriority() + PRIORITY_DIFFERENCE; entity.setPriority(priority); @@ -165,14 +165,11 @@ public class JpaUserCredentialStore implements UserCredentialStore { int currentPriority = entity.getPriority(); - List credentials = getStoredCredentialEntities(realm, user); - - // Decrease priority of all credentials after our - for (CredentialEntity cred : credentials) { + this.getStoredCredentialEntities(realm, user).forEach(cred -> { if (cred.getPriority() > currentPriority) { cred.setPriority(cred.getPriority() - PRIORITY_DIFFERENCE); } - } + }); em.remove(entity); em.flush(); @@ -182,11 +179,9 @@ public class JpaUserCredentialStore implements UserCredentialStore { ////Operations to handle the linked list of credentials @Override public boolean moveCredentialTo(RealmModel realm, UserModel user, String id, String newPreviousCredentialId) { - List sortedCreds = getStoredCredentialEntities(realm, user); // 1 - Create new list and move everything to it. - List newList = new ArrayList<>(); - newList.addAll(sortedCreds); + List newList = this.getStoredCredentialEntities(realm, user).collect(Collectors.toList()); // 2 - Find indexes of our and newPrevious credential int ourCredentialIndex = -1; diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java index 5224568fc0..b46d7d63c8 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java @@ -64,12 +64,10 @@ import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.Stream; import javax.persistence.LockModeType; @@ -82,7 +80,7 @@ import static org.keycloak.utils.StreamsUtil.closing; * @version $Revision: 1 $ */ @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_VERIFIED = "emailVerified"; @@ -1022,27 +1020,20 @@ public class JpaUserProvider implements UserProvider.Streams, UserCredentialStor } @Override - public List getStoredCredentials(RealmModel realm, UserModel user) { - return credentialStore.getStoredCredentials(realm, user); + public Stream getStoredCredentialsStream(RealmModel realm, UserModel user) { + return credentialStore.getStoredCredentialsStream(realm, user); } @Override - public List getStoredCredentialsByType(RealmModel realm, UserModel user, String type) { - List results; + public Stream getStoredCredentialsByTypeStream(RealmModel realm, UserModel user, String type) { UserEntity userEntity = userInEntityManagerContext(user.getId()); if (userEntity != null) { - // 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)) - .collect(Collectors.toList()); - List rtn = new LinkedList<>(); - for (CredentialEntity entity : results) { - rtn.add(toModel(entity)); - } - return rtn; + .map(this::toModel); } else { - return credentialStore.getStoredCredentialsByType(realm, user, type); + return credentialStore.getStoredCredentialsByTypeStream(realm, user, type); } } diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java index 2bf8ea588e..af79ae266b 100644 --- a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java @@ -71,7 +71,7 @@ import static org.keycloak.utils.StreamsUtil.closing; */ public class JpaUserFederatedStorageProvider implements UserFederatedStorageProvider.Streams, - UserCredentialStore { + UserCredentialStore.Streams { protected static final Logger logger = Logger.getLogger(JpaUserFederatedStorageProvider.class); @@ -690,13 +690,13 @@ public class JpaUserFederatedStorageProvider implements } @Override - public List getStoredCredentials(RealmModel realm, UserModel user) { - return getStoredCredentialsStream(realm, user.getId()).collect(Collectors.toList()); + public Stream getStoredCredentialsStream(RealmModel realm, UserModel user) { + return getStoredCredentialsStream(realm, user.getId()); } @Override - public List getStoredCredentialsByType(RealmModel realm, UserModel user, String type) { - return getStoredCredentialsByTypeStream(realm, user.getId(), type).collect(Collectors.toList()); + public Stream getStoredCredentialsByTypeStream(RealmModel realm, UserModel user, String type) { + return getStoredCredentialsByTypeStream(realm, user.getId(), type); } @Override diff --git a/server-spi-private/src/main/java/org/keycloak/authentication/CredentialValidator.java b/server-spi-private/src/main/java/org/keycloak/authentication/CredentialValidator.java index 82cd7dc528..7aa19f9971 100644 --- a/server-spi-private/src/main/java/org/keycloak/authentication/CredentialValidator.java +++ b/server-spi-private/src/main/java/org/keycloak/authentication/CredentialValidator.java @@ -7,11 +7,13 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import java.util.List; +import java.util.stream.Collectors; public interface CredentialValidator { T getCredentialProvider(KeycloakSession session); default List 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) { return getCredentialProvider(session).getType(); diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 8c63f74be2..cb6a4e6d53 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -188,7 +188,8 @@ public class ModelToRepresentation { rep.setEnabled(user.isEnabled()); rep.setEmailVerified(user.isEmailVerified()); 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.setNotBefore(session.users().getNotBeforeOfUser(realm, user)); rep.setRequiredActions(user.getRequiredActionsStream().collect(Collectors.toList())); diff --git a/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java index 7861fe233f..350b15ba4b 100644 --- a/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java @@ -28,6 +28,7 @@ import org.keycloak.models.credential.PasswordCredentialModel; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author Stian Thorgersen @@ -53,37 +54,36 @@ public class HistoryPasswordPolicyProvider implements PasswordPolicyProvider { PasswordPolicy policy = session.getContext().getRealm().getPasswordPolicy(); int passwordHistoryPolicyValue = policy.getPolicyConfig(PasswordPolicy.PASSWORD_HISTORY_ID); if (passwordHistoryPolicyValue != -1) { - List storedPasswords = session.userCredentialManager().getStoredCredentialsByType(realm, user, PasswordCredentialModel.TYPE); - for (CredentialModel cred : storedPasswords) { - PasswordCredentialModel passwordCredential = PasswordCredentialModel.createFromCredentialModel(cred); - PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, passwordCredential.getPasswordCredentialData().getAlgorithm()); - if (hash == null) continue; - if (hash.verify(password, passwordCredential)) { - return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue); - } + if (session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, PasswordCredentialModel.TYPE) + .map(PasswordCredentialModel::createFromCredentialModel) + .anyMatch(passwordCredential -> { + PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, + passwordCredential.getPasswordCredentialData().getAlgorithm()); + return hash != null && hash.verify(password, passwordCredential); + })) { + return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue); } if (passwordHistoryPolicyValue > 0) { - List passwordHistory = session.userCredentialManager().getStoredCredentialsByType(realm, user, PasswordCredentialModel.PASSWORD_HISTORY); - List recentPasswordHistory = getRecent(passwordHistory, passwordHistoryPolicyValue - 1); - for (CredentialModel cred : recentPasswordHistory) { - PasswordCredentialModel passwordCredential = PasswordCredentialModel.createFromCredentialModel(cred); - PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, passwordCredential.getPasswordCredentialData().getAlgorithm()); - if (hash.verify(password, passwordCredential)) { - return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue); - } - + if (this.getRecent(session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, PasswordCredentialModel.PASSWORD_HISTORY), + passwordHistoryPolicyValue - 1) + .map(PasswordCredentialModel::createFromCredentialModel) + .anyMatch(passwordCredential -> { + PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, + passwordCredential.getPasswordCredentialData().getAlgorithm()); + return hash.verify(password, passwordCredential); + })) { + return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue); } } } return null; } - private List getRecent(List passwordHistory, int limit) { - return passwordHistory.stream() + private Stream getRecent(Stream passwordHistory, int limit) { + return passwordHistory .sorted(CredentialModel.comparingByStartDateDesc()) - .limit(limit) - .collect(Collectors.toList()); + .limit(limit); } @Override diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java b/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java index 8b1093888e..081e30aade 100644 --- a/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java +++ b/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java @@ -20,6 +20,8 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author Bill Burke @@ -37,6 +39,39 @@ public interface CredentialInputUpdater { * @param realm * @param user * @return + * @deprecated Use {@link #getDisableableCredentialTypesStream(RealmModel, UserModel) getDisableableCredentialTypesStream} + * instead. */ + @Deprecated Set 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 getDisableableCredentialTypesStream(RealmModel realm, UserModel user) { + Set 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. + *

+ * 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 getDisableableCredentialTypes(RealmModel realm, UserModel user) { + return this.getDisableableCredentialTypesStream(realm, user).collect(Collectors.toSet()); + } + + @Override + Stream getDisableableCredentialTypesStream(RealmModel realm, UserModel user); + } } diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialProvider.java b/server-spi/src/main/java/org/keycloak/credential/CredentialProvider.java index 327d7110e6..0c4a9b0c4f 100644 --- a/server-spi/src/main/java/org/keycloak/credential/CredentialProvider.java +++ b/server-spi/src/main/java/org/keycloak/credential/CredentialProvider.java @@ -21,8 +21,6 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.provider.Provider; -import java.util.List; - /** * @author Bill Burke * @version $Revision: 1 $ @@ -43,11 +41,9 @@ public interface CredentialProvider extends Provider T getCredentialFromModel(CredentialModel model); default T getDefaultCredential(KeycloakSession session, RealmModel realm, UserModel user) { - List models = session.userCredentialManager().getStoredCredentialsByType(realm, user, getType()); - if (models.isEmpty()) { - return null; - } - return getCredentialFromModel(models.get(0)); + CredentialModel model = session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, getType()) + .findFirst().orElse(null); + return model != null ? getCredentialFromModel(model) : null; } CredentialTypeMetadata getCredentialTypeMetadata(CredentialTypeMetadataContext metadataContext); diff --git a/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java b/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java index 2be8482a57..0ca8497d49 100644 --- a/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java +++ b/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java @@ -21,6 +21,8 @@ import org.keycloak.models.UserModel; import org.keycloak.provider.Provider; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author Bill Burke @@ -31,11 +33,72 @@ public interface UserCredentialStore extends Provider { CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred); boolean removeStoredCredential(RealmModel realm, UserModel user, String id); CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id); + + /** + * @deprecated Use {@link #getStoredCredentialsStream(RealmModel, UserModel) getStoredCredentialsStream} instead. + */ + @Deprecated List 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 getStoredCredentialsStream(RealmModel realm, UserModel user) { + List result = this.getStoredCredentials(realm, user); + return result != null ? result.stream() : Stream.empty(); + } + + /** + * @deprecated Use {@link #getStoredCredentialsByTypeStream(RealmModel, UserModel, String) getStoredCredentialsByTypeStream} + * instead. + */ + @Deprecated List 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 getStoredCredentialsByTypeStream(RealmModel realm, UserModel user, String type) { + List result = this.getStoredCredentialsByType(realm, user, type); + return result != null ? result.stream() : Stream.empty(); + } + CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type); //list operations 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. + *

+ * 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 getStoredCredentials(RealmModel realm, UserModel user) { + return this.getStoredCredentialsStream(realm, user).collect(Collectors.toList()); + } + + @Override + Stream getStoredCredentialsStream(RealmModel realm, UserModel user); + + @Override + default List getStoredCredentialsByType(RealmModel realm, UserModel user, String type) { + return this.getStoredCredentialsByTypeStream(realm, user, type).collect(Collectors.toList()); + } + + @Override + Stream getStoredCredentialsByTypeStream(RealmModel realm, UserModel user, String type); + } } diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java index d555f15dfd..314d76cd49 100644 --- a/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java +++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java @@ -22,6 +22,8 @@ import org.keycloak.credential.UserCredentialStore; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author Bill Burke @@ -95,9 +97,25 @@ public interface UserCredentialManager extends UserCredentialStore { * @param realm * @param user * @return + * @deprecated Use {@link #getDisableableCredentialTypesStream(RealmModel, UserModel) getDisableableCredentialTypesStream} + * instead. */ + @Deprecated Set 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 getDisableableCredentialTypesStream(RealmModel realm, UserModel user) { + Set 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, * 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 * * @return + * @deprecated Use {@link #getConfiguredUserStorageCredentialTypesStream(RealmModel, UserModel) getConfiguredUserStorageCredentialTypesStream} + * instead. */ + @Deprecated List 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. + *

+ * 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 getConfiguredUserStorageCredentialTypesStream(RealmModel realm, UserModel user) { + List 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. + *

+ * 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 getDisableableCredentialTypes(RealmModel realm, UserModel user) { + return this.getDisableableCredentialTypesStream(realm, user).collect(Collectors.toSet()); + } + + @Override + Stream getDisableableCredentialTypesStream(RealmModel realm, UserModel user); + + @Override + default List getConfiguredUserStorageCredentialTypes(RealmModel realm, UserModel user) { + return this.getConfiguredUserStorageCredentialTypesStream(realm, user).collect(Collectors.toList()); + } + + @Override + Stream getConfiguredUserStorageCredentialTypesStream(RealmModel realm, UserModel user); + } } diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationSelectionResolver.java b/services/src/main/java/org/keycloak/authentication/AuthenticationSelectionResolver.java index 7996b8dece..437131d29d 100644 --- a/services/src/main/java/org/keycloak/authentication/AuthenticationSelectionResolver.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticationSelectionResolver.java @@ -76,13 +76,9 @@ class AuthenticationSelectionResolver { //add credential authenticators in order if (processor.getAuthenticationSession().getAuthenticatedUser() != null) { - List credentials = processor.getSession().userCredentialManager() - .getStoredCredentials(processor.getRealm(), processor.getAuthenticationSession().getAuthenticatedUser()) - .stream() + authenticationSelectionList = processor.getSession().userCredentialManager() + .getStoredCredentialsStream(processor.getRealm(), processor.getAuthenticationSession().getAuthenticatedUser()) .filter(credential -> typeAuthExecMap.containsKey(credential.getType())) - .collect(Collectors.toList()); - - authenticationSelectionList = credentials.stream() .map(CredentialModel::getType) .distinct() .map(credentialType -> new AuthenticationSelectionOption(processor.getSession(), typeAuthExecMap.get(credentialType))) diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/UserIdentityToModelMapper.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/UserIdentityToModelMapper.java index 6ae819c933..74b061b6e6 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/UserIdentityToModelMapper.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/UserIdentityToModelMapper.java @@ -20,6 +20,7 @@ package org.keycloak.authentication.authenticators.x509; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -67,7 +68,7 @@ public abstract class UserIdentityToModelMapper { for (int i = 1; i <_customAttributes.size(); ++i) { String customAttribute = _customAttributes.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 users = usersStream.collect(Collectors.toList()); if (users.size() > 1) { diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java index a5e6abdfcd..f0a8ae2332 100644 --- a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java @@ -43,8 +43,7 @@ import org.keycloak.utils.CredentialHelper; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; -import java.util.Collections; -import java.util.List; +import java.util.stream.Stream; /** * @author Bill Burke @@ -96,10 +95,10 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory return; } OTPCredentialProvider otpCredentialProvider = (OTPCredentialProvider) context.getSession().getProvider(CredentialProvider.class, "keycloak-otp"); - final List otpCredentials = (otpCredentialProvider.isConfiguredFor(context.getRealm(), context.getUser())) - ? context.getSession().userCredentialManager().getStoredCredentialsByType(context.getRealm(), context.getUser(), OTPCredentialModel.TYPE) - : Collections.EMPTY_LIST; - if (otpCredentials.size() >= 1 && Validation.isBlank(userLabel)) { + final Stream otpCredentials = (otpCredentialProvider.isConfiguredFor(context.getRealm(), context.getUser())) + ? context.getSession().userCredentialManager().getStoredCredentialsByTypeStream(context.getRealm(), context.getUser(), OTPCredentialModel.TYPE) + : Stream.empty(); + if (otpCredentials.count() >= 1 && Validation.isBlank(userLabel)) { Response challenge = context.form() .setAttribute("mode", mode) .addError(new FormMessage(Validation.FIELD_OTP_LABEL, Messages.MISSING_TOTP_DEVICE_NAME)) diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java b/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java index 83147cd7bc..b2b7941c8d 100644 --- a/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java @@ -22,6 +22,7 @@ import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; @@ -47,7 +48,6 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.UserModel; import org.keycloak.models.WebAuthnPolicy; -import com.webauthn4j.WebAuthnManager; import com.webauthn4j.converter.util.ObjectConverter; import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData; import com.webauthn4j.data.attestation.statement.AttestationStatement; @@ -125,15 +125,11 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis String excludeCredentialIds = ""; if (avoidSameAuthenticatorRegister) { - List webAuthnCredentials = session.userCredentialManager().getStoredCredentialsByType(context.getRealm(), userModel, getCredentialType()); - List webAuthnCredentialPubKeyIds = webAuthnCredentials.stream().map(credentialModel -> { - - WebAuthnCredentialModel credModel = WebAuthnCredentialModel.createFromCredentialModel(credentialModel); - return Base64Url.encodeBase64ToBase64Url(credModel.getWebAuthnCredentialData().getCredentialId()); - - }).collect(Collectors.toList()); - - excludeCredentialIds = stringifyExcludeCredentialIds(webAuthnCredentialPubKeyIds); + excludeCredentialIds = session.userCredentialManager().getStoredCredentialsByTypeStream(context.getRealm(), userModel, getCredentialType()) + .map(credentialModel -> { + WebAuthnCredentialModel credModel = WebAuthnCredentialModel.createFromCredentialModel(credentialModel); + return Base64Url.encodeBase64ToBase64Url(credModel.getWebAuthnCredentialData().getCredentialId()); + }).collect(Collectors.joining(",")); } String isSetRetry = context.getHttpRequest().getDecodedFormParameters().getFirst(WebAuthnConstants.IS_SET_RETRY); @@ -300,15 +296,6 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis return sb.toString(); } - private String stringifyExcludeCredentialIds(List 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) { AttestedCredentialData attestedCredentialData = response.getAttestationObject().getAuthenticatorData().getAttestedCredentialData(); AttestationStatement attestationStatement = response.getAttestationObject().getAttestationStatement(); diff --git a/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java b/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java index f30a65e66f..1f1020e936 100644 --- a/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java +++ b/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java @@ -92,7 +92,7 @@ public class OTPCredentialProvider implements CredentialProvider 0; } public boolean isConfiguredFor(RealmModel realm, UserModel user){ diff --git a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java index c4dc9145a1..424f668c68 100644 --- a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java +++ b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java @@ -32,15 +32,16 @@ import org.keycloak.models.cache.UserCache; import org.keycloak.policy.PasswordPolicyManagerProvider; import org.keycloak.policy.PolicyError; -import java.util.Collections; import java.util.List; -import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author Bill Burke * @version $Revision: 1 $ */ -public class PasswordCredentialProvider implements CredentialProvider, CredentialInputUpdater, CredentialInputValidator, OnUserCache { +public class PasswordCredentialProvider implements CredentialProvider, CredentialInputUpdater.Streams, + CredentialInputValidator, OnUserCache { public static final String PASSWORD_CACHE_KEY = PasswordCredentialProvider.class.getName() + "." + PasswordCredentialModel.TYPE; private static final Logger logger = Logger.getLogger(PasswordCredentialProvider.class); @@ -64,7 +65,7 @@ public class PasswordCredentialProvider implements CredentialProvider passwordHistoryList = getCredentialStore().getStoredCredentialsByType(realm, user, PasswordCredentialModel.PASSWORD_HISTORY); final int passwordHistoryListMaxSize = Math.max(0, expiredPasswordsPolicyValue - 1); - if (passwordHistoryList.size() > passwordHistoryListMaxSize) { - passwordHistoryList.stream() - .sorted(CredentialModel.comparingByStartDateDesc()) - .skip(passwordHistoryListMaxSize) - .forEach(p -> getCredentialStore().removeStoredCredential(realm, user, p.getId())); - } + getCredentialStore().getStoredCredentialsByTypeStream(realm, user, PasswordCredentialModel.PASSWORD_HISTORY) + .sorted(CredentialModel.comparingByStartDateDesc()) + .skip(passwordHistoryListMaxSize) + .collect(Collectors.toList()) + .forEach(p -> getCredentialStore().removeStoredCredential(realm, user, p.getId())); UserCache userCache = session.userCache(); if (userCache != null) { @@ -220,8 +219,8 @@ public class PasswordCredentialProvider implements CredentialProvider getDisableableCredentialTypes(RealmModel realm, UserModel user) { - return Collections.emptySet(); + public Stream getDisableableCredentialTypesStream(RealmModel realm, UserModel user) { + return Stream.empty(); } @Override @@ -282,11 +281,9 @@ public class PasswordCredentialProvider implements CredentialProvider passwords = getCredentialStore().getStoredCredentialsByType(realm, user, getType()); - if (passwords != null) { - user.getCachedWith().put(PASSWORD_CACHE_KEY, passwords); - } - + List passwords = getCredentialStore().getStoredCredentialsByTypeStream(realm, user, getType()) + .collect(Collectors.toList()); + user.getCachedWith().put(PASSWORD_CACHE_KEY, passwords); } @Override diff --git a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java index d55de3c1fa..b6c64094c9 100644 --- a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java +++ b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java @@ -25,7 +25,6 @@ import org.keycloak.models.UserModel; import org.keycloak.models.cache.CachedUserModel; import org.keycloak.models.cache.OnUserCache; import org.keycloak.models.cache.UserCache; -import org.keycloak.provider.ProviderFactory; import org.keycloak.storage.AbstractStorageManager; import org.keycloak.storage.StorageId; import org.keycloak.storage.UserStorageProvider; @@ -33,12 +32,8 @@ import org.keycloak.storage.UserStorageProviderFactory; import org.keycloak.storage.UserStorageProviderModel; import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.Objects; -import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -46,7 +41,8 @@ import java.util.stream.Stream; * @author Bill Burke * @version $Revision: 1 $ */ -public class UserCredentialStoreManager extends AbstractStorageManager implements UserCredentialManager, OnUserCache { +public class UserCredentialStoreManager extends AbstractStorageManager + implements UserCredentialManager.Streams, OnUserCache { public UserCredentialStoreManager(KeycloakSession session) { super(session, UserStorageProviderFactory.class, UserStorageProvider.class, UserStorageProviderModel::new, "user"); @@ -89,13 +85,13 @@ public class UserCredentialStoreManager extends AbstractStorageManager getStoredCredentials(RealmModel realm, UserModel user) { - return getStoreForUser(user).getStoredCredentials(realm, user); + public Stream getStoredCredentialsStream(RealmModel realm, UserModel user) { + return getStoreForUser(user).getStoredCredentialsStream(realm, user); } @Override - public List getStoredCredentialsByType(RealmModel realm, UserModel user, String type) { - return getStoreForUser(user).getStoredCredentialsByType(realm, user, type); + public Stream getStoredCredentialsByTypeStream(RealmModel realm, UserModel user, String type) { + return getStoreForUser(user).getStoredCredentialsByTypeStream(realm, user, type); } @Override @@ -218,23 +214,20 @@ public class UserCredentialStoreManager extends AbstractStorageManager getDisableableCredentialTypes(RealmModel realm, UserModel user) { - Set types = new HashSet<>(); + public Stream getDisableableCredentialTypesStream(RealmModel realm, UserModel user) { + Stream types = Stream.empty(); String providerId = StorageId.isLocalStorage(user) ? user.getFederationLink() : StorageId.resolveProviderId(user); if (providerId != null) { UserStorageProviderModel model = getStorageProviderModel(realm, providerId); - if (model == null || !model.isEnabled()) return Collections.EMPTY_SET; + if (model == null || !model.isEnabled()) return types; 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) - .map(updater -> updater.getDisableableCredentialTypes(realm, user)) - .flatMap(Set::stream) - .collect(Collectors.toSet())); - - return types; + return Stream.concat(types, getCredentialProviders(session, CredentialInputUpdater.class) + .flatMap(updater -> updater.getDisableableCredentialTypesStream(realm, user))) + .distinct(); } @Override @@ -298,10 +291,9 @@ public class UserCredentialStoreManager extends AbstractStorageManager getConfiguredUserStorageCredentialTypes(RealmModel realm, UserModel user) { + public Stream getConfiguredUserStorageCredentialTypesStream(RealmModel realm, UserModel user) { return getCredentialProviders(session, CredentialProvider.class).map(CredentialProvider::getType) - .filter(credentialType -> UserStorageCredentialConfigured.CONFIGURED == isConfiguredThroughUserStorage(realm, user, credentialType)) - .collect(Collectors.toList()); + .filter(credentialType -> UserStorageCredentialConfigured.CONFIGURED == isConfiguredThroughUserStorage(realm, user, credentialType)); } @Override diff --git a/services/src/main/java/org/keycloak/credential/WebAuthnCredentialProvider.java b/services/src/main/java/org/keycloak/credential/WebAuthnCredentialProvider.java index c9736d8644..d8dc53e449 100644 --- a/services/src/main/java/org/keycloak/credential/WebAuthnCredentialProvider.java +++ b/services/src/main/java/org/keycloak/credential/WebAuthnCredentialProvider.java @@ -157,7 +157,7 @@ public class WebAuthnCredentialProvider implements CredentialProvider 0; } @@ -224,9 +224,7 @@ public class WebAuthnCredentialProvider implements CredentialProvider getWebAuthnCredentialModelList(RealmModel realm, UserModel user) { - List credentialModels = session.userCredentialManager().getStoredCredentialsByType(realm, user, getType()); - - return credentialModels.stream() + return session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, getType()) .map(this::getCredentialInputFromCredentialModel) .collect(Collectors.toList()); } diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java index a4a09ce373..ba9367dd63 100755 --- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java +++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java @@ -495,12 +495,8 @@ public class ExportUtils { // Credentials - extra security, do not export credentials if service accounts if (internal) { - List creds = session.userCredentialManager().getStoredCredentials(realm, user); - List credReps = new ArrayList<>(); - for (CredentialModel cred : creds) { - CredentialRepresentation credRep = exportCredential(cred); - credReps.add(credRep); - } + List credReps = session.userCredentialManager().getStoredCredentialsStream(realm, user) + .map(ExportUtils::exportCredential).collect(Collectors.toList()); userRep.setCredentials(credReps); } diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java index f5710c227c..67fd692b77 100755 --- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java +++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java @@ -54,45 +54,7 @@ public class ApplicationsBean { this.applications = this.getApplications(session, realm, user) .filter(client -> !isAdminClient(client) || AdminPermissions.realms(session, realm, user).isAdmin()) - .map(client -> { - - // Construct scope parameter with all optional scopes to see all potentially available roles - Stream allClientScopes = Stream.concat( - client.getClientScopes(true, true).values().stream(), - client.getClientScopes(false, true).values().stream()); - allClientScopes = Stream.concat(allClientScopes, Stream.of(client)).distinct(); - - Set 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 realmRolesAvailable = new LinkedList<>(); - MultivaluedHashMap resourceRolesAvailable = new MultivaluedHashMap<>(); - processRoles(availableRoles, realmRolesAvailable, resourceRolesAvailable); - - List orderedScopes = new LinkedList<>(); - if (client.isConsentRequired()) { - UserConsentModel consent = session.users().getConsentByClient(realm, user.getId(), client.getId()); - - if (consent != null) { - orderedScopes.addAll(consent.getGrantedClientScopes()); - } - } - List clientScopesGranted = orderedScopes.stream() - .sorted(OrderedModel.OrderedModelComparator.getInstance()) - .map(ClientScopeModel::getConsentScreenText) - .collect(Collectors.toList()); - - List additionalGrants = new ArrayList<>(); - if (offlineClients.contains(client)) { - additionalGrants.add("${offlineToken}"); - } - return new ApplicationEntry(session, realmRolesAvailable, resourceRolesAvailable, client, clientScopesGranted, additionalGrants); - }) + .map(client -> toApplicationEntry(session, realm, user, client, offlineClients)) .filter(Objects::nonNull) .collect(Collectors.toList()); } @@ -204,4 +166,56 @@ public class ApplicationsBean { 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 offlineClients) { + + // Construct scope parameter with all optional scopes to see all potentially available roles + Stream allClientScopes = Stream.concat( + client.getClientScopes(true, true).values().stream(), + client.getClientScopes(false, true).values().stream()); + allClientScopes = Stream.concat(allClientScopes, Stream.of(client)).distinct(); + + Set 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 realmRolesAvailable = new LinkedList<>(); + MultivaluedHashMap resourceRolesAvailable = new MultivaluedHashMap<>(); + processRoles(availableRoles, realmRolesAvailable, resourceRolesAvailable); + + List orderedScopes = new LinkedList<>(); + if (client.isConsentRequired()) { + UserConsentModel consent = session.users().getConsentByClient(realm, user.getId(), client.getId()); + + if (consent != null) { + orderedScopes.addAll(consent.getGrantedClientScopes()); + } + } + List clientScopesGranted = orderedScopes.stream() + .sorted(OrderedModel.OrderedModelComparator.getInstance()) + .map(ClientScopeModel::getConsentScreenText) + .collect(Collectors.toList()); + + List additionalGrants = new ArrayList<>(); + if (offlineClients.contains(client)) { + additionalGrants.add("${offlineToken}"); + } + return new ApplicationEntry(session, realmRolesAvailable, resourceRolesAvailable, client, clientScopesGranted, additionalGrants); + } } diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/TotpBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/TotpBean.java index 34937635c2..a769e6a5a8 100644 --- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/TotpBean.java +++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/TotpBean.java @@ -18,8 +18,6 @@ package org.keycloak.forms.account.freemarker.model; import org.keycloak.credential.CredentialModel; -import org.keycloak.credential.CredentialProvider; -import org.keycloak.credential.OTPCredentialProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.models.OTPPolicy; import org.keycloak.models.RealmModel; @@ -33,6 +31,7 @@ import org.keycloak.utils.TotpUtils; import javax.ws.rs.core.UriBuilder; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import static org.keycloak.utils.CredentialHelper.createUserStorageCredentialRepresentation; @@ -54,7 +53,8 @@ public class TotpBean { this.uriBuilder = uriBuilder; this.enabled = session.userCredentialManager().isConfiguredFor(realm, user, OTPCredentialModel.TYPE); if (enabled) { - List otpCredentials = session.userCredentialManager().getStoredCredentialsByType(realm, user, OTPCredentialModel.TYPE); + List otpCredentials = session.userCredentialManager() + .getStoredCredentialsByTypeStream(realm, user, OTPCredentialModel.TYPE).collect(Collectors.toList()); if (otpCredentials.isEmpty()) { // Credential is configured on userStorage side. Create the "fake" credential similar like we do for the new account console diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/model/TotpBean.java b/services/src/main/java/org/keycloak/forms/login/freemarker/model/TotpBean.java index 82c8eabd1d..d712a134b7 100755 --- a/services/src/main/java/org/keycloak/forms/login/freemarker/model/TotpBean.java +++ b/services/src/main/java/org/keycloak/forms/login/freemarker/model/TotpBean.java @@ -28,6 +28,7 @@ import org.keycloak.utils.TotpUtils; import javax.ws.rs.core.UriBuilder; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; /** * Used for UpdateTotp required action @@ -49,7 +50,8 @@ public class TotpBean { this.uriBuilder = uriBuilder; this.enabled = session.userCredentialManager().isConfiguredFor(realm, user, OTPCredentialModel.TYPE); if (enabled) { - otpCredentials = session.userCredentialManager().getStoredCredentialsByType(realm, user, OTPCredentialModel.TYPE); + otpCredentials = session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, OTPCredentialModel.TYPE) + .collect(Collectors.toList()); } else { otpCredentials = Collections.EMPTY_LIST; } diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/model/TotpLoginBean.java b/services/src/main/java/org/keycloak/forms/login/freemarker/model/TotpLoginBean.java index aec44e2243..e6b1b709e9 100644 --- a/services/src/main/java/org/keycloak/forms/login/freemarker/model/TotpLoginBean.java +++ b/services/src/main/java/org/keycloak/forms/login/freemarker/model/TotpLoginBean.java @@ -42,10 +42,8 @@ public class TotpLoginBean { private final List userOtpCredentials; public TotpLoginBean(KeycloakSession session, RealmModel realm, UserModel user, String selectedCredentialId) { - List userOtpCredentials = session.userCredentialManager() - .getStoredCredentialsByType(realm, user, OTPCredentialModel.TYPE); - this.userOtpCredentials = userOtpCredentials.stream() + this.userOtpCredentials = session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, OTPCredentialModel.TYPE) .map(OTPCredential::new) .collect(Collectors.toList()); diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/model/WebAuthnAuthenticatorsBean.java b/services/src/main/java/org/keycloak/forms/login/freemarker/model/WebAuthnAuthenticatorsBean.java index 94b6ed65a9..eda8ff6f57 100644 --- a/services/src/main/java/org/keycloak/forms/login/freemarker/model/WebAuthnAuthenticatorsBean.java +++ b/services/src/main/java/org/keycloak/forms/login/freemarker/model/WebAuthnAuthenticatorsBean.java @@ -17,9 +17,9 @@ package org.keycloak.forms.login.freemarker.model; import java.util.LinkedList; import java.util.List; +import java.util.stream.Collectors; import org.keycloak.common.util.Base64Url; -import org.keycloak.credential.CredentialModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; @@ -30,13 +30,13 @@ public class WebAuthnAuthenticatorsBean { public WebAuthnAuthenticatorsBean(KeycloakSession session, RealmModel realm, UserModel user, String credentialType) { // should consider multiple credentials in the future, but only single credential supported now. - for (CredentialModel credential : session.userCredentialManager().getStoredCredentialsByType(realm, user, credentialType)) { - WebAuthnCredentialModel webAuthnCredential = WebAuthnCredentialModel.createFromCredentialModel(credential); - - String credentialId = Base64Url.encodeBase64ToBase64Url(webAuthnCredential.getWebAuthnCredentialData().getCredentialId()); - String label = (webAuthnCredential.getUserLabel()==null || webAuthnCredential.getUserLabel().isEmpty()) ? "label missing" : webAuthnCredential.getUserLabel(); - authenticators.add(new WebAuthnAuthenticatorBean(credentialId, label)); - } + this.authenticators = session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, credentialType) + .map(WebAuthnCredentialModel::createFromCredentialModel) + .map(webAuthnCredential -> { + String credentialId = Base64Url.encodeBase64ToBase64Url(webAuthnCredential.getWebAuthnCredentialData().getCredentialId()); + String label = (webAuthnCredential.getUserLabel()==null || webAuthnCredential.getUserLabel().isEmpty()) ? "label missing" : webAuthnCredential.getUserLabel(); + return new WebAuthnAuthenticatorBean(credentialId, label); + }).collect(Collectors.toList()); } public List getAuthenticators() { diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountCredentialResource.java b/services/src/main/java/org/keycloak/services/resources/account/AccountCredentialResource.java index 0484064ae1..04b83faecd 100644 --- a/services/src/main/java/org/keycloak/services/resources/account/AccountCredentialResource.java +++ b/services/src/main/java/org/keycloak/services/resources/account/AccountCredentialResource.java @@ -170,14 +170,9 @@ public class AccountCredentialResource { .collect(Collectors.toList()); Set enabledCredentialTypes = getEnabledCredentialTypes(credentialProviders); - List models = includeUserCredentials ? session.userCredentialManager().getStoredCredentials(realm, user) : null; - + Stream modelsStream = includeUserCredentials ? session.userCredentialManager().getStoredCredentialsStream(realm, user) : Stream.empty(); // Don't return secrets from REST endpoint - if (models != null) { - for (CredentialModel credential : models) { - credential.setSecretData(null); - } - } + List models = modelsStream.peek(model -> model.setSecretData(null)).collect(Collectors.toList()); Function toCredentialContainer = (credentialProvider) -> { CredentialTypeMetadataContext ctx = CredentialTypeMetadataContext.builder() diff --git a/services/src/main/java/org/keycloak/services/resources/account/LinkedAccountsResource.java b/services/src/main/java/org/keycloak/services/resources/account/LinkedAccountsResource.java index 14ab906de6..9665ab5be6 100644 --- a/services/src/main/java/org/keycloak/services/resources/account/LinkedAccountsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/account/LinkedAccountsResource.java @@ -19,7 +19,13 @@ package org.keycloak.services.resources.account; import java.net.URI; import java.nio.charset.StandardCharsets; 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.Stream; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java index ae0a8a7152..1c5cc64a05 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java @@ -600,7 +600,7 @@ public class UserResource { @Produces(MediaType.APPLICATION_JSON) public Stream credentials(){ auth.users().requireManage(user); - return session.userCredentialManager().getStoredCredentials(realm, user).stream() + return session.userCredentialManager().getStoredCredentialsStream(realm, user) .peek(model -> model.setSecretData(null)) .map(ModelToRepresentation::toRepresentation); } @@ -616,11 +616,11 @@ public class UserResource { @Path("configured-user-storage-credential-types") @NoCache @Produces(MediaType.APPLICATION_JSON) - public List getConfiguredUserStorageCredentialTypes() { + public Stream getConfiguredUserStorageCredentialTypes() { // 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. auth.users().requireManage(user); - return session.userCredentialManager().getConfiguredUserStorageCredentialTypes(realm, user); + return session.userCredentialManager().getConfiguredUserStorageCredentialTypesStream(realm, user); } diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorage.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorage.java index 89e7585c5f..9f919228f0 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorage.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorage.java @@ -18,10 +18,13 @@ package org.keycloak.testsuite.federation; +import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Stream; +import java.util.stream.Collectors; import org.jboss.logging.Logger; import org.keycloak.common.util.Time; @@ -57,7 +60,7 @@ import org.keycloak.storage.user.UserRegistrationProvider; * @author Marek Posolda */ public class BackwardsCompatibilityUserStorage implements UserLookupProvider, UserStorageProvider, UserRegistrationProvider, - CredentialInputUpdater, CredentialInputValidator, UserQueryProvider.Streams { + CredentialInputUpdater, CredentialInputValidator, UserQueryProvider { 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) { - return new AbstractUserAdapterFederatedStorage.Streams(session, realm, model) { + return new AbstractUserAdapterFederatedStorage(session, realm, model) { @Override public String getUsername() { return username; @@ -316,57 +319,58 @@ public class BackwardsCompatibilityUserStorage implements UserLookupProvider, Us } @Override - public Stream getUsersStream(RealmModel realm) { - return getUsersStream(realm, -1, -1); + public List getUsers(RealmModel realm) { + return getUsers(realm, -1, -1); } @Override - public Stream getUsersStream(RealmModel realm, int firstResult, int maxResults) { + public List getUsers(RealmModel realm, int firstResult, int maxResults) { return users.values() .stream() .skip(firstResult).limit(maxResults) - .map(myUser -> createUser(realm, myUser.username)); + .map(myUser -> createUser(realm, myUser.username)) + .collect(Collectors.toList()); } @Override - public Stream searchForUserStream(String search, RealmModel realm) { - return searchForUserStream(search, realm, -1, -1); + public List searchForUser(String search, RealmModel realm) { + return searchForUser(search, realm, -1, -1); } @Override - public Stream searchForUserStream(String search, RealmModel realm, int firstResult, int maxResults) { + public List searchForUser(String search, RealmModel realm, int firstResult, int maxResults) { UserModel user = getUserByUsername(search, realm); - return user == null ? Stream.empty() : Stream.of(user); + return user == null ? Collections.emptyList() : Arrays.asList(user); } @Override - public Stream searchForUserStream(Map params, RealmModel realm) { + public List searchForUser(Map params, RealmModel realm) { // Assume that this is not supported - return Stream.empty(); + return Collections.emptyList(); } @Override - public Stream searchForUserStream(Map params, RealmModel realm, int firstResult, int maxResults) { + public List searchForUser(Map params, RealmModel realm, int firstResult, int maxResults) { // Assume that this is not supported - return Stream.empty(); + return Collections.emptyList(); } @Override - public Stream getGroupMembersStream(RealmModel realm, GroupModel group, int firstResult, int maxResults) { + public List getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) { // Assume that this is not supported - return Stream.empty(); + return Collections.emptyList(); } @Override - public Stream getGroupMembersStream(RealmModel realm, GroupModel group) { + public List getGroupMembers(RealmModel realm, GroupModel group) { // Assume that this is not supported - return Stream.empty(); + return Collections.emptyList(); } @Override - public Stream searchForUserByUserAttributeStream(String attrName, String attrValue, RealmModel realm) { + public List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) { // Assume that this is not supported - return Stream.empty(); + return Collections.emptyList(); } @Override diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/FailableHardcodedStorageProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/FailableHardcodedStorageProvider.java index cf0ef1c653..6a93f2d358 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/FailableHardcodedStorageProvider.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/FailableHardcodedStorageProvider.java @@ -33,10 +33,8 @@ import org.keycloak.storage.user.ImportedUserValidation; import org.keycloak.storage.user.UserLookupProvider; import org.keycloak.storage.user.UserQueryProvider; -import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Stream; /** @@ -44,7 +42,7 @@ import java.util.stream.Stream; * @version $Revision: 1 $ */ public class FailableHardcodedStorageProvider implements UserStorageProvider, UserLookupProvider, UserQueryProvider.Streams, - ImportedUserValidation, CredentialInputUpdater, CredentialInputValidator { + ImportedUserValidation, CredentialInputUpdater.Streams, CredentialInputValidator { public static String username = "billb"; public static String password = "password"; @@ -97,9 +95,9 @@ public class FailableHardcodedStorageProvider implements UserStorageProvider, Us } @Override - public Set getDisableableCredentialTypes(RealmModel realm, UserModel user) { + public Stream getDisableableCredentialTypesStream(RealmModel realm, UserModel user) { checkForceFail(); - return Collections.EMPTY_SET; + return Stream.empty(); } @Override diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/PassThroughFederatedUserStorageProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/PassThroughFederatedUserStorageProvider.java index 6cddd6cb6a..810310971d 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/PassThroughFederatedUserStorageProvider.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/PassThroughFederatedUserStorageProvider.java @@ -23,7 +23,6 @@ import org.keycloak.credential.CredentialInputValidator; import org.keycloak.credential.CredentialModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; -import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.models.credential.PasswordCredentialModel; import org.keycloak.storage.StorageId; @@ -32,10 +31,10 @@ import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage; import org.keycloak.storage.user.UserLookupProvider; import java.util.Collections; -import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; /** @@ -83,12 +82,9 @@ public class PassThroughFederatedUserStorageProvider implements if (INITIAL_PASSWORD.equals(input.getChallengeResponse())) { return true; } - Optional existing = session.userFederatedStorage() - .getStoredCredentialsByTypeStream(realm, user.getId(), "CLEAR_TEXT_PASSWORD") - .findFirst(); - if (existing.isPresent()) - return existing.get().getSecretData().equals("{\"value\":\"" + input.getChallengeResponse() + "\"}"); - return false; + return session.userFederatedStorage().getStoredCredentialsByTypeStream(realm, user.getId(), "CLEAR_TEXT_PASSWORD") + .map(credentialModel -> credentialModel.getSecretData()) + .anyMatch(Predicate.isEqual("{\"value\":\"" + input.getChallengeResponse() + "\"}")); } return false; } diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorage.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorage.java index eb183d3f00..bb5bdb85d5 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorage.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorage.java @@ -39,7 +39,6 @@ import org.keycloak.storage.user.UserLookupProvider; import org.keycloak.storage.user.UserQueryProvider; import org.keycloak.storage.user.UserRegistrationProvider; -import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -54,7 +53,7 @@ import static org.keycloak.storage.UserStorageProviderModel.IMPORT_ENABLED; * @author Bill Burke * @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 { private static final Logger log = Logger.getLogger(UserMapStorage.class); @@ -174,8 +173,8 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider, } @Override - public Set getDisableableCredentialTypes(RealmModel realm, UserModel user) { - return Collections.EMPTY_SET; + public Stream getDisableableCredentialTypesStream(RealmModel realm, UserModel user) { + return Stream.empty(); } @Override diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/runonserver/RunHelpers.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/runonserver/RunHelpers.java index 7dfe505f99..ed5824cfcf 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/runonserver/RunHelpers.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/runonserver/RunHelpers.java @@ -9,6 +9,7 @@ import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import java.util.List; +import java.util.stream.Collectors; /** * Created by st on 26.01.17. @@ -55,7 +56,9 @@ public class RunHelpers { return (FetchOnServer) session -> { RealmModel realm = session.getContext().getRealm(); UserModel user = session.users().getUserByUsername(username, realm); - List storedCredentialsByType = session.userCredentialManager().getStoredCredentialsByType(realm, user, CredentialRepresentation.PASSWORD); + List storedCredentialsByType = session.userCredentialManager() + .getStoredCredentialsByTypeStream(realm, user, CredentialRepresentation.PASSWORD) + .collect(Collectors.toList()); System.out.println(storedCredentialsByType.size()); return storedCredentialsByType.get(0); }; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java index 5c696fe3c7..5099d2b449 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java @@ -79,6 +79,7 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.hamcrest.Matchers; import org.junit.Assume; @@ -482,7 +483,8 @@ public class AccountFormServiceTest extends AbstractTestRealmKeycloakTest { RealmModel realm = session.getContext().getRealm(); UserModel user = session.users().getUserById(uId, realm); assertThat(user, Matchers.notNullValue()); - List storedCredentials = session.userCredentialManager().getStoredCredentials(realm, user); + List storedCredentials = session.userCredentialManager() + .getStoredCredentialsStream(realm, user).collect(Collectors.toList()); assertThat(storedCredentials, Matchers.hasSize(expectedNumberOfStoredCredentials)); }); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/KcinitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/KcinitTest.java index a9a68f282e..f8ed6f6e76 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/KcinitTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/KcinitTest.java @@ -64,6 +64,8 @@ import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; + import org.junit.Assume; import org.junit.BeforeClass; @@ -630,9 +632,9 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest { testingClient.server().run(session -> { RealmModel realm = session.realms().getRealmByName("test"); UserModel user = session.users().getUserByUsername("wburke", realm); - for (CredentialModel c: session.userCredentialManager().getStoredCredentialsByType(realm, user, OTPCredentialModel.TYPE)){ - session.userCredentialManager().removeStoredCredential(realm, user, c.getId()); - } + session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, OTPCredentialModel.TYPE) + .collect(Collectors.toList()) + .forEach(model -> session.userCredentialManager().removeStoredCredential(realm, user, model.getId())); }); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPProvidersIntegrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPProvidersIntegrationTest.java index 545afd6c64..e4a3a4c7d7 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPProvidersIntegrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPProvidersIntegrationTest.java @@ -892,7 +892,10 @@ public class LDAPProvidersIntegrationTest extends AbstractLDAPTest { UserCredentialModel cred = UserCredentialModel.password("Candycand1", true); 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.assertTrue(session.userCredentialManager().isValid(appRealm, user, cred)); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java index c9d7018a4f..e05e76d6b3 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java @@ -23,6 +23,7 @@ import java.net.URISyntaxException; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import javax.ws.rs.core.Response; @@ -292,8 +293,8 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest { testingClient.server().run(session -> { RealmModel realm1 = session.realms().getRealmByName("test"); UserModel user1 = session.users().getUserByUsername("otp1", realm1); - List keycloakDBCredentials = session.userCredentialManager().getStoredCredentials(realm1, user1); - Assert.assertTrue(keycloakDBCredentials.isEmpty()); + Assert.assertEquals(0, session.userCredentialManager() + .getStoredCredentialsStream(realm1, user1).count()); }); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java index 350562efe5..36d6053c91 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java @@ -63,6 +63,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; +import java.util.stream.Stream; import static java.util.Calendar.DAY_OF_WEEK; import static java.util.Calendar.HOUR_OF_DAY; @@ -879,8 +880,8 @@ public class UserStorageTest extends AbstractAuthTest { UserModel user = currentSession.users().getUserByUsername("thor", realm); Assert.assertFalse(StorageId.isLocalStorage(user)); - List list = currentSession.userCredentialManager().getStoredCredentials(realm, user); - org.keycloak.testsuite.Assert.assertEquals(0, list.size()); + Stream credentials = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user); + org.keycloak.testsuite.Assert.assertEquals(0, credentials.count()); // Create password 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); // Assert priorities: password, otp1, otp2 - List list = currentSession.userCredentialManager().getStoredCredentials(realm, user); + List list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user) + .collect(Collectors.toList()); assertOrder(list, passwordId.get(), otp1Id.get(), otp2Id.get()); // 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); // Assert priorities: password, otp2, otp1 - List list = currentSession.userCredentialManager().getStoredCredentials(realm, user); + List list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user) + .collect(Collectors.toList()); assertOrder(list, passwordId.get(), otp2Id.get(), otp1Id.get()); // Move otp2 to the top @@ -932,7 +935,8 @@ public class UserStorageTest extends AbstractAuthTest { UserModel user = currentSession.users().getUserByUsername("thor", realm); // Assert priorities: otp2, password, otp1 - List list = currentSession.userCredentialManager().getStoredCredentials(realm, user); + List list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user) + .collect(Collectors.toList()); assertOrder(list, otp2Id.get(), passwordId.get(), otp1Id.get()); // Move password down @@ -944,7 +948,8 @@ public class UserStorageTest extends AbstractAuthTest { UserModel user = currentSession.users().getUserByUsername("thor", realm); // Assert priorities: otp2, otp1, password - List list = currentSession.userCredentialManager().getStoredCredentials(realm, user); + List list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user) + .collect(Collectors.toList()); assertOrder(list, otp2Id.get(), otp1Id.get(), passwordId.get()); // Remove otp2 down two positions @@ -956,7 +961,8 @@ public class UserStorageTest extends AbstractAuthTest { UserModel user = currentSession.users().getUserByUsername("thor", realm); // Assert priorities: otp2, otp1, password - List list = currentSession.userCredentialManager().getStoredCredentials(realm, user); + List list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user) + .collect(Collectors.toList()); assertOrder(list, otp1Id.get(), passwordId.get(), otp2Id.get()); // Remove password @@ -968,7 +974,8 @@ public class UserStorageTest extends AbstractAuthTest { UserModel user = currentSession.users().getUserByUsername("thor", realm); // Assert priorities: otp2, password - List list = currentSession.userCredentialManager().getStoredCredentials(realm, user); + List list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user) + .collect(Collectors.toList()); assertOrder(list, otp1Id.get(), otp2Id.get()); }); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/PasswordHashingTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/PasswordHashingTest.java index 7750a26ff9..962a1c3430 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/PasswordHashingTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/PasswordHashingTest.java @@ -242,7 +242,8 @@ public class PasswordHashingTest extends AbstractTestRealmKeycloakTest { return testingClient.server("test").fetch(session -> { RealmModel realm = session.getContext().getRealm(); 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); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/CredentialModelTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/CredentialModelTest.java index 0f8d68adc0..60e455b51b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/CredentialModelTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/CredentialModelTest.java @@ -15,6 +15,7 @@ import org.keycloak.testsuite.arquillian.annotation.ModelTest; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; 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"); UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm); - List list = currentSession.userCredentialManager().getStoredCredentials(realm, user); + List list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user) + .collect(Collectors.toList()); Assert.assertEquals(1, list.size()); passwordId.set(list.get(0).getId()); @@ -60,7 +62,8 @@ public class CredentialModelTest extends AbstractTestRealmKeycloakTest { UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm); // Assert priorities: password, otp1, otp2 - List list = currentSession.userCredentialManager().getStoredCredentials(realm, user); + List list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user) + .collect(Collectors.toList()); assertOrder(list, passwordId.get(), otp1Id.get(), otp2Id.get()); // 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); // Assert priorities: password, otp2, otp1 - List list = currentSession.userCredentialManager().getStoredCredentials(realm, user); + List list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user) + .collect(Collectors.toList()); assertOrder(list, passwordId.get(), otp2Id.get(), otp1Id.get()); // Move otp2 to the top @@ -90,7 +94,8 @@ public class CredentialModelTest extends AbstractTestRealmKeycloakTest { UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm); // Assert priorities: otp2, password, otp1 - List list = currentSession.userCredentialManager().getStoredCredentials(realm, user); + List list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user) + .collect(Collectors.toList()); assertOrder(list, otp2Id.get(), passwordId.get(), otp1Id.get()); // Move password down @@ -102,7 +107,8 @@ public class CredentialModelTest extends AbstractTestRealmKeycloakTest { UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm); // Assert priorities: otp2, otp1, password - List list = currentSession.userCredentialManager().getStoredCredentials(realm, user); + List list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user) + .collect(Collectors.toList()); assertOrder(list, otp2Id.get(), otp1Id.get(), passwordId.get()); // Remove otp2 down two positions @@ -114,7 +120,8 @@ public class CredentialModelTest extends AbstractTestRealmKeycloakTest { UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm); // Assert priorities: otp2, otp1, password - List list = currentSession.userCredentialManager().getStoredCredentials(realm, user); + List list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user) + .collect(Collectors.toList()); assertOrder(list, otp1Id.get(), passwordId.get(), otp2Id.get()); // Remove password @@ -126,7 +133,8 @@ public class CredentialModelTest extends AbstractTestRealmKeycloakTest { UserModel user = currentSession.users().getUserByUsername("test-user@localhost", realm); // Assert priorities: otp2, password - List list = currentSession.userCredentialManager().getStoredCredentials(realm, user); + List list = currentSession.userCredentialManager().getStoredCredentialsStream(realm, user) + .collect(Collectors.toList()); assertOrder(list, otp1Id.get(), otp2Id.get()); }); }