Skip to content

Commit

Permalink
verify clientsession actions
Browse files Browse the repository at this point in the history
  • Loading branch information
patriot1burke committed Jun 15, 2015
1 parent 2f2a2dc commit 9638c0d
Show file tree
Hide file tree
Showing 21 changed files with 248 additions and 682 deletions.
@@ -1,7 +1,6 @@
package org.keycloak.protocol.saml; package org.keycloak.protocol.saml;


import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse; import org.jboss.resteasy.spi.HttpResponse;
import org.keycloak.ClientConnection; import org.keycloak.ClientConnection;
Expand All @@ -17,7 +16,6 @@
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
Expand All @@ -30,14 +28,11 @@
import org.keycloak.protocol.oidc.utils.RedirectUtils; import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.saml.common.constants.GeneralConstants; import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants; 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.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.services.ErrorPage; import org.keycloak.services.ErrorPage;
import org.keycloak.services.Urls; 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.managers.HttpAuthenticationManager;
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.util.StreamUtil; import org.keycloak.util.StreamUtil;
Expand All @@ -52,13 +47,10 @@
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Providers; import javax.ws.rs.ext.Providers;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.security.PublicKey; import java.security.PublicKey;
Expand Down Expand Up @@ -291,36 +283,6 @@ protected Response loginRequest(String relayState, AuthnRequestType requestAbstr
return newBrowserAuthentication(clientSession); 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<String, String> formData = new MultivaluedMapImpl<String, String>();
formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername);
formData.add("rememberMe", "on");

forms.setFormData(formData);
}

return forms.createLogin();
}

