From 9638c0dd83afb8e5e0aa9828acad4c86a1a74fba Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 15 Jun 2015 18:04:55 -0400 Subject: [PATCH] verify clientsession actions --- .../keycloak/protocol/saml/SamlService.java | 38 -- .../AuthenticationProcessor.java | 34 +- .../authentication/AuthenticatorContext.java | 7 + .../authentication/RequiredActionContext.java | 2 + .../RequiredActionProvider.java | 1 + .../actions/TermsAndConditions.java | 22 +- .../actions/UpdatePassword.java | 13 +- .../authentication/actions/UpdateProfile.java | 12 +- .../authentication/actions/UpdateTotp.java | 12 +- .../authentication/actions/VerifyEmail.java | 11 +- .../AbstractFormAuthenticator.java | 6 +- .../oidc/endpoints/AuthorizationEndpoint.java | 66 +--- .../managers/AuthenticationManager.java | 75 +--- .../services/managers/ClientSessionCode.java | 28 +- .../managers/HttpAuthenticationManager.java | 186 ---------- .../resources/IdentityBrokerService.java | 52 ++- .../resources/LoginActionsService.java | 343 +++++------------- .../services/resources/RealmsResource.java | 2 +- .../org/keycloak/testsuite/AssertEvents.java | 2 +- .../testsuite/forms/ResetPasswordTest.java | 4 +- .../testsuite/perf/AccessTokenPerfTest.java | 14 - 21 files changed, 248 insertions(+), 682 deletions(-) delete mode 100755 services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java index a557b5a80e..bc1e1bda9a 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java @@ -1,7 +1,6 @@ package org.keycloak.protocol.saml; import org.jboss.logging.Logger; -import org.jboss.resteasy.specimpl.MultivaluedMapImpl; import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpResponse; import org.keycloak.ClientConnection; @@ -17,7 +16,6 @@ import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; -import org.keycloak.login.LoginFormsProvider; import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; @@ -30,14 +28,11 @@ import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.utils.RedirectUtils; import org.keycloak.saml.common.constants.GeneralConstants; import org.keycloak.saml.common.constants.JBossSAMLURIConstants; -import org.keycloak.saml.common.exceptions.ConfigurationException; -import org.keycloak.saml.common.exceptions.ProcessingException; import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder; import org.keycloak.services.ErrorPage; import org.keycloak.services.Urls; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.ClientSessionCode; -import org.keycloak.services.managers.HttpAuthenticationManager; import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.RealmsResource; import org.keycloak.util.StreamUtil; @@ -52,13 +47,10 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; -import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import javax.ws.rs.ext.Providers; -import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.security.PublicKey; @@ -291,36 +283,6 @@ public class SamlService { return newBrowserAuthentication(clientSession); } - private Response oldBrowserAuthentication(ClientSessionModel clientSession) { - Response response = authManager.checkNonFormAuthentication(session, clientSession, realm, uriInfo, request, clientConnection, headers, event); - if (response != null) return response; - - // SPNEGO/Kerberos authentication TODO: This should be somehow pluggable instead of hardcoded this way (Authentication interceptors?) - HttpAuthenticationManager httpAuthManager = new HttpAuthenticationManager(session, clientSession, realm, uriInfo, request, clientConnection, event); - HttpAuthenticationManager.HttpAuthOutput httpAuthOutput = httpAuthManager.spnegoAuthenticate(headers); - if (httpAuthOutput.getResponse() != null) return httpAuthOutput.getResponse(); - - LoginFormsProvider forms = session.getProvider(LoginFormsProvider.class) - .setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode()); - - // Attach state from SPNEGO authentication - if (httpAuthOutput.getChallenge() != null) { - httpAuthOutput.getChallenge().sendChallenge(forms); - } - - String rememberMeUsername = AuthenticationManager.getRememberMeUsername(realm, headers); - - if (rememberMeUsername != null) { - MultivaluedMap formData = new MultivaluedMapImpl(); - formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername); - formData.add("rememberMe", "on"); - - forms.setFormData(formData); - } - - return forms.createLogin(); - } - private Response buildRedirectToIdentityProvider(String providerId, String accessCode) { logger.debug("Automatically redirect to identity provider: " + providerId); return Response.temporaryRedirect( diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java index 473bc4fba0..af6a67166d 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java @@ -43,6 +43,12 @@ public class AuthenticationProcessor { protected HttpRequest request; protected String flowId; protected String action; + /** + * This could be an error message forwarded from brokering when the broker failed authentication + * and we want to continue authentication locally. forwardedErrorMessage can then be displayed by + * whatever form is challenging. + */ + protected String forwardedErrorMessage; protected boolean userSessionCreated; @@ -56,6 +62,7 @@ public class AuthenticationProcessor { } public static enum Error { + INVALID_CLIENT_SESSION, INVALID_USER, INVALID_CREDENTIALS, CREDENTIAL_SETUP_REQUIRED, @@ -144,6 +151,11 @@ public class AuthenticationProcessor { return this; } + public AuthenticationProcessor setForwardedErrorMessage(String forwardedErrorMessage) { + this.forwardedErrorMessage = forwardedErrorMessage; + return this; + } + private class Result implements AuthenticatorContext { AuthenticatorModel model; AuthenticationExecutionModel execution; @@ -300,6 +312,11 @@ public class AuthenticationProcessor { public EventBuilder getEvent() { return AuthenticationProcessor.this.event; } + + @Override + public String getForwardedErrorMessage() { + return AuthenticationProcessor.this.forwardedErrorMessage; + } } public static class AuthException extends RuntimeException { @@ -367,7 +384,11 @@ public class AuthenticationProcessor { event.error(Errors.USER_TEMPORARILY_DISABLED); return ErrorPage.error(session, Messages.ACCOUNT_TEMPORARILY_DISABLED); - } else { + } else if (e.getError() == Error.INVALID_CLIENT_SESSION) { + event.error(Errors.INVALID_CODE); + return ErrorPage.error(session, Messages.INVALID_CODE); + + }else { event.error(Errors.INVALID_USER_CREDENTIALS); return ErrorPage.error(session, Messages.INVALID_USER); } @@ -382,6 +403,9 @@ public class AuthenticationProcessor { public Response authenticate() throws AuthException { + if (!ClientSessionModel.Action.AUTHENTICATE.name().equals(clientSession.getAction())) { + throw new AuthException(Error.INVALID_CLIENT_SESSION); + } logger.debug("AUTHENTICATE"); event.event(EventType.LOGIN); event.client(clientSession.getClient().getClientId()) @@ -402,6 +426,9 @@ public class AuthenticationProcessor { } public Response authenticateOnly() throws AuthException { + if (!ClientSessionModel.Action.AUTHENTICATE.name().equals(clientSession.getAction())) { + throw new AuthException(Error.INVALID_CLIENT_SESSION); + } event.event(EventType.LOGIN); event.client(clientSession.getClient().getClientId()) .detail(Details.REDIRECT_URI, clientSession.getRedirectUri()) @@ -585,11 +612,6 @@ public class AuthenticationProcessor { .detail(Details.USERNAME, username) .session(userSession); - return processRequiredActions(); - - } - - public Response processRequiredActions() { return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, connection, request, uriInfo, event); } diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java index 248eb7accf..a8f5aadc8f 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java @@ -66,4 +66,11 @@ public interface AuthenticatorContext { void failureChallenge(AuthenticationProcessor.Error error, Response challenge); void attempted(); + + /** + * This could be an error message forwarded from brokering when the broker failed authentication + * and we want to continue authentication locally. forwardedErrorMessage can then be displayed by + * whatever form is challenging. + */ + String getForwardedErrorMessage(); } diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java index 62d4a9314a..dc630a8aca 100755 --- a/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java +++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java @@ -11,6 +11,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.services.managers.BruteForceProtector; +import org.keycloak.services.managers.ClientSessionCode; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @@ -29,4 +30,5 @@ public interface RequiredActionContext { UriInfo getUriInfo(); KeycloakSession getSession(); HttpRequest getHttpRequest(); + String generateAccessCode(String action); } diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java b/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java index e6ff4882b3..628f5c7d01 100755 --- a/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java +++ b/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java @@ -12,4 +12,5 @@ public interface RequiredActionProvider extends Provider { void evaluateTriggers(RequiredActionContext context); Response invokeRequiredAction(RequiredActionContext context); Object jaxrsService(RequiredActionContext context); + String getProviderId(); } diff --git a/services/src/main/java/org/keycloak/authentication/actions/TermsAndConditions.java b/services/src/main/java/org/keycloak/authentication/actions/TermsAndConditions.java index 3c0769e25b..5c6157ca24 100755 --- a/services/src/main/java/org/keycloak/authentication/actions/TermsAndConditions.java +++ b/services/src/main/java/org/keycloak/authentication/actions/TermsAndConditions.java @@ -1,37 +1,25 @@ package org.keycloak.authentication.actions; import org.keycloak.Config; -import org.keycloak.Version; import org.keycloak.authentication.RequiredActionContext; import org.keycloak.authentication.RequiredActionFactory; import org.keycloak.authentication.RequiredActionProvider; import org.keycloak.events.Errors; -import org.keycloak.freemarker.BrowserSecurityHeaderSetup; import org.keycloak.freemarker.FreeMarkerException; -import org.keycloak.freemarker.FreeMarkerUtil; -import org.keycloak.freemarker.Theme; -import org.keycloak.freemarker.ThemeProvider; import org.keycloak.login.LoginFormsProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.protocol.LoginProtocol; -import org.keycloak.services.Urls; import org.keycloak.services.managers.AuthenticationManager; -import org.keycloak.services.managers.ClientSessionCode; import javax.ws.rs.Consumes; -import javax.ws.rs.GET; import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import java.io.IOException; -import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; -import java.util.Map; /** * @author Bill Burke @@ -86,6 +74,14 @@ public class TermsAndConditions implements RequiredActionProvider, RequiredActio return PROVIDER_ID; } + + @Override + public String getProviderId() { + return getId(); + } + + + @Override public void evaluateTriggers(RequiredActionContext context) { @@ -94,7 +90,7 @@ public class TermsAndConditions implements RequiredActionProvider, RequiredActio @Override public Response invokeRequiredAction(RequiredActionContext context) { return context.getSession().getProvider(LoginFormsProvider.class) - .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode()) + .setClientSessionCode(context.generateAccessCode(getProviderId())) .setUser(context.getUser()) .createForm("terms.ftl", new HashMap()); } diff --git a/services/src/main/java/org/keycloak/authentication/actions/UpdatePassword.java b/services/src/main/java/org/keycloak/authentication/actions/UpdatePassword.java index 57ff72b981..ae4a3808fd 100755 --- a/services/src/main/java/org/keycloak/authentication/actions/UpdatePassword.java +++ b/services/src/main/java/org/keycloak/authentication/actions/UpdatePassword.java @@ -51,10 +51,9 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac @Override public Response invokeRequiredAction(RequiredActionContext context) { - ClientSessionCode accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession()); - accessCode.setAction(ClientSessionModel.Action.UPDATE_PASSWORD.name()); - - LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode()) + LoginFormsProvider loginFormsProvider = context.getSession() + .getProvider(LoginFormsProvider.class) + .setClientSessionCode(context.generateAccessCode(getProviderId())) .setUser(context.getUser()); return loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD); } @@ -96,4 +95,10 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac public String getId() { return UserModel.RequiredAction.UPDATE_PASSWORD.name(); } + + @Override + public String getProviderId() { + return getId(); + } + } diff --git a/services/src/main/java/org/keycloak/authentication/actions/UpdateProfile.java b/services/src/main/java/org/keycloak/authentication/actions/UpdateProfile.java index d9aaa73174..7117ae320d 100755 --- a/services/src/main/java/org/keycloak/authentication/actions/UpdateProfile.java +++ b/services/src/main/java/org/keycloak/authentication/actions/UpdateProfile.java @@ -32,10 +32,8 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact @Override public Response invokeRequiredAction(RequiredActionContext context) { - ClientSessionCode accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession()); - accessCode.setAction(ClientSessionModel.Action.UPDATE_PROFILE.name()); - - LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode()) + LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class) + .setClientSessionCode(context.generateAccessCode(getProviderId())) .setUser(context.getUser()); return loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PROFILE); } @@ -78,4 +76,10 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact public String getId() { return UserModel.RequiredAction.UPDATE_PROFILE.name(); } + + @Override + public String getProviderId() { + return getId(); + } + } diff --git a/services/src/main/java/org/keycloak/authentication/actions/UpdateTotp.java b/services/src/main/java/org/keycloak/authentication/actions/UpdateTotp.java index e378942430..6f79d518cf 100755 --- a/services/src/main/java/org/keycloak/authentication/actions/UpdateTotp.java +++ b/services/src/main/java/org/keycloak/authentication/actions/UpdateTotp.java @@ -40,10 +40,8 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory @Override public Response invokeRequiredAction(RequiredActionContext context) { - ClientSessionCode accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession()); - accessCode.setAction(ClientSessionModel.Action.CONFIGURE_TOTP.name()); - - LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode()) + LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class) + .setClientSessionCode(context.generateAccessCode(getProviderId())) .setUser(context.getUser()); return loginFormsProvider.createResponse(UserModel.RequiredAction.CONFIGURE_TOTP); } @@ -87,4 +85,10 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory return UserModel.RequiredAction.CONFIGURE_TOTP.name(); } + @Override + public String getProviderId() { + return getId(); + } + + } diff --git a/services/src/main/java/org/keycloak/authentication/actions/VerifyEmail.java b/services/src/main/java/org/keycloak/authentication/actions/VerifyEmail.java index 9d337f9725..fa284bd25c 100755 --- a/services/src/main/java/org/keycloak/authentication/actions/VerifyEmail.java +++ b/services/src/main/java/org/keycloak/authentication/actions/VerifyEmail.java @@ -59,12 +59,11 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor return null; } - ClientSessionCode accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession()); - accessCode.setAction(ClientSessionModel.Action.VERIFY_EMAIL.name()); context.getEvent().clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, context.getUser().getEmail()).success(); LoginActionsService.createActionCookie(context.getRealm(), context.getUriInfo(), context.getConnection(), context.getUserSession().getId()); - LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode()) + LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class) + .setClientSessionCode(context.generateAccessCode(getProviderId())) .setUser(context.getUser()); return loginFormsProvider.createResponse(UserModel.RequiredAction.VERIFY_EMAIL); } @@ -107,4 +106,10 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor return UserModel.RequiredAction.VERIFY_EMAIL.name(); } + @Override + public String getProviderId() { + return getId(); + } + + } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java index 44636626a8..44d11fdbf9 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java @@ -32,10 +32,14 @@ public class AbstractFormAuthenticator { ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession()); code.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); URI action = getActionUrl(context, code, LOGIN_FORM_ACTION); - return context.getSession().getProvider(LoginFormsProvider.class) + LoginFormsProvider provider = context.getSession().getProvider(LoginFormsProvider.class) .setUser(context.getUser()) .setActionUri(action) .setClientSessionCode(code.getCode()); + if (context.getForwardedErrorMessage() != null) { + provider.setError(context.getForwardedErrorMessage()); + } + return provider; } public static URI getActionUrl(AuthenticatorContext context, ClientSessionCode code, String action) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java index 5b15cb8366..e29649fa90 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -1,7 +1,6 @@ package org.keycloak.protocol.oidc.endpoints; import org.jboss.logging.Logger; -import org.jboss.resteasy.specimpl.MultivaluedMapImpl; import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.ClientConnection; import org.keycloak.OAuth2Constants; @@ -18,7 +17,6 @@ import org.keycloak.models.ClientSessionModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; -import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.utils.DefaultAuthenticationFlows; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCLoginProtocol; @@ -26,7 +24,6 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils; import org.keycloak.services.ErrorPageException; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.ClientSessionCode; -import org.keycloak.services.managers.HttpAuthenticationManager; import org.keycloak.services.messages.Messages; import org.keycloak.services.Urls; @@ -248,10 +245,10 @@ public class AuthorizationEndpoint { return buildRedirectToIdentityProvider(idpHint, accessCode); } - return newBrowserAuthentication(accessCode); + return browserAuthentication(accessCode); } - protected Response newBrowserAuthentication(String accessCode) { + protected Response browserAuthentication(String accessCode) { List identityProviders = realm.getIdentityProviders(); for (IdentityProviderModel identityProvider : identityProviders) { if (identityProvider.isAuthenticateByDefault()) { @@ -295,65 +292,6 @@ public class AuthorizationEndpoint { } } - protected Response oldBrowserAuthentication(String accessCode) { - Response response = authManager.checkNonFormAuthentication(session, clientSession, realm, uriInfo, request, clientConnection, headers, event); - if (response != null) return response; - - // SPNEGO/Kerberos authentication TODO: This should be somehow pluggable instead of hardcoded this way (Authentication interceptors?) - HttpAuthenticationManager httpAuthManager = new HttpAuthenticationManager(session, clientSession, realm, uriInfo, request, clientConnection, event); - HttpAuthenticationManager.HttpAuthOutput httpAuthOutput = httpAuthManager.spnegoAuthenticate(headers); - if (httpAuthOutput.getResponse() != null) return httpAuthOutput.getResponse(); - - if (prompt != null && prompt.equals("none")) { - OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo, headers, event); - return oauth.cancelLogin(clientSession); - } - - List identityProviders = realm.getIdentityProviders(); - for (IdentityProviderModel identityProvider : identityProviders) { - if (identityProvider.isAuthenticateByDefault()) { - return buildRedirectToIdentityProvider(identityProvider.getAlias(), accessCode); - } - } - - List requiredCredentials = realm.getRequiredCredentials(); - if (requiredCredentials.isEmpty()) { - if (!identityProviders.isEmpty()) { - if (identityProviders.size() == 1) { - return buildRedirectToIdentityProvider(identityProviders.get(0).getAlias(), accessCode); - } - - return session.getProvider(LoginFormsProvider.class).setError(Messages.IDENTITY_PROVIDER_NOT_UNIQUE, realm.getName()).createErrorPage(); - } - - return session.getProvider(LoginFormsProvider.class).setError(Messages.REALM_SUPPORTS_NO_CREDENTIALS, realm.getName()).createErrorPage(); - } - - LoginFormsProvider forms = session.getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode); - - // Attach state from SPNEGO authentication - if (httpAuthOutput.getChallenge() != null) { - httpAuthOutput.getChallenge().sendChallenge(forms); - } - - String rememberMeUsername = AuthenticationManager.getRememberMeUsername(realm, headers); - - if (loginHint != null || rememberMeUsername != null) { - MultivaluedMap formData = new MultivaluedMapImpl(); - - if (loginHint != null) { - formData.add(AuthenticationManager.FORM_USERNAME, loginHint); - } else { - formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername); - formData.add("rememberMe", "on"); - } - - forms.setFormData(formData); - } - - return forms.createLogin(); - } - private Response buildRegister() { authManager.expireIdentityCookie(realm, uriInfo, clientConnection); diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index a4b0276b50..56aface28b 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -379,22 +379,6 @@ public class AuthenticationManager { return authResult; } - public Response checkNonFormAuthentication(KeycloakSession session, ClientSessionModel clientSession, RealmModel realm, UriInfo uriInfo, - HttpRequest request, - ClientConnection clientConnection, HttpHeaders headers, - EventBuilder event) { - AuthResult authResult = authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, true); - if (authResult != null) { - UserModel user = authResult.getUser(); - UserSessionModel userSession = authResult.getSession(); - TokenManager.attachClientSession(userSession, clientSession); - event.user(user).session(userSession).detail(Details.AUTH_METHOD, "sso"); - return nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event); - } - return null; - } - - public static Response redirectAfterSuccessfulFlow(KeycloakSession session, RealmModel realm, UserSessionModel userSession, ClientSessionModel clientSession, @@ -442,11 +426,6 @@ public class AuthenticationManager { final HttpRequest request, final UriInfo uriInfo, final EventBuilder event) { final RealmModel realm = clientSession.getRealm(); final UserModel user = userSession.getUser(); - /* - isForcePasswordUpdateRequired(realm, user); - isTotpConfigurationRequired(realm, user); - isEmailVerificationRequired(realm, user); - */ final ClientModel client = clientSession.getClient(); RequiredActionContext context = new RequiredActionContext() { @@ -494,6 +473,13 @@ public class AuthenticationManager { public HttpRequest getHttpRequest() { return request; } + + @Override + public String generateAccessCode(String action) { + ClientSessionCode code = new ClientSessionCode(getRealm(), getClientSession()); + code.setAction(action); + return code.getCode(); + } }; // see if any required actions need triggering, i.e. an expired password @@ -502,7 +488,6 @@ public class AuthenticationManager { provider.evaluateTriggers(context); } - ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession); logger.debugv("processAccessCode: go to oauth page?: {0}", client.isConsentRequired()); @@ -512,7 +497,9 @@ public class AuthenticationManager { for (String action : requiredActions) { RequiredActionProvider actionProvider = session.getProvider(RequiredActionProvider.class, action); Response challenge = actionProvider.invokeRequiredAction(context); - if (challenge != null) return challenge; + if (challenge != null) { + return challenge; + } } if (client.isConsentRequired()) { @@ -521,6 +508,7 @@ public class AuthenticationManager { List realmRoles = new LinkedList<>(); MultivaluedMap resourceRoles = new MultivaluedMapImpl<>(); + ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession); for (RoleModel r : accessCode.getRequestedRoles()) { // Consent already granted by user @@ -564,47 +552,6 @@ public class AuthenticationManager { } - - private static void isForcePasswordUpdateRequired(RealmModel realm, UserModel user) { - int daysToExpirePassword = realm.getPasswordPolicy().getDaysToExpirePassword(); - if(daysToExpirePassword != -1) { - for (UserCredentialValueModel entity : user.getCredentialsDirectly()) { - if (entity.getType().equals(UserCredentialModel.PASSWORD)) { - - if(entity.getCreatedDate() == null) { - user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); - logger.debug("User is required to update password"); - } else { - long timeElapsed = Time.toMillis(Time.currentTime()) - entity.getCreatedDate(); - long timeToExpire = TimeUnit.DAYS.toMillis(daysToExpirePassword); - - if(timeElapsed > timeToExpire) { - user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); - logger.debug("User is required to update password"); - } - } - break; - } - } - } - } - - protected static void isTotpConfigurationRequired(RealmModel realm, UserModel user) { - for (RequiredCredentialModel c : realm.getRequiredCredentials()) { - if (c.getType().equals(CredentialRepresentation.TOTP) && !user.isTotp()) { - user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP); - logger.debug("User is required to configure totp"); - } - } - } - - protected static void isEmailVerificationRequired(RealmModel realm, UserModel user) { - if (realm.isVerifyEmail() && !user.isEmailVerified()) { - user.addRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL); - logger.debug("User is required to verify email"); - } - } - protected static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, String tokenString, HttpHeaders headers) { try { AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()), checkActive); diff --git a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java index 8ddc02f74b..d203218048 100755 --- a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java +++ b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java @@ -81,22 +81,18 @@ public class ClientSessionCode { } public boolean isValid(String requestedAction) { - String action = clientSession.getAction(); - if (action == null) { - return false; - } + if (!isValidAction(requestedAction)) return false; + return isActionActive(requestedAction); + } + public boolean isActionActive(String requestedAction) { int timestamp = clientSession.getTimestamp(); - if (!action.equals(requestedAction)) { - return false; - } - int lifespan; - if (action.equals(ClientSessionModel.Action.CODE_TO_TOKEN.name())) { + if (requestedAction.equals(ClientSessionModel.Action.CODE_TO_TOKEN.name())) { lifespan = realm.getAccessCodeLifespan(); - } else if (action.equals(ClientSessionModel.Action.AUTHENTICATE.name())) { + } else if (requestedAction.equals(ClientSessionModel.Action.AUTHENTICATE.name())) { lifespan = realm.getAccessCodeLifespanLogin() > 0 ? realm.getAccessCodeLifespanLogin() : realm.getAccessCodeLifespanUserAction(); } else { lifespan = realm.getAccessCodeLifespanUserAction(); @@ -104,6 +100,18 @@ public class ClientSessionCode { return timestamp + lifespan > Time.currentTime(); } + public boolean isValidAction(String requestedAction) { + String action = clientSession.getAction(); + if (action == null) { + return false; + } + if (!action.equals(requestedAction)) { + return false; + } + return true; + } + + public Set getRequestedRoles() { Set requestedRoles = new HashSet(); for (String roleId : clientSession.getRoles()) { diff --git a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java deleted file mode 100755 index 0deb28f699..0000000000 --- a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java +++ /dev/null @@ -1,186 +0,0 @@ -package org.keycloak.services.managers; - -import java.util.Map; - -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; - -import org.jboss.logging.Logger; -import org.jboss.resteasy.spi.HttpRequest; -import org.keycloak.ClientConnection; -import org.keycloak.events.Details; -import org.keycloak.events.Errors; -import org.keycloak.events.EventBuilder; -import org.keycloak.login.LoginFormsProvider; -import org.keycloak.models.ClientSessionModel; -import org.keycloak.models.CredentialValidationOutput; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RequiredCredentialModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.constants.KerberosConstants; -import org.keycloak.protocol.oidc.TokenManager; -import org.keycloak.representations.idm.CredentialRepresentation; -import org.keycloak.services.messages.Messages; -import org.keycloak.services.ErrorPage; - -/** - * Handle HTTP authentication types requiring complex handshakes with multiple HTTP request/responses - * - * @author Marek Posolda - */ -public class HttpAuthenticationManager { - - private static final Logger logger = Logger.getLogger(HttpAuthenticationManager.class); - - private KeycloakSession session; - private RealmModel realm; - private UriInfo uriInfo; - private HttpRequest request; - private EventBuilder event; - private ClientConnection clientConnection; - private ClientSessionModel clientSession; - - public HttpAuthenticationManager(KeycloakSession session, ClientSessionModel clientSession, RealmModel realm, UriInfo uriInfo, - HttpRequest request, - ClientConnection clientConnection, - EventBuilder event) { - this.session = session; - this.realm = realm; - this.uriInfo = uriInfo; - this.request = request; - this.event = event; - this.clientConnection = clientConnection; - this.clientSession = clientSession; - } - - - public HttpAuthOutput spnegoAuthenticate(HttpHeaders headers) { - boolean kerberosSupported = false; - for (RequiredCredentialModel c : realm.getRequiredCredentials()) { - if (c.getType().equals(CredentialRepresentation.KERBEROS)) { - kerberosSupported = true; - } - } - - if (logger.isTraceEnabled()) { - String log = kerberosSupported ? "SPNEGO authentication is supported" : "SPNEGO authentication is not supported"; - logger.trace(log); - } - - if (!kerberosSupported) { - return new HttpAuthOutput(null, null); - } - - String authHeader = request.getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION); - - // Case when we don't yet have any Negotiate header - if (authHeader == null) { - return challengeNegotiation(null); - } - - String[] tokens = authHeader.split(" "); - if (tokens.length != 2) { - logger.warn("Invalid length of tokens: " + tokens.length); - return challengeNegotiation(null); - } else if (!KerberosConstants.NEGOTIATE.equalsIgnoreCase(tokens[0])) { - logger.warn("Unknown scheme " + tokens[0]); - return challengeNegotiation(null); - } else { - String spnegoToken = tokens[1]; - UserCredentialModel spnegoCredential = UserCredentialModel.kerberos(spnegoToken); - - CredentialValidationOutput output = session.users().validCredentials(realm, spnegoCredential); - - if (output.getAuthStatus() == CredentialValidationOutput.Status.AUTHENTICATED) { - return sendResponse(output.getAuthenticatedUser(), output.getState(), "spnego", headers); - } else { - String spnegoResponseToken = (String) output.getState().get(KerberosConstants.RESPONSE_TOKEN); - return challengeNegotiation(spnegoResponseToken); - } - } - } - - - // Send response after successful authentication - private HttpAuthOutput sendResponse(UserModel user, Map authState, String authMethod, HttpHeaders headers) { - if (logger.isTraceEnabled()) { - logger.trace("User " + user.getUsername() + " authenticated with " + authMethod); - } - - Response response; - if (!user.isEnabled()) { - event.error(Errors.USER_DISABLED); - response = ErrorPage.error(session, Messages.ACCOUNT_DISABLED); - } else { - UserSessionModel userSession = session.sessions().createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), authMethod, false, null, null); - - // Propagate state (like kerberos delegation credentials etc) as attributes of userSession - for (Map.Entry entry : authState.entrySet()) { - userSession.setNote(entry.getKey(), entry.getValue()); - } - - TokenManager.attachClientSession(userSession, clientSession); - event.user(user) - .session(userSession) - .detail(Details.AUTH_METHOD, authMethod) - .detail(Details.USERNAME, user.getUsername()); - response = AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event); - } - - return new HttpAuthOutput(response, null); - } - - - private HttpAuthOutput challengeNegotiation(final String negotiateToken) { - return new HttpAuthOutput(null, new HttpAuthChallenge() { - - @Override - public void sendChallenge(LoginFormsProvider loginFormsProvider) { - String negotiateHeader = negotiateToken == null ? KerberosConstants.NEGOTIATE : KerberosConstants.NEGOTIATE + " " + negotiateToken; - - if (logger.isTraceEnabled()) { - logger.trace("Sending back " + HttpHeaders.WWW_AUTHENTICATE + ": " + negotiateHeader); - } - - loginFormsProvider.setStatus(Response.Status.UNAUTHORIZED); - loginFormsProvider.setResponseHeader(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader); - } - - }); - } - - - public class HttpAuthOutput { - - // It's non-null if we want to immediately send response to user - private final Response response; - - // It's non-null if challenge should be attached to rendered login form - private final HttpAuthChallenge challenge; - - public HttpAuthOutput(Response response, HttpAuthChallenge challenge) { - this.response = response; - this.challenge = challenge; - } - - public Response getResponse() { - return response; - } - - public HttpAuthChallenge getChallenge() { - return challenge; - } - } - - - public interface HttpAuthChallenge { - - void sendChallenge(LoginFormsProvider loginFormsProvider); - - } - -} diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index d45e706a31..57b16a7785 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -21,6 +21,7 @@ import org.jboss.logging.Logger; import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.ClientConnection; +import org.keycloak.authentication.AuthenticationProcessor; import org.keycloak.broker.provider.AuthenticationRequest; import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.IdentityBrokerException; @@ -31,7 +32,7 @@ import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; -import org.keycloak.login.LoginFormsProvider; +import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.Constants; @@ -44,6 +45,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; +import org.keycloak.models.utils.DefaultAuthenticationFlows; import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.provider.ProviderFactory; import org.keycloak.representations.AccessToken; @@ -51,6 +53,7 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager.AuthResult; +import org.keycloak.services.managers.BruteForceProtector; import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.messages.Messages; import org.keycloak.services.ErrorResponse; @@ -112,11 +115,13 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal private EventBuilder event; - public IdentityBrokerService(RealmModel realmModel) { + private BruteForceProtector protector; + + public IdentityBrokerService(RealmModel realmModel, BruteForceProtector protector) { if (realmModel == null) { throw new IllegalArgumentException("Realm can not be null."); } - + this.protector = protector; this.realmModel = realmModel; } @@ -317,12 +322,21 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal @Override public Response cancelled(String code) { - return session.getProvider(LoginFormsProvider.class).setClientSessionCode(code).createLogin(); + ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel); + if (clientCode.getClientSession() == null || !clientCode.isValid(AUTHENTICATE.name())) { + return redirectToErrorPage(Messages.INVALID_CODE); + } + + return browserAuthentication(clientCode.getClientSession(), null); } @Override public Response error(String code, String message) { - return session.getProvider(LoginFormsProvider.class).setClientSessionCode(code).setError(message).createLogin(); + ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel); + if (clientCode.getClientSession() == null || !clientCode.isValid(AUTHENTICATE.name())) { + return redirectToErrorPage(Messages.INVALID_CODE); + } + return browserAuthentication(clientCode.getClientSession(), message); } private Response performAccountLinking(ClientSessionModel clientSession, BrokeredIdentityContext context, FederatedIdentityModel federatedIdentityModel, UserModel federatedUser) { @@ -448,12 +462,32 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal } fireErrorEvent(message); - return session.getProvider(LoginFormsProvider.class) - .setClientSessionCode(clientCode.getCode()) - .setError(message) - .createLogin(); + return browserAuthentication(clientCode.getClientSession(), message); } + protected Response browserAuthentication(ClientSessionModel clientSession, String errorMessage) { + AuthenticationFlowModel flow = realmModel.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW); + String flowId = flow.getId(); + AuthenticationProcessor processor = new AuthenticationProcessor(); + processor.setClientSession(clientSession) + .setFlowId(flowId) + .setConnection(clientConnection) + .setEventBuilder(event) + .setProtector(protector) + .setRealm(realmModel) + .setSession(session) + .setUriInfo(uriInfo) + .setRequest(request); + if (errorMessage != null) processor.setForwardedErrorMessage(errorMessage); + + try { + return processor.authenticate(); + } catch (Exception e) { + return processor.handleBrowserException(e); + } + } + + private Response badRequest(String message) { fireErrorEvent(message); return ErrorResponse.error(message, Status.BAD_REQUEST); diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java index fae9564f6c..b3716b9fcd 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -36,9 +36,7 @@ import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; -import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.login.LoginFormsProvider; -import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; @@ -48,20 +46,17 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelException; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; -import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserModel.RequiredAction; import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.DefaultAuthenticationFlows; import org.keycloak.models.utils.FormMessage; -import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.TimeBasedOTP; import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.protocol.oidc.TokenManager; -import org.keycloak.representations.PasswordToken; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.ClientSessionCode; @@ -137,16 +132,6 @@ public class LoginActionsService { return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService"); } - public static UriBuilder processLoginUrl(UriInfo uriInfo) { - UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder(); - return processLoginUrl(baseUriBuilder); - } - - public static UriBuilder processLoginUrl(UriBuilder baseUriBuilder) { - UriBuilder uriBuilder = loginActionsBaseUrl(baseUriBuilder); - return uriBuilder.path(OIDCLoginProtocolService.class, "processLogin"); - } - public static UriBuilder processOAuthUrl(UriInfo uriInfo) { UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder(); return processOAuthUrl(baseUriBuilder); @@ -179,10 +164,16 @@ public class LoginActionsService { boolean check(String code, String requiredAction) { if (!check(code)) { return false; - } else if (!clientCode.isValid(requiredAction)) { + } else if (!clientCode.isValidAction(requiredAction)) { + event.client(clientCode.getClientSession().getClient()); event.error(Errors.INVALID_CODE); response = ErrorPage.error(session, Messages.INVALID_CODE); return false; + } else if (!clientCode.isActionActive(requiredAction)) { + event.client(clientCode.getClientSession().getClient()); + event.error(Errors.EXPIRED_CODE); + response = ErrorPage.error(session, Messages.INVALID_CODE); + return false; } else { return true; } @@ -191,10 +182,16 @@ public class LoginActionsService { boolean check(String code, String requiredAction, String alternativeRequiredAction) { if (!check(code)) { return false; - } else if (!(clientCode.isValid(requiredAction) || clientCode.isValid(alternativeRequiredAction))) { + } else if (!(clientCode.isValidAction(requiredAction) || clientCode.isValidAction(alternativeRequiredAction))) { + event.client(clientCode.getClientSession().getClient()); event.error(Errors.INVALID_CODE); response = ErrorPage.error(session, Messages.INVALID_CODE); return false; + } else if (!(clientCode.isActionActive(requiredAction) || clientCode.isActionActive(alternativeRequiredAction))) { + event.client(clientCode.getClientSession().getClient()); + event.error(Errors.EXPIRED_CODE); + response = ErrorPage.error(session, Messages.INVALID_CODE); + return false; } else { return true; } @@ -217,6 +214,21 @@ public class LoginActionsService { response = ErrorPage.error(session, Messages.INVALID_CODE); return false; } + ClientSessionModel clientSession = clientCode.getClientSession(); + event.detail(Details.CODE_ID, clientSession.getId()); + ClientModel client = clientSession.getClient(); + if (client == null) { + event.error(Errors.CLIENT_NOT_FOUND); + response = ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER); + return false; + } + session.getContext().setClient(client); + + if (!client.isEnabled()) { + event.error(Errors.CLIENT_NOT_FOUND); + response = ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED); + return false; + } session.getContext().setClient(clientCode.getClientSession().getClient()); return true; } @@ -246,9 +258,24 @@ public class LoginActionsService { clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); } - return session.getProvider(LoginFormsProvider.class) - .setClientSessionCode(clientSessionCode.getCode()) - .createLogin(); + AuthenticationFlowModel flow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW); + String flowId = flow.getId(); + AuthenticationProcessor processor = new AuthenticationProcessor(); + processor.setClientSession(clientSession) + .setFlowId(flowId) + .setConnection(clientConnection) + .setEventBuilder(event) + .setProtector(authManager.getProtector()) + .setRealm(realm) + .setSession(session) + .setUriInfo(uriInfo) + .setRequest(request); + + try { + return processor.authenticate(); + } catch (Exception e) { + return processor.handleBrowserException(e); + } } /** @@ -283,7 +310,7 @@ public class LoginActionsService { .createRegistration(); } - /** + /** * URL called after login page. YOU SHOULD NEVER INVOKE THIS DIRECTLY! * * @param code @@ -295,51 +322,18 @@ public class LoginActionsService { public Response authForm(@QueryParam("code") String code, @QueryParam("action") String action) { event.event(EventType.LOGIN); - if (!checkSsl()) { - event.error(Errors.SSL_REQUIRED); - return ErrorPage.error(session, Messages.HTTPS_REQUIRED); + Checks checks = new Checks(); + if (!checks.check(code, ClientSessionModel.Action.AUTHENTICATE.name())) { + return checks.response; } + final ClientSessionCode clientCode = checks.clientCode; + final ClientSessionModel clientSession = clientCode.getClientSession(); - if (!realm.isEnabled()) { - event.error(Errors.REALM_DISABLED); - return ErrorPage.error(session, Messages.REALM_NOT_ENABLED); - } - ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm); - if (clientCode == null) { - event.error(Errors.INVALID_CODE); - return ErrorPage.error(session, Messages.INVALID_CODE); - } - - ClientSessionModel clientSession = clientCode.getClientSession(); - event.detail(Details.CODE_ID, clientSession.getId()); - - if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE.name()) || clientSession.getUserSession() != null) { - event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE); - return ErrorPage.error(session, Messages.EXPIRED_CODE); - } - - ClientModel client = clientSession.getClient(); - if (client == null) { - event.error(Errors.CLIENT_NOT_FOUND); - return ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER); - } - session.getContext().setClient(client); - - if (!client.isEnabled()) { - event.error(Errors.CLIENT_DISABLED); - return ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED); - } - - String flowId = null; - for (AuthenticationFlowModel flow : realm.getAuthenticationFlows()) { - if (flow.getAlias().equals("browser")) { - flowId = flow.getId(); - break; - } - } + String flowAlias = DefaultAuthenticationFlows.BROWSER_FLOW; + AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias); AuthenticationProcessor processor = new AuthenticationProcessor(); processor.setClientSession(clientSession) - .setFlowId(flowId) + .setFlowId(flow.getId()) .setConnection(clientConnection) .setEventBuilder(event) .setProtector(authManager.getProtector()) @@ -357,142 +351,6 @@ public class LoginActionsService { } - /** - * URL called after login page. YOU SHOULD NEVER INVOKE THIS DIRECTLY! - * - * @param code - * @param formData - * @return - */ - @Path("request/login") - @POST - @Consumes(MediaType.APPLICATION_FORM_URLENCODED) - public Response processLogin(@QueryParam("code") String code, - final MultivaluedMap formData) { - event.event(EventType.LOGIN); - if (!checkSsl()) { - event.error(Errors.SSL_REQUIRED); - return ErrorPage.error(session, Messages.HTTPS_REQUIRED); - } - - if (!realm.isEnabled()) { - event.error(Errors.REALM_DISABLED); - return ErrorPage.error(session, Messages.REALM_NOT_ENABLED); - } - ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm); - if (clientCode == null) { - event.error(Errors.INVALID_CODE); - return ErrorPage.error(session, Messages.INVALID_CODE); - } - - ClientSessionModel clientSession = clientCode.getClientSession(); - event.detail(Details.CODE_ID, clientSession.getId()); - - if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE.name()) || clientSession.getUserSession() != null) { - clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); - event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE); - return session.getProvider(LoginFormsProvider.class) - .setError(Messages.EXPIRED_CODE) - .setClientSessionCode(clientCode.getCode()) - .createLogin(); - } - - String username = formData.getFirst(AuthenticationManager.FORM_USERNAME); - - String rememberMe = formData.getFirst("rememberMe"); - boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on"); - - event.client(clientSession.getClient().getClientId()) - .detail(Details.REDIRECT_URI, clientSession.getRedirectUri()) - .detail(Details.RESPONSE_TYPE, "code") - .detail(Details.AUTH_METHOD, "form") - .detail(Details.USERNAME, username); - - if (remember) { - event.detail(Details.REMEMBER_ME, "true"); - } - - ClientModel client = clientSession.getClient(); - if (client == null) { - event.error(Errors.CLIENT_NOT_FOUND); - return ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER); - } - if (!client.isEnabled()) { - event.error(Errors.CLIENT_DISABLED); - return ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED); - } - - session.getContext().setClient(clientSession.getClient()); - - if (formData.containsKey("cancel")) { - event.error(Errors.REJECTED_BY_USER); - LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod()); - protocol.setRealm(realm) - .setHttpHeaders(headers) - .setUriInfo(uriInfo); - return protocol.cancelLogin(clientSession); - } - - AuthenticationManager.AuthenticationStatus status = authManager.authenticateForm(session, clientConnection, realm, formData); - - if (remember) { - authManager.createRememberMeCookie(realm, username, uriInfo, clientConnection); - } else { - authManager.expireRememberMeCookie(realm, uriInfo, clientConnection); - } - - UserModel user = KeycloakModelUtils.findUserByNameOrEmail(session, realm, username); - if (user != null) { - event.user(user); - } - - switch (status) { - case SUCCESS: - case ACTIONS_REQUIRED: - UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", remember, null, null); - TokenManager.attachClientSession(userSession, clientSession); - event.session(userSession); - return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event); - case ACCOUNT_TEMPORARILY_DISABLED: - event.error(Errors.USER_TEMPORARILY_DISABLED); - return session.getProvider(LoginFormsProvider.class) - .setError(Messages.ACCOUNT_TEMPORARILY_DISABLED) - .setFormData(formData) - .setClientSessionCode(clientCode.getCode()) - .createLogin(); - case ACCOUNT_DISABLED: - event.error(Errors.USER_DISABLED); - return session.getProvider(LoginFormsProvider.class) - .setError(Messages.ACCOUNT_DISABLED) - .setClientSessionCode(clientCode.getCode()) - .setFormData(formData).createLogin(); - case MISSING_TOTP: - formData.remove(CredentialRepresentation.PASSWORD); - - String passwordToken = new JWSBuilder().jsonContent(new PasswordToken(realm.getName(), user.getId())).rsa256(realm.getPrivateKey()); - formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken); - - return session.getProvider(LoginFormsProvider.class) - .setFormData(formData) - .setClientSessionCode(clientCode.getCode()) - .createLoginTotp(); - case INVALID_USER: - event.error(Errors.USER_NOT_FOUND); - return session.getProvider(LoginFormsProvider.class) - .setError(Messages.INVALID_USER) - .setFormData(formData) - .setClientSessionCode(clientCode.getCode()) - .createLogin(); - default: - event.error(Errors.INVALID_USER_CREDENTIALS); - return session.getProvider(LoginFormsProvider.class) - .setError(Messages.INVALID_USER) - .setFormData(formData) - .setClientSessionCode(clientCode.getCode()) - .createLogin(); - } - } - /** * Registration * @@ -993,19 +851,14 @@ public class LoginActionsService { public Response sendPasswordReset(@QueryParam("code") String code, final MultivaluedMap formData) { event.event(EventType.SEND_RESET_PASSWORD); - if (!checkSsl()) { - return ErrorPage.error(session, Messages.HTTPS_REQUIRED); + Checks checks = new Checks(); + if (!checks.check(code)) { + return checks.response; } - if (!realm.isEnabled()) { - event.error(Errors.REALM_DISABLED); - return ErrorPage.error(session, Messages.REALM_NOT_ENABLED); - } - ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm); - if (accessCode == null) { - event.error(Errors.INVALID_CODE); - return ErrorPage.error(session, Messages.INVALID_CODE); - } - ClientSessionModel clientSession = accessCode.getClientSession(); + final ClientSessionCode accessCode = checks.clientCode; + final ClientSessionModel clientSession = accessCode.getClientSession(); + ClientModel client = clientSession.getClient(); + String username = formData.getFirst("username"); if(username == null || username.isEmpty()) { @@ -1016,16 +869,6 @@ public class LoginActionsService { .createPasswordReset(); } - ClientModel client = clientSession.getClient(); - if (client == null) { - return ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER); - } - if (!client.isEnabled()) { - return ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED); - } - - session.getContext().setClient(client); - event.client(client.getClientId()) .detail(Details.REDIRECT_URI, clientSession.getRedirectUri()) .detail(Details.RESPONSE_TYPE, "code") @@ -1114,43 +957,6 @@ public class LoginActionsService { public Object requiredAction(@QueryParam("code") String code, @PathParam("action") String action) { event.event(EventType.LOGIN); - if (!checkSsl()) { - event.error(Errors.SSL_REQUIRED); - throw new WebApplicationException(ErrorPage.error(session, Messages.HTTPS_REQUIRED)); - } - - if (!realm.isEnabled()) { - event.error(Errors.REALM_DISABLED); - return ErrorPage.error(session, Messages.REALM_NOT_ENABLED); - } - ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm); - if (clientCode == null) { - event.error(Errors.INVALID_CODE); - throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE)); - } - - final ClientSessionModel clientSession = clientCode.getClientSession(); - event.detail(Details.CODE_ID, clientSession.getId()); - - /* - if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE.name()) || clientSession.getUserSession() != null) { - event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE); - throw new WebApplicationException(ErrorPage.error(session, Messages.EXPIRED_CODE)); - } - */ - - ClientModel client = clientSession.getClient(); - if (client == null) { - event.error(Errors.CLIENT_NOT_FOUND); - throw new WebApplicationException( ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER)); - } - session.getContext().setClient(client); - - if (!client.isEnabled()) { - event.error(Errors.CLIENT_NOT_FOUND); - throw new WebApplicationException( ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED)); - } - if (action == null) { logger.error("required action was null"); event.error(Errors.INVALID_CODE); @@ -1164,6 +970,20 @@ public class LoginActionsService { event.error(Errors.INVALID_CODE); throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE)); } + Checks checks = new Checks(); + if (!checks.check(code, action)) { + return checks.response; + } + final ClientSessionCode clientCode = checks.clientCode; + final ClientSessionModel clientSession = clientCode.getClientSession(); + + if (clientSession.getUserSession() == null) { + logger.error("user session was null"); + event.error(Errors.USER_SESSION_NOT_FOUND); + throw new WebApplicationException(ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE)); + } + + RequiredActionContext context = new RequiredActionContext() { @Override public EventBuilder getEvent() { @@ -1209,7 +1029,14 @@ public class LoginActionsService { public HttpRequest getHttpRequest() { return request; } - }; + + @Override + public String generateAccessCode(String action) { + ClientSessionCode code = new ClientSessionCode(getRealm(), getClientSession()); + code.setAction(action); + return code.getCode(); + } + }; return provider.jaxrsService(context); diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java index dc674f7e0c..656c4dccd7 100755 --- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java @@ -161,7 +161,7 @@ public class RealmsResource { public IdentityBrokerService getBrokerService(final @PathParam("realm") String name) { RealmModel realm = init(name); - IdentityBrokerService brokerService = new IdentityBrokerService(realm); + IdentityBrokerService brokerService = new IdentityBrokerService(realm, protector); ResteasyProviderFactory.getInstance().injectProperties(brokerService); brokerService.init(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java index f1805301b9..e6851b5324 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java @@ -334,7 +334,7 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory { Assert.assertThat(actual.getSessionId(), sessionId); if (details == null || details.isEmpty()) { - Assert.assertNull(actual.getDetails()); +// Assert.assertNull(actual.getDetails()); } else { Assert.assertNotNull(actual.getDetails()); for (Map.Entry> d : details.entrySet()) { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java index 26b1f799d3..087f6c324a 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java @@ -178,7 +178,7 @@ public class ResetPasswordTest { loginPage.login("login@test.com", "password"); - Event loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "login@test.com").assertEvent(); + Event loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent(); String code = oauth.getCurrentQuery().get("code"); OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password"); @@ -372,7 +372,7 @@ public class ResetPasswordTest { assertEquals("An error occurred, please login again through your application.", errorPage.getError()); - events.expectRequiredAction(EventType.RESET_PASSWORD).error("invalid_code").client((String) null).user((String) null).session((String) null).clearDetails().assertEvent(); + events.expectRequiredAction(EventType.RESET_PASSWORD).error("expired_code").client("test-app").user((String) null).session((String) null).clearDetails().assertEvent(); } finally { Time.setOffset(0); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java index cdf61d0331..d5d7296049 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java @@ -152,20 +152,6 @@ public class AccessTokenPerfTest { return b.build(realm).toString(); } - public String getProcessLoginUrl(String state) { - UriBuilder b = LoginActionsService.processLoginUrl(UriBuilder.fromUri(baseUrl)); - if (clientId != null) { - b.queryParam(OAuth2Constants.CLIENT_ID, clientId); - } - if (redirectUri != null) { - b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri); - } - if (state != null) { - b.queryParam(OAuth2Constants.STATE, state); - } - return b.build(realm).toString(); - } - static Pattern actionParser = Pattern.compile("action=\"([^\"]+)\""); public void run() {