From 25347cd45e86df4a6f1666fcbd1decf763a53e16 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Wed, 27 Jan 2016 22:14:28 -0500 Subject: [PATCH] browser back button --- .../InfinispanUserSessionProvider.java | 5 +- .../keycloak/models/ClientSessionModel.java | 1 + .../services/managers/ClientSessionCode.java | 5 +- .../AuthenticationProcessor.java | 15 +++ .../protocol/AuthorizationEndpointBase.java | 6 +- .../oidc/endpoints/AuthorizationEndpoint.java | 7 +- .../keycloak/protocol/saml/SamlService.java | 34 +++-- .../profile/ecp/SamlEcpProfileService.java | 4 +- .../managers/AuthenticationManager.java | 3 +- .../resources/IdentityBrokerService.java | 4 + .../resources/LoginActionsService.java | 125 +++++++++++------- .../services/util/CacheControlUtil.java | 7 + 12 files changed, 148 insertions(+), 68 deletions(-) mode change 100644 => 100755 services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java mode change 100644 => 100755 services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java mode change 100644 => 100755 services/src/main/java/org/keycloak/services/util/CacheControlUtil.java diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java index ce7ebf143c..7a472aaa05 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java @@ -54,9 +54,12 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { entity.setTimestamp(Time.currentTime()); entity.setClient(client.getId()); + tx.put(sessionCache, id, entity); - return wrap(realm, entity, false); + ClientSessionAdapter wrap = wrap(realm, entity, false); + wrap.setNote(ClientSessionModel.ACTION_KEY, KeycloakModelUtils.generateCodeSecret()); + return wrap; } @Override diff --git a/server-spi/src/main/java/org/keycloak/models/ClientSessionModel.java b/server-spi/src/main/java/org/keycloak/models/ClientSessionModel.java index 293f38b97b..803473a9fc 100755 --- a/server-spi/src/main/java/org/keycloak/models/ClientSessionModel.java +++ b/server-spi/src/main/java/org/keycloak/models/ClientSessionModel.java @@ -7,6 +7,7 @@ import java.util.Set; * @author Stian Thorgersen */ public interface ClientSessionModel { + public static final String ACTION_KEY = "action_key"; public String getId(); public RealmModel getRealm(); diff --git a/server-spi/src/main/java/org/keycloak/services/managers/ClientSessionCode.java b/server-spi/src/main/java/org/keycloak/services/managers/ClientSessionCode.java index 91c388f704..64386ab742 100755 --- a/server-spi/src/main/java/org/keycloak/services/managers/ClientSessionCode.java +++ b/server-spi/src/main/java/org/keycloak/services/managers/ClientSessionCode.java @@ -22,8 +22,6 @@ import java.util.UUID; */ public class ClientSessionCode { - public static final String ACTION_KEY = "action_key"; - private static final byte[] HASH_SEPERATOR = "//".getBytes(); private final RealmModel realm; @@ -211,7 +209,6 @@ public class ClientSessionCode { public void setAction(String action) { clientSession.setAction(action); - clientSession.setNote(ACTION_KEY, UUID.randomUUID().toString()); clientSession.setTimestamp(Time.currentTime()); } @@ -237,7 +234,7 @@ public class ClientSessionCode { mac.init(codeSecretKey); mac.update(clientSession.getId().getBytes()); mac.update(HASH_SEPERATOR); - mac.update(clientSession.getNote(ACTION_KEY).getBytes()); + mac.update(clientSession.getNote(ClientSessionModel.ACTION_KEY).getBytes()); return Base64Url.encode(mac.doFinal()); } catch (Exception e) { throw new RuntimeException(e); diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java index 184df85f3f..d40b96bd86 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java @@ -1,5 +1,6 @@ package org.keycloak.authentication; +import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.common.ClientConnection; import org.keycloak.OAuth2Constants; @@ -30,6 +31,7 @@ import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.LoginActionsService; import org.keycloak.common.util.Time; +import org.keycloak.services.util.CacheControlUtil; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @@ -575,9 +577,11 @@ public class AuthenticationProcessor { .setConnection(connection) .setEventBuilder(event) .setRealm(realm) + .setBrowserFlow(isBrowserFlow()) .setSession(session) .setUriInfo(uriInfo) .setRequest(request); + CacheControlUtil.noBackButtonCacheControlHeader(); return processor.authenticate(); } else { @@ -656,6 +660,17 @@ public class AuthenticationProcessor { } } + + public Response redirectToFlow() { + String code = generateCode(); + + URI redirect = LoginActionsService.loginActionsBaseUrl(getUriInfo()) + .path(flowPath) + .queryParam(OAuth2Constants.CODE, code).build(getRealm().getName()); + return Response.status(302).location(redirect).build(); + + } + public static Response redirectToRequiredActions(RealmModel realm, ClientSessionModel clientSession, UriInfo uriInfo) { // redirect to non-action url so browser refresh button works without reposting past data diff --git a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java index 1b2e87148c..2ae084f40c 100755 --- a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java +++ b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java @@ -74,9 +74,10 @@ public abstract class AuthorizationEndpointBase { * @param clientSession for current request * @param protocol handler for protocol used to initiate login * @param isPassive set to true if login should be passive (without login screen shown) + * @param redirectToAuthentication if true redirect to flow url. If initial call to protocol is a POST, you probably want to do this. This is so we can disable the back button on browser * @return response to be returned to the browser */ - protected Response handleBrowserAuthenticationRequest(ClientSessionModel clientSession, LoginProtocol protocol, boolean isPassive) { + protected Response handleBrowserAuthenticationRequest(ClientSessionModel clientSession, LoginProtocol protocol, boolean isPassive, boolean redirectToAuthentication) { List identityProviders = realm.getIdentityProviders(); for (IdentityProviderModel identityProvider : identityProviders) { @@ -115,6 +116,9 @@ public abstract class AuthorizationEndpointBase { } else { try { RestartLoginCookie.setRestartCookie(realm, clientConnection, uriInfo, clientSession); + if (redirectToAuthentication) { + return processor.redirectToFlow(); + } return processor.authenticate(); } catch (Exception e) { return processor.handleBrowserException(e); 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 old mode 100644 new mode 100755 index 61db4f1410..f4dd22a1c3 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -28,6 +28,7 @@ import org.keycloak.services.Urls; import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.LoginActionsService; +import org.keycloak.services.util.CacheControlUtil; /** * @author Stian Thorgersen @@ -87,7 +88,8 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { checkRedirectUri(); createClientSession(); - + // So back button doesn't work + CacheControlUtil.noBackButtonCacheControlHeader(); switch (action) { case REGISTER: return buildRegister(); @@ -219,7 +221,6 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL); clientSession.setRedirectUri(redirectUri); clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); - clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret()); clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, responseType); clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUriParam); clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())); @@ -249,7 +250,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { this.event.event(EventType.LOGIN); clientSession.setNote(Details.AUTH_TYPE, CODE_AUTH_TYPE); - return handleBrowserAuthenticationRequest(clientSession, new OIDCLoginProtocol(session, realm, uriInfo, headers, event), prompt != null && prompt.equals("none")); + return handleBrowserAuthenticationRequest(clientSession, new OIDCLoginProtocol(session, realm, uriInfo, headers, event), prompt != null && prompt.equals("none"), false); } private Response buildRegister() { diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java index 0260fc7aba..566cf87ee4 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java +++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java @@ -18,6 +18,7 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.jboss.logging.Logger; +import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.common.VerificationException; import org.keycloak.common.util.StreamUtil; @@ -50,6 +51,7 @@ import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.RealmsResource; +import org.keycloak.services.util.CacheControlUtil; /** * Resource class for the oauth/openid connect token service @@ -66,6 +68,12 @@ public class SamlService extends AuthorizationEndpointBase { } public abstract class BindingProtocol { + + // this is to support back button on browser + // if true, we redirect to authenticate URL otherwise back button behavior has bad side effects + // and we want to turn it off. + protected boolean redirectToAuthentication; + protected Response basicChecks(String samlRequest, String samlResponse) { if (!checkSsl()) { event.event(EventType.LOGIN); @@ -229,7 +237,6 @@ public class SamlService extends AuthorizationEndpointBase { clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL); clientSession.setRedirectUri(redirect); clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); - clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret()); clientSession.setNote(SamlProtocol.SAML_BINDING, bindingType); clientSession.setNote(GeneralConstants.RELAY_STATE, relayState); clientSession.setNote(SamlProtocol.SAML_REQUEST_ID, requestAbstractType.getID()); @@ -248,7 +255,7 @@ public class SamlService extends AuthorizationEndpointBase { } } - return newBrowserAuthentication(clientSession, requestAbstractType.isIsPassive()); + return newBrowserAuthentication(clientSession, requestAbstractType.isIsPassive(), redirectToAuthentication); } protected String getBindingType(AuthnRequestType requestAbstractType) { @@ -449,13 +456,13 @@ public class SamlService extends AuthorizationEndpointBase { } - protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive) { + protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive, boolean redirectToAuthentication) { SamlProtocol samlProtocol = new SamlProtocol().setEventBuilder(event).setHttpHeaders(headers).setRealm(realm).setSession(session).setUriInfo(uriInfo); - return newBrowserAuthentication(clientSession, isPassive, samlProtocol); + return newBrowserAuthentication(clientSession, isPassive, redirectToAuthentication, samlProtocol); } - protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive, SamlProtocol samlProtocol) { - return handleBrowserAuthenticationRequest(clientSession, samlProtocol, isPassive); + protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive, boolean redirectToAuthentication, SamlProtocol samlProtocol) { + return handleBrowserAuthenticationRequest(clientSession, samlProtocol, isPassive, redirectToAuthentication); } /** @@ -463,21 +470,29 @@ public class SamlService extends AuthorizationEndpointBase { @GET public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest, @QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse, @QueryParam(GeneralConstants.RELAY_STATE) String relayState) { logger.debug("SAML GET"); + CacheControlUtil.noBackButtonCacheControlHeader(); return new RedirectBindingProtocol().execute(samlRequest, samlResponse, relayState); } /** */ @POST + @NoCache @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest, @FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse, @FormParam(GeneralConstants.RELAY_STATE) String relayState) { logger.debug("SAML POST"); - return new PostBindingProtocol().execute(samlRequest, samlResponse, relayState); + PostBindingProtocol postBindingProtocol = new PostBindingProtocol(); + // this is to support back button on browser + // if true, we redirect to authenticate URL otherwise back button behavior has bad side effects + // and we want to turn it off. + postBindingProtocol.redirectToAuthentication = true; + return postBindingProtocol.execute(samlRequest, samlResponse, relayState); } @GET @Path("descriptor") @Produces(MediaType.APPLICATION_XML) + @NoCache public String getDescriptor() throws Exception { return getIDPMetadataDescriptor(uriInfo, realm); @@ -499,6 +514,7 @@ public class SamlService extends AuthorizationEndpointBase { @Produces(MediaType.TEXT_HTML) public Response idpInitiatedSSO(@PathParam("client") String clientUrlName, @QueryParam("RelayState") String relayState) { event.event(EventType.LOGIN); + CacheControlUtil.noBackButtonCacheControlHeader(); ClientModel client = null; for (ClientModel c : realm.getClients()) { String urlName = c.getAttribute(SamlProtocol.SAML_IDP_INITIATED_SSO_URL_NAME); @@ -537,7 +553,6 @@ public class SamlService extends AuthorizationEndpointBase { ClientSessionModel clientSession = session.sessions().createClientSession(realm, client); clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL); clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); - clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret()); clientSession.setNote(SamlProtocol.SAML_BINDING, SamlProtocol.SAML_POST_BINDING); clientSession.setNote(SamlProtocol.SAML_IDP_INITIATED_LOGIN, "true"); clientSession.setRedirectUri(redirect); @@ -549,11 +564,12 @@ public class SamlService extends AuthorizationEndpointBase { clientSession.setNote(GeneralConstants.RELAY_STATE, relayState); } - return newBrowserAuthentication(clientSession, false); + return newBrowserAuthentication(clientSession, false, false); } @POST + @NoCache @Consumes({"application/soap+xml",MediaType.TEXT_XML}) public Response soapBinding(InputStream inputStream) { SamlEcpProfileService bindingService = new SamlEcpProfileService(realm, event); diff --git a/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java b/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java index 2885f38067..7d30170ef8 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java +++ b/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java @@ -68,8 +68,8 @@ public class SamlEcpProfileService extends SamlService { } @Override - protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive, SamlProtocol samlProtocol) { - return super.newBrowserAuthentication(clientSession, isPassive, createEcpSamlProtocol()); + protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive, boolean redirectToAuthentication, SamlProtocol samlProtocol) { + return super.newBrowserAuthentication(clientSession, isPassive, redirectToAuthentication, createEcpSamlProtocol()); } private SamlProtocol createEcpSamlProtocol() { 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 0f9a124bef..bb7aae374b 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -528,7 +528,8 @@ public class AuthenticationManager { // Skip grant screen if everything was already approved by this user if (realmRoles.size() > 0 || resourceRoles.size() > 0 || protocolMappers.size() > 0) { - accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT.name()); + accessCode.setAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name()); + clientSession.setNote(CURRENT_REQUIRED_ACTION, ClientSessionModel.Action.OAUTH_GRANT.name()); return session.getProvider(LoginFormsProvider.class) .setClientSessionCode(accessCode.getCode()) diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java old mode 100644 new mode 100755 index 937df1652a..f19151a3f8 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -17,6 +17,7 @@ */ package org.keycloak.services.resources; +import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.OAuth2Constants; @@ -61,6 +62,7 @@ import org.keycloak.services.ErrorResponse; import org.keycloak.services.ErrorPage; import org.keycloak.services.ServicesLogger; import org.keycloak.services.Urls; +import org.keycloak.services.util.CacheControlUtil; import org.keycloak.services.validation.Validation; import org.keycloak.broker.social.SocialIdentityProvider; import org.keycloak.common.util.ObjectUtil; @@ -694,6 +696,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal processor.setClientSession(clientSession) .setFlowPath(LoginActionsService.AUTHENTICATE_PATH) .setFlowId(flowId) + .setBrowserFlow(true) .setConnection(clientConnection) .setEventBuilder(event) .setRealm(realmModel) @@ -703,6 +706,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal if (errorMessage != null) processor.setForwardedErrorMessage(new FormMessage(null, errorMessage)); try { + CacheControlUtil.noBackButtonCacheControlHeader(); return processor.authenticate(); } catch (Exception e) { return processor.handleBrowserException(e); 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 e23f9df216..bf654f6286 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -67,6 +67,7 @@ import org.keycloak.services.Urls; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.messages.Messages; +import org.keycloak.services.util.CacheControlUtil; import org.keycloak.services.util.CookieHelper; import javax.ws.rs.Consumes; @@ -155,6 +156,7 @@ public class LoginActionsService { public LoginActionsService(RealmModel realm, EventBuilder event) { this.realm = realm; this.event = event; + CacheControlUtil.noBackButtonCacheControlHeader(); } private boolean checkSsl() { @@ -175,32 +177,51 @@ public class LoginActionsService { if (!verifyCode(code)) { return false; } - if (!verifyAction(requiredAction, actionType)) { - return false; - } else { - return true; + if (!clientCode.isValidAction(requiredAction)) { + if (ClientSessionModel.Action.REQUIRED_ACTIONS.name().equals(clientCode.getClientSession().getAction())) { + response = redirectToRequiredActions(code); + return false; + } else { + invalidAction(); + } } - } + if (isActionActive(actionType)) return false; + return true; + } public boolean verifyAction(String requiredAction, ClientSessionCode.ActionType actionType) { + if (isValidAction(requiredAction)) return false; + if (isActionActive(actionType)) return false; + return true; + } + + public boolean isValidAction(String requiredAction) { if (!clientCode.isValidAction(requiredAction)) { - event.client(clientCode.getClientSession().getClient()); - event.error(Errors.INVALID_CODE); - response = ErrorPage.error(session, Messages.INVALID_CODE); - return false; + invalidAction(); + return true; } + return false; + } + + private void invalidAction() { + event.client(clientCode.getClientSession().getClient()); + event.error(Errors.INVALID_CODE); + response = ErrorPage.error(session, Messages.INVALID_CODE); + } + + public boolean isActionActive(ClientSessionCode.ActionType actionType) { if (!clientCode.isActionActive(actionType)) { event.client(clientCode.getClientSession().getClient()); event.clone().error(Errors.EXPIRED_CODE); if (clientCode.getClientSession().getAction().equals(ClientSessionModel.Action.AUTHENTICATE.name())) { AuthenticationProcessor.resetFlow(clientCode.getClientSession()); response = processAuthentication(null, clientCode.getClientSession(), Messages.LOGIN_TIMEOUT); - return false; + return true; } response = ErrorPage.error(session, Messages.EXPIRED_CODE); - return false; + return true; } - return true; + return false; } public boolean verifyCode(String code) { @@ -256,8 +277,48 @@ public class LoginActionsService { session.getContext().setClient(client); return true; } + + public boolean verifyRequiredAction(String code, String executedAction) { + if (!verifyCode(code)) { + return false; + } + if (isValidAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name())) return false; + if (isActionActive(ClientSessionCode.ActionType.USER)) return false; + + final ClientSessionModel clientSession = clientCode.getClientSession(); + + final UserSessionModel userSession = clientSession.getUserSession(); + if (userSession == null) { + logger.userSessionNull(); + event.error(Errors.USER_SESSION_NOT_FOUND); + throw new WebApplicationException(ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE)); + } + if (!AuthenticationManager.isSessionValid(realm, userSession)) { + AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true); + event.error(Errors.INVALID_CODE); + response = ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE); + return false; + } + + if (executedAction == null && userSession != null) { // do next required action only if user is already authenticated + initEvent(clientSession); + event.event(EventType.LOGIN); + response = AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event); + return false; + } + + if (!executedAction.equals(clientSession.getNote(AuthenticationManager.CURRENT_REQUIRED_ACTION))) { + logger.debug("required action doesn't match current required action"); + clientSession.removeNote(AuthenticationManager.CURRENT_REQUIRED_ACTION); + response = redirectToRequiredActions(code); + return false; + } + return true; + + } } + /** * protocol independent login page entry point * @@ -361,13 +422,11 @@ public class LoginActionsService { ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID); ClientSessionModel clientSession = session.sessions().createClientSession(realm, client); clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); - clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret()); //clientSession.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true"); clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL); String redirectUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName()).toString(); clientSession.setRedirectUri(redirectUri); clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); - clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret()); clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE); clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUri); clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())); @@ -589,19 +648,12 @@ public class LoginActionsService { @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public Response processConsent(final MultivaluedMap formData) { event.event(EventType.LOGIN); - - - if (!checkSsl()) { - return ErrorPage.error(session, Messages.HTTPS_REQUIRED); - } - String code = formData.getFirst("code"); - - ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm); - if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT.name(), ClientSessionCode.ActionType.LOGIN)) { - event.error(Errors.INVALID_CODE); - return ErrorPage.error(session, Messages.INVALID_ACCESS_CODE); + Checks checks = new Checks(); + if (!checks.verifyRequiredAction(code, ClientSessionModel.Action.OAUTH_GRANT.name())) { + return checks.response; } + ClientSessionCode accessCode = checks.clientCode; ClientSessionModel clientSession = accessCode.getClientSession(); initEvent(clientSession); @@ -610,11 +662,6 @@ public class LoginActionsService { UserModel user = userSession.getUser(); ClientModel client = clientSession.getClient(); - if (!AuthenticationManager.isSessionValid(realm, userSession)) { - AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true); - event.error(Errors.INVALID_CODE); - return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE); - } if (formData.containsKey("cancel")) { LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod()); @@ -810,29 +857,13 @@ public class LoginActionsService { event.event(EventType.CUSTOM_REQUIRED_ACTION); event.detail(Details.CUSTOM_REQUIRED_ACTION, action); Checks checks = new Checks(); - if (!checks.verifyCode(code, ClientSessionModel.Action.REQUIRED_ACTIONS.name(), ClientSessionCode.ActionType.USER)) { + if (!checks.verifyRequiredAction(code, action)) { return checks.response; } final ClientSessionCode clientCode = checks.clientCode; final ClientSessionModel clientSession = clientCode.getClientSession(); final UserSessionModel userSession = clientSession.getUserSession(); - if (userSession == null) { - logger.userSessionNull(); - event.error(Errors.USER_SESSION_NOT_FOUND); - throw new WebApplicationException(ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE)); - } - if (action == null && userSession != null) { // do next required action only if user is already authenticated - initEvent(clientSession); - event.event(EventType.LOGIN); - return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event); - } - - if (!action.equals(clientSession.getNote(AuthenticationManager.CURRENT_REQUIRED_ACTION))) { - logger.debug("required action doesn't match current required action"); - clientSession.removeNote(AuthenticationManager.CURRENT_REQUIRED_ACTION); - redirectToRequiredActions(code); - } RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, action); if (factory == null) { diff --git a/services/src/main/java/org/keycloak/services/util/CacheControlUtil.java b/services/src/main/java/org/keycloak/services/util/CacheControlUtil.java old mode 100644 new mode 100755 index 0e2f1e0c97..3c9199d959 --- a/services/src/main/java/org/keycloak/services/util/CacheControlUtil.java +++ b/services/src/main/java/org/keycloak/services/util/CacheControlUtil.java @@ -1,5 +1,7 @@ package org.keycloak.services.util; +import org.jboss.resteasy.spi.HttpResponse; +import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.Config; import javax.ws.rs.core.CacheControl; @@ -9,6 +11,11 @@ import javax.ws.rs.core.CacheControl; */ public class CacheControlUtil { + public static void noBackButtonCacheControlHeader() { + HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class); + response.getOutputHeaders().putSingle("Cache-Control", "no-store, must-revalidate, max-age=0"); + } + public static CacheControl getDefaultCacheControl() { CacheControl cacheControl = new CacheControl(); cacheControl.setNoTransform(false);