private Response buildRedirectToIdentityProvider(String providerId, String accessCode) { private Response buildRedirectToIdentityProvider(String providerId, String accessCode) {
logger.debug("Automatically redirect to identity provider: " + providerId); logger.debug("Automatically redirect to identity provider: " + providerId);
return Response.temporaryRedirect( return Response.temporaryRedirect(
Expand Down
Expand Up @@ -43,6 +43,12 @@ public class AuthenticationProcessor {
protected HttpRequest request; protected HttpRequest request;
protected String flowId; protected String flowId;
protected String action; 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; protected boolean userSessionCreated;




Expand All @@ -56,6 +62,7 @@ public static enum Status {


} }
public static enum Error { public static enum Error {
INVALID_CLIENT_SESSION,
INVALID_USER, INVALID_USER,
INVALID_CREDENTIALS, INVALID_CREDENTIALS,
CREDENTIAL_SETUP_REQUIRED, CREDENTIAL_SETUP_REQUIRED,
Expand Down Expand Up @@ -144,6 +151,11 @@ public AuthenticationProcessor setAction(String action) {
return this; return this;
} }


public AuthenticationProcessor setForwardedErrorMessage(String forwardedErrorMessage) {
this.forwardedErrorMessage = forwardedErrorMessage;
return this;
}

private class Result implements AuthenticatorContext { private class Result implements AuthenticatorContext {
AuthenticatorModel model; AuthenticatorModel model;
AuthenticationExecutionModel execution; AuthenticationExecutionModel execution;
Expand Down Expand Up @@ -300,6 +312,11 @@ public BruteForceProtector getProtector() {
public EventBuilder getEvent() { public EventBuilder getEvent() {
return AuthenticationProcessor.this.event; return AuthenticationProcessor.this.event;
} }

@Override
public String getForwardedErrorMessage() {
return AuthenticationProcessor.this.forwardedErrorMessage;
}
} }


public static class AuthException extends RuntimeException { public static class AuthException extends RuntimeException {
Expand Down Expand Up @@ -367,7 +384,11 @@ public Response handleBrowserException(Exception failure) {
event.error(Errors.USER_TEMPORARILY_DISABLED); event.error(Errors.USER_TEMPORARILY_DISABLED);
return ErrorPage.error(session, Messages.ACCOUNT_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); event.error(Errors.INVALID_USER_CREDENTIALS);
return ErrorPage.error(session, Messages.INVALID_USER); return ErrorPage.error(session, Messages.INVALID_USER);
} }
Expand All @@ -382,6 +403,9 @@ public Response handleBrowserException(Exception failure) {




public Response authenticate() throws AuthException { public Response authenticate() throws AuthException {
if (!ClientSessionModel.Action.AUTHENTICATE.name().equals(clientSession.getAction())) {
throw new AuthException(Error.INVALID_CLIENT_SESSION);
}
logger.debug("AUTHENTICATE"); logger.debug("AUTHENTICATE");
event.event(EventType.LOGIN); event.event(EventType.LOGIN);
event.client(clientSession.getClient().getClientId()) event.client(clientSession.getClient().getClientId())
Expand All @@ -402,6 +426,9 @@ public Response authenticate() throws AuthException {
} }


public Response authenticateOnly() throws AuthException { 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.event(EventType.LOGIN);
event.client(clientSession.getClient().getClientId()) event.client(clientSession.getClient().getClientId())
.detail(Details.REDIRECT_URI, clientSession.getRedirectUri()) .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
Expand Down Expand Up @@ -585,11 +612,6 @@ protected Response authenticationComplete() {
.detail(Details.USERNAME, username) .detail(Details.USERNAME, username)
.session(userSession); .session(userSession);


return processRequiredActions();

}

public Response processRequiredActions() {
return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, connection, request, uriInfo, event); return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, connection, request, uriInfo, event);


} }
Expand Down
Expand Up @@ -66,4 +66,11 @@ public interface AuthenticatorContext {


void failureChallenge(AuthenticationProcessor.Error error, Response challenge); void failureChallenge(AuthenticationProcessor.Error error, Response challenge);
void attempted(); 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();
} }
Expand Up @@ -11,6 +11,7 @@
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.services.managers.BruteForceProtector; import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.managers.ClientSessionCode;


import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
Expand All @@ -29,4 +30,5 @@ public interface RequiredActionContext {
UriInfo getUriInfo(); UriInfo getUriInfo();
KeycloakSession getSession(); KeycloakSession getSession();
HttpRequest getHttpRequest(); HttpRequest getHttpRequest();
String generateAccessCode(String action);
} }
Expand Up @@ -12,4 +12,5 @@ public interface RequiredActionProvider extends Provider {
void evaluateTriggers(RequiredActionContext context); void evaluateTriggers(RequiredActionContext context);
Response invokeRequiredAction(RequiredActionContext context); Response invokeRequiredAction(RequiredActionContext context);
Object jaxrsService(RequiredActionContext context); Object jaxrsService(RequiredActionContext context);
String getProviderId();
} }
@@ -1,37 +1,25 @@
package org.keycloak.authentication.actions; package org.keycloak.authentication.actions;


import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.Version;
import org.keycloak.authentication.RequiredActionContext; import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionFactory; import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider; import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
import org.keycloak.freemarker.FreeMarkerException; 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.login.LoginFormsProvider;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.LoginProtocol;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;


import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST; 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.MediaType;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;


/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
Expand Down Expand Up @@ -86,6 +74,14 @@ public String getId() {
return PROVIDER_ID; return PROVIDER_ID;
} }



@Override
public String getProviderId() {
return getId();
}



@Override @Override
public void evaluateTriggers(RequiredActionContext context) { public void evaluateTriggers(RequiredActionContext context) {


Expand All @@ -94,7 +90,7 @@ public void evaluateTriggers(RequiredActionContext context) {
@Override @Override
public Response invokeRequiredAction(RequiredActionContext context) { public Response invokeRequiredAction(RequiredActionContext context) {
return context.getSession().getProvider(LoginFormsProvider.class) return context.getSession().getProvider(LoginFormsProvider.class)
.setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode()) .setClientSessionCode(context.generateAccessCode(getProviderId()))
.setUser(context.getUser()) .setUser(context.getUser())
.createForm("terms.ftl", new HashMap<String, Object>()); .createForm("terms.ftl", new HashMap<String, Object>());
} }
Expand Down
Expand Up @@ -51,10 +51,9 @@ public void evaluateTriggers(RequiredActionContext context) {


@Override @Override
public Response invokeRequiredAction(RequiredActionContext context) { public Response invokeRequiredAction(RequiredActionContext context) {
ClientSessionCode accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession()); LoginFormsProvider loginFormsProvider = context.getSession()
accessCode.setAction(ClientSessionModel.Action.UPDATE_PASSWORD.name()); .getProvider(LoginFormsProvider.class)

.setClientSessionCode(context.generateAccessCode(getProviderId()))
LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode())
.setUser(context.getUser()); .setUser(context.getUser());
return loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD); return loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
} }
Expand Down Expand Up @@ -96,4 +95,10 @@ public String getDisplayText() {
public String getId() { public String getId() {
return UserModel.RequiredAction.UPDATE_PASSWORD.name(); return UserModel.RequiredAction.UPDATE_PASSWORD.name();
} }

@Override
public String getProviderId() {
return getId();
}

} }
Expand Up @@ -32,10 +32,8 @@ public void evaluateTriggers(RequiredActionContext context) {


@Override @Override
public Response invokeRequiredAction(RequiredActionContext context) { public Response invokeRequiredAction(RequiredActionContext context) {
ClientSessionCode accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession()); LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
accessCode.setAction(ClientSessionModel.Action.UPDATE_PROFILE.name()); .setClientSessionCode(context.generateAccessCode(getProviderId()))

LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode())
.setUser(context.getUser()); .setUser(context.getUser());
return loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PROFILE); return loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
} }
Expand Down Expand Up @@ -78,4 +76,10 @@ public String getDisplayText() {
public String getId() { public String getId() {
return UserModel.RequiredAction.UPDATE_PROFILE.name(); return UserModel.RequiredAction.UPDATE_PROFILE.name();
} }

@Override
public String getProviderId() {
return getId();
}

} }
Expand Up @@ -40,10 +40,8 @@ public void evaluateTriggers(RequiredActionContext context) {


@Override @Override
public Response invokeRequiredAction(RequiredActionContext context) { public Response invokeRequiredAction(RequiredActionContext context) {
ClientSessionCode accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession()); LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
accessCode.setAction(ClientSessionModel.Action.CONFIGURE_TOTP.name()); .setClientSessionCode(context.generateAccessCode(getProviderId()))

LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode())
.setUser(context.getUser()); .setUser(context.getUser());
return loginFormsProvider.createResponse(UserModel.RequiredAction.CONFIGURE_TOTP); return loginFormsProvider.createResponse(UserModel.RequiredAction.CONFIGURE_TOTP);
} }
Expand Down Expand Up @@ -87,4 +85,10 @@ public String getId() {
return UserModel.RequiredAction.CONFIGURE_TOTP.name(); return UserModel.RequiredAction.CONFIGURE_TOTP.name();
} }


