diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java index c36f002122..16987d493d 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java @@ -637,11 +637,6 @@ public class UserCacheSession implements UserCache { return getDelegate().removeFederatedIdentity(realm, user, socialProvider); } - @Override - public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) { - return getDelegate().validCredentials(session, realm, input); - } - @Override public void grantToAllUsers(RealmModel realm, RoleModel role) { realmInvalidations.add(realm.getId()); // easier to just invalidate whole realm 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 e03b736423..6f85e38d0f 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 @@ -707,12 +707,6 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore { return (entity != null) ? new FederatedIdentityModel(entity.getIdentityProvider(), entity.getUserId(), entity.getUserName(), entity.getToken()) : null; } - @Override - public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) { - // Not supported yet - return null; - } - @Override public void preRemove(RealmModel realm, ComponentModel component) { diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java index 96a9d316b5..529aabe899 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java @@ -517,12 +517,6 @@ public class MongoUserProvider implements UserProvider, UserCredentialStore { getMongoStore().updateEntities(MongoUserConsentEntity.class, query, pull, invocationContext); } - @Override - public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) { - // Not supported yet - return null; - } - @Override public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) { String clientId = consent.getClient().getId(); diff --git a/server-spi/src/main/java/org/keycloak/storage/user/UserCredentialAuthenticationProvider.java b/server-spi/src/main/java/org/keycloak/credential/CredentialAuthentication.java similarity index 66% rename from server-spi/src/main/java/org/keycloak/storage/user/UserCredentialAuthenticationProvider.java rename to server-spi/src/main/java/org/keycloak/credential/CredentialAuthentication.java index 948ef4738b..0acbb71818 100644 --- a/server-spi/src/main/java/org/keycloak/storage/user/UserCredentialAuthenticationProvider.java +++ b/server-spi/src/main/java/org/keycloak/credential/CredentialAuthentication.java @@ -14,20 +14,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.keycloak.storage.user; +package org.keycloak.credential; import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; -import org.keycloak.models.UserCredentialModel; import java.util.Set; /** + * Single purpose method that knows how to authenticate a user based on a credential type. This is used when the user + * is not known but the provider knows how to extract this information from the credential. Examples are Kerberos. + * * @author Bill Burke * @version $Revision: 1 $ */ -public interface UserCredentialAuthenticationProvider { - Set getSupportedCredentialAuthenticationTypes(); - CredentialValidationOutput validCredential(KeycloakSession session, RealmModel realm, UserCredentialModel input); +public interface CredentialAuthentication { + boolean supportsCredentialAuthenticationFor(String type); + CredentialValidationOutput authenticate(RealmModel realm, CredentialInput input); } diff --git a/server-spi/src/main/java/org/keycloak/models/CredentialValidationOutput.java b/server-spi/src/main/java/org/keycloak/models/CredentialValidationOutput.java index e0faa22152..83694bc528 100644 --- a/server-spi/src/main/java/org/keycloak/models/CredentialValidationOutput.java +++ b/server-spi/src/main/java/org/keycloak/models/CredentialValidationOutput.java @@ -49,6 +49,11 @@ public class CredentialValidationOutput { return authStatus; } + /** + * State that is passed back by provider + * + * @return + */ public Map getState() { return state; } 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 8dad996608..f03ac8fa36 100644 --- a/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java +++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java @@ -90,4 +90,17 @@ public interface UserCredentialManager extends UserCredentialStore { * @return */ boolean isConfiguredLocally(RealmModel realm, UserModel user, String type); + + /** + * Given a CredentialInput, authenticate the user. This is used in the case where the credential must be processed + * to determine and find the user. An example is Kerberos where the kerberos token might be validated and processed + * by a variety of different storage providers. + * + * + * @param session + * @param realm + * @param input + * @return + */ + CredentialValidationOutput authenticate(KeycloakSession session, RealmModel realm, CredentialInput input); } diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java index 771a03a979..c722aa4437 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java +++ b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java @@ -73,7 +73,7 @@ public class UserFederationManager implements UserProvider { return user; } - protected UserFederationProvider getFederationProvider(UserFederationProviderModel model) { + public UserFederationProvider getFederationProvider(UserFederationProviderModel model) { return KeycloakModelUtils.getFederationProviderInstance(session, model); } @@ -484,40 +484,6 @@ public class UserFederationManager implements UserProvider { session.userStorage().preRemove(protocolMapper); } - @Override - public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) { - List fedProviderModels = realm.getUserFederationProviders(); - List fedProviders = new ArrayList(); - for (UserFederationProviderModel fedProviderModel : fedProviderModels) { - fedProviders.add(getFederationProvider(fedProviderModel)); - } - - CredentialValidationOutput result = null; - for (UserCredentialModel cred : input) { - UserFederationProvider providerSupportingCreds = null; - - // Find first provider, which supports required credential type - for (UserFederationProvider fedProvider : fedProviders) { - if (fedProvider.getSupportedCredentialTypes().contains(cred.getType())) { - providerSupportingCreds = fedProvider; - break; - } - } - - if (providerSupportingCreds == null) { - logger.warn("Don't have provider supporting credentials of type " + cred.getType()); - return CredentialValidationOutput.failed(); - } - - logger.debug("Found provider [" + providerSupportingCreds + "] supporting credentials of type " + cred.getType()); - CredentialValidationOutput currentResult = providerSupportingCreds.validCredentials(realm, cred); - result = (result == null) ? currentResult : result.merge(currentResult); - } - - // For now, validCredentials(realm, input) is not supported for local userProviders - return (result != null) ? result : CredentialValidationOutput.failed(); - } - @Override public void preRemove(RealmModel realm, ComponentModel component) { session.userStorage().preRemove(realm, component); diff --git a/server-spi/src/main/java/org/keycloak/models/UserProvider.java b/server-spi/src/main/java/org/keycloak/models/UserProvider.java index e1b64fa927..3c796b50db 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java @@ -75,10 +75,6 @@ public interface UserProvider extends Provider, void preRemove(RealmModel realm, ClientModel client); void preRemove(ProtocolMapperModel protocolMapper); - - CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input); - - void close(); void preRemove(RealmModel realm, ComponentModel component); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticator.java index 1d6e7ae300..c88f49208a 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticator.java @@ -86,7 +86,7 @@ public class SpnegoAuthenticator extends AbstractUsernameFormAuthenticator imple String spnegoToken = tokens[1]; UserCredentialModel spnegoCredential = UserCredentialModel.kerberos(spnegoToken); - CredentialValidationOutput output = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), spnegoCredential); + CredentialValidationOutput output = context.getSession().userCredentialManager().authenticate(context.getSession(), context.getRealm(), spnegoCredential); if (output.getAuthStatus() == CredentialValidationOutput.Status.AUTHENTICATED) { context.setUser(output.getAuthenticatedUser()); diff --git a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java index 065be1fe58..5801efd63d 100644 --- a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java +++ b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java @@ -17,18 +17,23 @@ package org.keycloak.credential; import org.keycloak.common.util.reflections.Types; +import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserCredentialManager; +import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserFederationProvider; +import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.models.cache.CachedUserModel; import org.keycloak.models.cache.OnUserCache; +import org.keycloak.models.utils.CredentialValidation; import org.keycloak.provider.ProviderFactory; import org.keycloak.storage.StorageId; import org.keycloak.storage.UserStorageManager; import org.keycloak.storage.UserStorageProvider; +import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; @@ -246,6 +251,37 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser return false; } + @Override + public CredentialValidationOutput authenticate(KeycloakSession session, RealmModel realm, CredentialInput input) { + List fedProviderModels = realm.getUserFederationProviders(); + List fedProviders = new ArrayList(); + for (UserFederationProviderModel fedProviderModel : fedProviderModels) { + UserFederationProvider provider = session.users().getFederationProvider(fedProviderModel); + if (input instanceof UserCredentialModel && provider != null && provider.supportsCredentialType(input.getType())) { + CredentialValidationOutput output = provider.validCredentials(realm, (UserCredentialModel)input); + if (output != null) return output; + } + } + + List list = UserStorageManager.getStorageProviders(session, realm, CredentialAuthentication.class); + for (CredentialAuthentication auth : list) { + if (auth.supportsCredentialAuthenticationFor(input.getType())) { + CredentialValidationOutput output = auth.authenticate(realm, input); + if (output != null) return output; + } + } + + list = getCredentialProviders(realm, CredentialAuthentication.class); + for (CredentialAuthentication auth : list) { + if (auth.supportsCredentialAuthenticationFor(input.getType())) { + CredentialValidationOutput output = auth.authenticate(realm, input); + if (output != null) return output; + } + } + + return null; + } + @Override public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) { List credentialProviders = getCredentialProviders(realm, OnUserCache.class); diff --git a/services/src/main/java/org/keycloak/storage/UserStorageManager.java b/services/src/main/java/org/keycloak/storage/UserStorageManager.java index 90c8f7264a..6061323727 100755 --- a/services/src/main/java/org/keycloak/storage/UserStorageManager.java +++ b/services/src/main/java/org/keycloak/storage/UserStorageManager.java @@ -37,7 +37,7 @@ import org.keycloak.models.UserProvider; import org.keycloak.models.cache.CachedUserModel; import org.keycloak.models.cache.OnUserCache; import org.keycloak.storage.federated.UserFederatedStorageProvider; -import org.keycloak.storage.user.UserCredentialAuthenticationProvider; +import org.keycloak.credential.CredentialAuthentication; import org.keycloak.storage.user.UserLookupProvider; import org.keycloak.storage.user.UserQueryProvider; import org.keycloak.storage.user.UserRegistrationProvider; @@ -528,37 +528,6 @@ public class UserStorageManager implements UserProvider, OnUserCache { if (getFederatedStorage() != null) getFederatedStorage().preRemove(protocolMapper); } - @Override - public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) { - List providers = getStorageProviders(session, realm, UserCredentialAuthenticationProvider.class); - if (providers.isEmpty()) return CredentialValidationOutput.failed(); - - CredentialValidationOutput result = null; - for (UserCredentialModel cred : input) { - UserCredentialAuthenticationProvider providerSupportingCreds = null; - - // Find first provider, which supports required credential type - for (UserCredentialAuthenticationProvider provider : providers) { - if (provider.getSupportedCredentialAuthenticationTypes().contains(cred.getType())) { - providerSupportingCreds = provider; - break; - } - } - - if (providerSupportingCreds == null) { - logger.warn("Don't have provider supporting credentials of type " + cred.getType()); - return CredentialValidationOutput.failed(); - } - - logger.debug("Found provider [" + providerSupportingCreds + "] supporting credentials of type " + cred.getType()); - CredentialValidationOutput currentResult = providerSupportingCreds.validCredential(session, realm, cred); - result = (result == null) ? currentResult : result.merge(currentResult); - } - - // For now, validCredentials(realm, input) is not supported for local userProviders - return (result != null) ? result : CredentialValidationOutput.failed(); - } - @Override public void preRemove(RealmModel realm, ComponentModel component) { if (!component.getProviderType().equals(UserStorageProvider.class.getName())) return; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ScriptAuthenticatorTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ScriptAuthenticatorTest.java index 667c85ff59..61cc47c94b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ScriptAuthenticatorTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ScriptAuthenticatorTest.java @@ -22,20 +22,17 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.keycloak.authentication.AuthenticationFlow; import org.keycloak.authentication.authenticators.browser.ScriptBasedAuthenticatorFactory; import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory; import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventType; import org.keycloak.models.AuthenticationExecutionModel; -import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation; import org.keycloak.representations.idm.AuthenticationExecutionRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; -import org.keycloak.storage.user.UserCredentialAuthenticationProvider; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.util.ExecutionBuilder; @@ -45,8 +42,6 @@ import org.keycloak.testsuite.util.UserBuilder; import javax.ws.rs.core.Response; import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; /** * Tests for {@link org.keycloak.authentication.authenticators.browser.ScriptBasedAuthenticator}