browser back button

This commit is contained in:
Bill Burke 2016-01-27 22:14:28 -05:00
parent 1b0ccae4ac
commit 25347cd45e
12 changed files with 148 additions and 68 deletions

View file

@ -54,9 +54,12 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
entity.setTimestamp(Time.currentTime()); entity.setTimestamp(Time.currentTime());
entity.setClient(client.getId()); entity.setClient(client.getId());
tx.put(sessionCache, id, entity); 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 @Override

View file

@ -7,6 +7,7 @@ import java.util.Set;
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public interface ClientSessionModel { public interface ClientSessionModel {
public static final String ACTION_KEY = "action_key";
public String getId(); public String getId();
public RealmModel getRealm(); public RealmModel getRealm();

View file

@ -22,8 +22,6 @@ import java.util.UUID;
*/ */
public class ClientSessionCode { public class ClientSessionCode {
public static final String ACTION_KEY = "action_key";
private static final byte[] HASH_SEPERATOR = "//".getBytes(); private static final byte[] HASH_SEPERATOR = "//".getBytes();
private final RealmModel realm; private final RealmModel realm;
@ -211,7 +209,6 @@ public class ClientSessionCode {
public void setAction(String action) { public void setAction(String action) {
clientSession.setAction(action); clientSession.setAction(action);
clientSession.setNote(ACTION_KEY, UUID.randomUUID().toString());
clientSession.setTimestamp(Time.currentTime()); clientSession.setTimestamp(Time.currentTime());
} }
@ -237,7 +234,7 @@ public class ClientSessionCode {
mac.init(codeSecretKey); mac.init(codeSecretKey);
mac.update(clientSession.getId().getBytes()); mac.update(clientSession.getId().getBytes());
mac.update(HASH_SEPERATOR); mac.update(HASH_SEPERATOR);
mac.update(clientSession.getNote(ACTION_KEY).getBytes()); mac.update(clientSession.getNote(ClientSessionModel.ACTION_KEY).getBytes());
return Base64Url.encode(mac.doFinal()); return Base64Url.encode(mac.doFinal());
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);

View file

@ -1,5 +1,6 @@
package org.keycloak.authentication; package org.keycloak.authentication;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.common.ClientConnection; import org.keycloak.common.ClientConnection;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
@ -30,6 +31,7 @@ import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.LoginActionsService; import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.services.util.CacheControlUtil;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
@ -575,9 +577,11 @@ public class AuthenticationProcessor {
.setConnection(connection) .setConnection(connection)
.setEventBuilder(event) .setEventBuilder(event)
.setRealm(realm) .setRealm(realm)
.setBrowserFlow(isBrowserFlow())
.setSession(session) .setSession(session)
.setUriInfo(uriInfo) .setUriInfo(uriInfo)
.setRequest(request); .setRequest(request);
CacheControlUtil.noBackButtonCacheControlHeader();
return processor.authenticate(); return processor.authenticate();
} else { } 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) { public static Response redirectToRequiredActions(RealmModel realm, ClientSessionModel clientSession, UriInfo uriInfo) {
// redirect to non-action url so browser refresh button works without reposting past data // redirect to non-action url so browser refresh button works without reposting past data

View file

@ -74,9 +74,10 @@ public abstract class AuthorizationEndpointBase {
* @param clientSession for current request * @param clientSession for current request
* @param protocol handler for protocol used to initiate login * @param protocol handler for protocol used to initiate login
* @param isPassive set to true if login should be passive (without login screen shown) * @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 * @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<IdentityProviderModel> identityProviders = realm.getIdentityProviders(); List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
for (IdentityProviderModel identityProvider : identityProviders) { for (IdentityProviderModel identityProvider : identityProviders) {
@ -115,6 +116,9 @@ public abstract class AuthorizationEndpointBase {
} else { } else {
try { try {
RestartLoginCookie.setRestartCookie(realm, clientConnection, uriInfo, clientSession); RestartLoginCookie.setRestartCookie(realm, clientConnection, uriInfo, clientSession);
if (redirectToAuthentication) {
return processor.redirectToFlow();
}
return processor.authenticate(); return processor.authenticate();
} catch (Exception e) { } catch (Exception e) {
return processor.handleBrowserException(e); return processor.handleBrowserException(e);

View file

@ -28,6 +28,7 @@ import org.keycloak.services.Urls;
import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.LoginActionsService; import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.util.CacheControlUtil;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -87,7 +88,8 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
checkRedirectUri(); checkRedirectUri();
createClientSession(); createClientSession();
// So back button doesn't work
CacheControlUtil.noBackButtonCacheControlHeader();
switch (action) { switch (action) {
case REGISTER: case REGISTER:
return buildRegister(); return buildRegister();
@ -219,7 +221,6 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL); clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
clientSession.setRedirectUri(redirectUri); clientSession.setRedirectUri(redirectUri);
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, responseType); clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, responseType);
clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUriParam); clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUriParam);
clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())); clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
@ -249,7 +250,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
this.event.event(EventType.LOGIN); this.event.event(EventType.LOGIN);
clientSession.setNote(Details.AUTH_TYPE, CODE_AUTH_TYPE); 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() { private Response buildRegister() {

View file

@ -18,6 +18,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.common.util.StreamUtil; 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.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.util.CacheControlUtil;
/** /**
* Resource class for the oauth/openid connect token service * Resource class for the oauth/openid connect token service
@ -66,6 +68,12 @@ public class SamlService extends AuthorizationEndpointBase {
} }
public abstract class BindingProtocol { 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) { protected Response basicChecks(String samlRequest, String samlResponse) {
if (!checkSsl()) { if (!checkSsl()) {
event.event(EventType.LOGIN); event.event(EventType.LOGIN);
@ -229,7 +237,6 @@ public class SamlService extends AuthorizationEndpointBase {
clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL); clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL);
clientSession.setRedirectUri(redirect); clientSession.setRedirectUri(redirect);
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
clientSession.setNote(SamlProtocol.SAML_BINDING, bindingType); clientSession.setNote(SamlProtocol.SAML_BINDING, bindingType);
clientSession.setNote(GeneralConstants.RELAY_STATE, relayState); clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
clientSession.setNote(SamlProtocol.SAML_REQUEST_ID, requestAbstractType.getID()); 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) { 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); 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) { protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive, boolean redirectToAuthentication, SamlProtocol samlProtocol) {
return handleBrowserAuthenticationRequest(clientSession, samlProtocol, isPassive); return handleBrowserAuthenticationRequest(clientSession, samlProtocol, isPassive, redirectToAuthentication);
} }
/** /**
@ -463,21 +470,29 @@ public class SamlService extends AuthorizationEndpointBase {
@GET @GET
public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest, @QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse, @QueryParam(GeneralConstants.RELAY_STATE) String relayState) { 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"); logger.debug("SAML GET");
CacheControlUtil.noBackButtonCacheControlHeader();
return new RedirectBindingProtocol().execute(samlRequest, samlResponse, relayState); return new RedirectBindingProtocol().execute(samlRequest, samlResponse, relayState);
} }
/** /**
*/ */
@POST @POST
@NoCache
@Consumes(MediaType.APPLICATION_FORM_URLENCODED) @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) { 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"); 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 @GET
@Path("descriptor") @Path("descriptor")
@Produces(MediaType.APPLICATION_XML) @Produces(MediaType.APPLICATION_XML)
@NoCache
public String getDescriptor() throws Exception { public String getDescriptor() throws Exception {
return getIDPMetadataDescriptor(uriInfo, realm); return getIDPMetadataDescriptor(uriInfo, realm);
@ -499,6 +514,7 @@ public class SamlService extends AuthorizationEndpointBase {
@Produces(MediaType.TEXT_HTML) @Produces(MediaType.TEXT_HTML)
public Response idpInitiatedSSO(@PathParam("client") String clientUrlName, @QueryParam("RelayState") String relayState) { public Response idpInitiatedSSO(@PathParam("client") String clientUrlName, @QueryParam("RelayState") String relayState) {
event.event(EventType.LOGIN); event.event(EventType.LOGIN);
CacheControlUtil.noBackButtonCacheControlHeader();
ClientModel client = null; ClientModel client = null;
for (ClientModel c : realm.getClients()) { for (ClientModel c : realm.getClients()) {
String urlName = c.getAttribute(SamlProtocol.SAML_IDP_INITIATED_SSO_URL_NAME); 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); ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL); clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL);
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); 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_BINDING, SamlProtocol.SAML_POST_BINDING);
clientSession.setNote(SamlProtocol.SAML_IDP_INITIATED_LOGIN, "true"); clientSession.setNote(SamlProtocol.SAML_IDP_INITIATED_LOGIN, "true");
clientSession.setRedirectUri(redirect); clientSession.setRedirectUri(redirect);
@ -549,11 +564,12 @@ public class SamlService extends AuthorizationEndpointBase {
clientSession.setNote(GeneralConstants.RELAY_STATE, relayState); clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
} }
return newBrowserAuthentication(clientSession, false); return newBrowserAuthentication(clientSession, false, false);
} }
@POST @POST
@NoCache
@Consumes({"application/soap+xml",MediaType.TEXT_XML}) @Consumes({"application/soap+xml",MediaType.TEXT_XML})
public Response soapBinding(InputStream inputStream) { public Response soapBinding(InputStream inputStream) {
SamlEcpProfileService bindingService = new SamlEcpProfileService(realm, event); SamlEcpProfileService bindingService = new SamlEcpProfileService(realm, event);

View file

@ -68,8 +68,8 @@ public class SamlEcpProfileService extends SamlService {
} }
@Override @Override
protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive, SamlProtocol samlProtocol) { protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive, boolean redirectToAuthentication, SamlProtocol samlProtocol) {
return super.newBrowserAuthentication(clientSession, isPassive, createEcpSamlProtocol()); return super.newBrowserAuthentication(clientSession, isPassive, redirectToAuthentication, createEcpSamlProtocol());
} }
private SamlProtocol createEcpSamlProtocol() { private SamlProtocol createEcpSamlProtocol() {

View file

@ -528,7 +528,8 @@ public class AuthenticationManager {
// Skip grant screen if everything was already approved by this user // Skip grant screen if everything was already approved by this user
if (realmRoles.size() > 0 || resourceRoles.size() > 0 || protocolMappers.size() > 0) { 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) return session.getProvider(LoginFormsProvider.class)
.setClientSessionCode(accessCode.getCode()) .setClientSessionCode(accessCode.getCode())

View file

@ -17,6 +17,7 @@
*/ */
package org.keycloak.services.resources; package org.keycloak.services.resources;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
@ -61,6 +62,7 @@ import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ErrorPage; import org.keycloak.services.ErrorPage;
import org.keycloak.services.ServicesLogger; import org.keycloak.services.ServicesLogger;
import org.keycloak.services.Urls; import org.keycloak.services.Urls;
import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.validation.Validation; import org.keycloak.services.validation.Validation;
import org.keycloak.broker.social.SocialIdentityProvider; import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.common.util.ObjectUtil; import org.keycloak.common.util.ObjectUtil;
@ -694,6 +696,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
processor.setClientSession(clientSession) processor.setClientSession(clientSession)
.setFlowPath(LoginActionsService.AUTHENTICATE_PATH) .setFlowPath(LoginActionsService.AUTHENTICATE_PATH)
.setFlowId(flowId) .setFlowId(flowId)
.setBrowserFlow(true)
.setConnection(clientConnection) .setConnection(clientConnection)
.setEventBuilder(event) .setEventBuilder(event)
.setRealm(realmModel) .setRealm(realmModel)
@ -703,6 +706,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
if (errorMessage != null) processor.setForwardedErrorMessage(new FormMessage(null, errorMessage)); if (errorMessage != null) processor.setForwardedErrorMessage(new FormMessage(null, errorMessage));
try { try {
CacheControlUtil.noBackButtonCacheControlHeader();
return processor.authenticate(); return processor.authenticate();
} catch (Exception e) { } catch (Exception e) {
return processor.handleBrowserException(e); return processor.handleBrowserException(e);

View file

@ -67,6 +67,7 @@ import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.util.CookieHelper; import org.keycloak.services.util.CookieHelper;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
@ -155,6 +156,7 @@ public class LoginActionsService {
public LoginActionsService(RealmModel realm, EventBuilder event) { public LoginActionsService(RealmModel realm, EventBuilder event) {
this.realm = realm; this.realm = realm;
this.event = event; this.event = event;
CacheControlUtil.noBackButtonCacheControlHeader();
} }
private boolean checkSsl() { private boolean checkSsl() {
@ -175,32 +177,51 @@ public class LoginActionsService {
if (!verifyCode(code)) { if (!verifyCode(code)) {
return false; return false;
} }
if (!verifyAction(requiredAction, actionType)) { if (!clientCode.isValidAction(requiredAction)) {
return false; if (ClientSessionModel.Action.REQUIRED_ACTIONS.name().equals(clientCode.getClientSession().getAction())) {
} else { response = redirectToRequiredActions(code);
return true; return false;
} else {
invalidAction();
}
} }
} if (isActionActive(actionType)) return false;
return true;
}
public boolean verifyAction(String requiredAction, ClientSessionCode.ActionType actionType) { 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)) { if (!clientCode.isValidAction(requiredAction)) {
event.client(clientCode.getClientSession().getClient()); invalidAction();
event.error(Errors.INVALID_CODE); return true;
response = ErrorPage.error(session, Messages.INVALID_CODE);
return false;
} }
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)) { if (!clientCode.isActionActive(actionType)) {
event.client(clientCode.getClientSession().getClient()); event.client(clientCode.getClientSession().getClient());
event.clone().error(Errors.EXPIRED_CODE); event.clone().error(Errors.EXPIRED_CODE);
if (clientCode.getClientSession().getAction().equals(ClientSessionModel.Action.AUTHENTICATE.name())) { if (clientCode.getClientSession().getAction().equals(ClientSessionModel.Action.AUTHENTICATE.name())) {
AuthenticationProcessor.resetFlow(clientCode.getClientSession()); AuthenticationProcessor.resetFlow(clientCode.getClientSession());
response = processAuthentication(null, clientCode.getClientSession(), Messages.LOGIN_TIMEOUT); response = processAuthentication(null, clientCode.getClientSession(), Messages.LOGIN_TIMEOUT);
return false; return true;
} }
response = ErrorPage.error(session, Messages.EXPIRED_CODE); response = ErrorPage.error(session, Messages.EXPIRED_CODE);
return false; return true;
} }
return true; return false;
} }
public boolean verifyCode(String code) { public boolean verifyCode(String code) {
@ -256,8 +277,48 @@ public class LoginActionsService {
session.getContext().setClient(client); session.getContext().setClient(client);
return true; 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 * protocol independent login page entry point
* *
@ -361,13 +422,11 @@ public class LoginActionsService {
ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID); ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client); ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
//clientSession.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true"); //clientSession.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true");
clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL); clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
String redirectUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName()).toString(); String redirectUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName()).toString();
clientSession.setRedirectUri(redirectUri); clientSession.setRedirectUri(redirectUri);
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE); clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE);
clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUri); clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUri);
clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())); clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
@ -589,19 +648,12 @@ public class LoginActionsService {
@Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processConsent(final MultivaluedMap<String, String> formData) { public Response processConsent(final MultivaluedMap<String, String> formData) {
event.event(EventType.LOGIN); event.event(EventType.LOGIN);
if (!checkSsl()) {
return ErrorPage.error(session, Messages.HTTPS_REQUIRED);
}
String code = formData.getFirst("code"); String code = formData.getFirst("code");
Checks checks = new Checks();
ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm); if (!checks.verifyRequiredAction(code, ClientSessionModel.Action.OAUTH_GRANT.name())) {
if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT.name(), ClientSessionCode.ActionType.LOGIN)) { return checks.response;
event.error(Errors.INVALID_CODE);
return ErrorPage.error(session, Messages.INVALID_ACCESS_CODE);
} }
ClientSessionCode accessCode = checks.clientCode;
ClientSessionModel clientSession = accessCode.getClientSession(); ClientSessionModel clientSession = accessCode.getClientSession();
initEvent(clientSession); initEvent(clientSession);
@ -610,11 +662,6 @@ public class LoginActionsService {
UserModel user = userSession.getUser(); UserModel user = userSession.getUser();
ClientModel client = clientSession.getClient(); 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")) { if (formData.containsKey("cancel")) {
LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod()); LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
@ -810,29 +857,13 @@ public class LoginActionsService {
event.event(EventType.CUSTOM_REQUIRED_ACTION); event.event(EventType.CUSTOM_REQUIRED_ACTION);
event.detail(Details.CUSTOM_REQUIRED_ACTION, action); event.detail(Details.CUSTOM_REQUIRED_ACTION, action);
Checks checks = new Checks(); Checks checks = new Checks();
if (!checks.verifyCode(code, ClientSessionModel.Action.REQUIRED_ACTIONS.name(), ClientSessionCode.ActionType.USER)) { if (!checks.verifyRequiredAction(code, action)) {
return checks.response; return checks.response;
} }
final ClientSessionCode clientCode = checks.clientCode; final ClientSessionCode clientCode = checks.clientCode;
final ClientSessionModel clientSession = clientCode.getClientSession(); final ClientSessionModel clientSession = clientCode.getClientSession();
final UserSessionModel userSession = clientSession.getUserSession(); 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); RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, action);
if (factory == null) { if (factory == null) {

View file

@ -1,5 +1,7 @@
package org.keycloak.services.util; package org.keycloak.services.util;
import org.jboss.resteasy.spi.HttpResponse;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.Config; import org.keycloak.Config;
import javax.ws.rs.core.CacheControl; import javax.ws.rs.core.CacheControl;
@ -9,6 +11,11 @@ import javax.ws.rs.core.CacheControl;
*/ */
public class CacheControlUtil { 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() { public static CacheControl getDefaultCacheControl() {
CacheControl cacheControl = new CacheControl(); CacheControl cacheControl = new CacheControl();
cacheControl.setNoTransform(false); cacheControl.setNoTransform(false);