@Override
public String getProviderId() {
return getId();
}


} }
Expand Up @@ -59,12 +59,11 @@ public Response invokeRequiredAction(RequiredActionContext context) {
return null; 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(); 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()); 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()); .setUser(context.getUser());
return loginFormsProvider.createResponse(UserModel.RequiredAction.VERIFY_EMAIL); return loginFormsProvider.createResponse(UserModel.RequiredAction.VERIFY_EMAIL);
} }
Expand Down Expand Up @@ -107,4 +106,10 @@ public String getId() {
return UserModel.RequiredAction.VERIFY_EMAIL.name(); return UserModel.RequiredAction.VERIFY_EMAIL.name();
} }


@Override
public String getProviderId() {
return getId();
}


} }
Expand Up @@ -32,10 +32,14 @@ protected LoginFormsProvider loginForm(AuthenticatorContext context) {
ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession()); ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession());
code.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); code.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
URI action = getActionUrl(context, code, LOGIN_FORM_ACTION); URI action = getActionUrl(context, code, LOGIN_FORM_ACTION);
return context.getSession().getProvider(LoginFormsProvider.class) LoginFormsProvider provider = context.getSession().getProvider(LoginFormsProvider.class)
.setUser(context.getUser()) .setUser(context.getUser())
.setActionUri(action) .setActionUri(action)
.setClientSessionCode(code.getCode()); .setClientSessionCode(code.getCode());
if (context.getForwardedErrorMessage() != null) {
provider.setError(context.getForwardedErrorMessage());
}
return provider;
} }


public static URI getActionUrl(AuthenticatorContext context, ClientSessionCode code, String action) { public static URI getActionUrl(AuthenticatorContext context, ClientSessionCode code, String action) {
Expand Down

0 comments on commit 9638c0d

Please sign in to comment.