diff --git a/docbook/auth-server-docs/reference/en/en-US/master.xml b/docbook/auth-server-docs/reference/en/en-US/master.xml index 16a8601228..60fa1ebf61 100755 --- a/docbook/auth-server-docs/reference/en/en-US/master.xml +++ b/docbook/auth-server-docs/reference/en/en-US/master.xml @@ -20,6 +20,7 @@ + @@ -115,6 +116,7 @@ This one is short &SpringSecurityAdapter; &InstalledApplications; &Logout; + &ErrorHandling; &MultiTenancy; &JAAS; diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml index b0a443dd41..571cd6ca57 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml @@ -83,6 +83,10 @@ Migrating to 1.7.0.CR1 Option 'Update Profile On First Login' moved from Identity provider to Review Profile authenticator + + form-error-page in web.xml will no longer work for client adapter authentication errors. You must define an error-page for + the the various HTTP error codes. See documentation for more details if you want to catch and handle adapter error conditions. + In this version, we added First Broker Login, which allows you to specify what exactly should be done when new user is logged through Identity provider (or Social provider), but there is no existing Keycloak user diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/adapter_error_handling.xml b/docbook/auth-server-docs/reference/en/en-US/modules/adapter_error_handling.xml new file mode 100755 index 0000000000..38c0bb1ec3 --- /dev/null +++ b/docbook/auth-server-docs/reference/en/en-US/modules/adapter_error_handling.xml @@ -0,0 +1,58 @@ +
+ Error Handling + + Keycloak has some error handling facilities for servlet based client adapters. When an error is encountered in + authentication, keycloak will call HttpServletResponse.sendError(). You can set up an error-page + within your web.xml file to handle the error however you want. Keycloak may throw + 400, 401, 403, and 500 errors. + + + + + 404 + /ErrorHandler +]]> + + + + Keycloak also sets an HttpServletRequest attribute that you can retrieve. The attribute name + is org.keycloak.adapters.spi.AuthenticationError. Typecast this object to: + org.keycloak.adapters.OIDCAuthenticationError. This class can tell you exactly what happened. + If this attribute is not set, then the adapter was not responsible for the error code. + + + +public class OIDCAuthenticationError implements AuthenticationError { + public static enum Reason { + NO_BEARER_TOKEN, + NO_REDIRECT_URI, + INVALID_STATE_COOKIE, + OAUTH_ERROR, + SSL_REQUIRED, + CODE_TO_TOKEN_FAILURE, + INVALID_TOKEN, + STALE_TOKEN, + NO_AUTHORIZATION_HEADER + } + + private Reason reason; + private String description; + + public OIDCAuthenticationError(Reason reason, String description) { + this.reason = reason; + this.description = description; + } + + public Reason getReason() { + return reason; + } + + public String getDescription() { + return description; + } +} + + + +
\ No newline at end of file diff --git a/docbook/saml-adapter-docs/reference/en/en-US/master.xml b/docbook/saml-adapter-docs/reference/en/en-US/master.xml index 5b798a1ae1..6e88ed6e58 100755 --- a/docbook/saml-adapter-docs/reference/en/en-US/master.xml +++ b/docbook/saml-adapter-docs/reference/en/en-US/master.xml @@ -9,6 +9,7 @@ + ]> @@ -49,6 +50,7 @@ This one is short &Jetty8Adapter; &FilterAdapter; &Logout; + &ErrorHandling; diff --git a/docbook/saml-adapter-docs/reference/en/en-US/modules/adapter_error_handling.xml b/docbook/saml-adapter-docs/reference/en/en-US/modules/adapter_error_handling.xml new file mode 100755 index 0000000000..1d6d11f080 --- /dev/null +++ b/docbook/saml-adapter-docs/reference/en/en-US/modules/adapter_error_handling.xml @@ -0,0 +1,42 @@ + + Error Handling + + Keycloak has some error handling facilities for servlet based client adapters. When an error is encountered in + authentication, keycloak will call HttpServletResponse.sendError(). You can set up an error-page + within your web.xml file to handle the error however you want. Keycloak may throw + 400, 401, 403, and 500 errors. + + + + + 404 + /ErrorHandler +]]> + + + + Keycloak also sets an HttpServletRequest attribute that you can retrieve. The attribute name + is org.keycloak.adapters.spi.AuthenticationError. Typecast this object to: + org.keycloak.adapters.saml.SamlAuthenticationError. This class can tell you exactly what happened. + If this attribute is not set, then the adapter was not responsible for the error code. + + + +public class SamlAuthenticationError implements AuthenticationError { + public static enum Reason { + EXTRACTION_FAILURE, + INVALID_SIGNATURE, + ERROR_STATUS + } + + public Reason getReason() { + return reason; + } + public StatusResponseType getStatus() { + return status; + } +} + + + \ No newline at end of file diff --git a/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java b/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java old mode 100644 new mode 100755 index 8290b3b268..1143968f81 --- a/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java +++ b/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java @@ -17,10 +17,12 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.keycloak.KeycloakSecurityContext; import org.keycloak.adapters.AdapterDeploymentContext; +import org.keycloak.adapters.spi.AuthenticationError; import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.RefreshableKeycloakSecurityContext; import org.keycloak.adapters.ServerRequest; +import org.keycloak.adapters.spi.LogoutError; import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.RefreshToken; import org.keycloak.util.JsonSerialization; @@ -203,6 +205,18 @@ public class OfflineAccessPortalServlet extends HttpServlet { public String getRemoteAddr() { return servletRequest.getRemoteAddr(); } + + @Override + public void setError(AuthenticationError error) { + servletRequest.setAttribute(AuthenticationError.class.getName(), error); + + } + + @Override + public void setError(LogoutError error) { + servletRequest.setAttribute(LogoutError.class.getName(), error); + } + }; } diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java old mode 100644 new mode 100755 index 6b90105d83..7f260ab8b4 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java @@ -33,7 +33,7 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat public AuthOutcome authenticate(HttpFacade exchange) { List authHeaders = exchange.getRequest().getHeaders("Authorization"); if (authHeaders == null || authHeaders.size() == 0) { - challenge = challengeResponse(exchange, null, null); + challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_AUTHORIZATION_HEADER, null, null); return AuthOutcome.NOT_ATTEMPTED; } @@ -46,7 +46,7 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat } if (tokenString == null) { - challenge = challengeResponse(exchange, null, null); + challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, null, null); return AuthOutcome.NOT_ATTEMPTED; } @@ -59,7 +59,7 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat tokenString = atr.getToken(); } catch (Exception e) { log.debug("Failed to obtain token", e); - challenge = challengeResponse(exchange, "no_token", e.getMessage()); + challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "no_token", e.getMessage()); return AuthOutcome.FAILED; } diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java index 92b03dab54..686b8020a7 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java @@ -45,7 +45,7 @@ public class BearerTokenRequestAuthenticator { public AuthOutcome authenticate(HttpFacade exchange) { List authHeaders = exchange.getRequest().getHeaders("Authorization"); if (authHeaders == null || authHeaders.size() == 0) { - challenge = challengeResponse(exchange, null, null); + challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, null, null); return AuthOutcome.NOT_ATTEMPTED; } @@ -58,7 +58,7 @@ public class BearerTokenRequestAuthenticator { } if (tokenString == null) { - challenge = challengeResponse(exchange, null, null); + challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, null, null); return AuthOutcome.NOT_ATTEMPTED; } @@ -70,12 +70,12 @@ public class BearerTokenRequestAuthenticator { token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl()); } catch (VerificationException e) { log.error("Failed to verify token", e); - challenge = challengeResponse(exchange, "invalid_token", e.getMessage()); + challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage()); return AuthOutcome.FAILED; } if (token.getIssuedAt() < deployment.getNotBefore()) { log.error("Stale token"); - challenge = challengeResponse(exchange, "invalid_token", "Stale token"); + challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.STALE_TOKEN, "invalid_token", "Stale token"); return AuthOutcome.FAILED; } boolean verifyCaller = false; @@ -112,11 +112,6 @@ public class BearerTokenRequestAuthenticator { protected AuthChallenge clientCertChallenge() { return new AuthChallenge() { - @Override - public boolean errorPage() { - return false; - } - @Override public int getResponseCode() { return 0; @@ -131,7 +126,7 @@ public class BearerTokenRequestAuthenticator { } - protected AuthChallenge challengeResponse(HttpFacade facade, String error, String description) { + protected AuthChallenge challengeResponse(HttpFacade facade, final OIDCAuthenticationError.Reason reason, final String error, final String description) { StringBuilder header = new StringBuilder("Bearer realm=\""); header.append(deployment.getRealm()).append("\""); if (error != null) { @@ -142,11 +137,6 @@ public class BearerTokenRequestAuthenticator { } final String challenge = header.toString(); return new AuthChallenge() { - @Override - public boolean errorPage() { - return true; - } - @Override public int getResponseCode() { return 401; @@ -154,8 +144,10 @@ public class BearerTokenRequestAuthenticator { @Override public boolean challenge(HttpFacade facade) { - facade.getResponse().setStatus(401); + OIDCAuthenticationError error = new OIDCAuthenticationError(reason, description); + facade.getRequest().setError(error); facade.getResponse().addHeader("WWW-Authenticate", challenge); + facade.getResponse().sendError(401); return true; } }; diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java index d34100311f..f41f80f202 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java @@ -174,15 +174,10 @@ public class OAuthRequestAuthenticator { final String state = getStateCode(); final String redirect = getRedirectUri(state); if (redirect == null) { - return challenge(403); + return challenge(403, OIDCAuthenticationError.Reason.NO_REDIRECT_URI, null); } return new AuthChallenge() { - @Override - public boolean errorPage() { - return false; - } - @Override public int getResponseCode() { return 0; @@ -205,7 +200,7 @@ public class OAuthRequestAuthenticator { if (stateCookie == null) { log.warn("No state cookie"); - return challenge(400); + return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null); } // reset the cookie log.debug("** reseting application state cookie"); @@ -215,13 +210,13 @@ public class OAuthRequestAuthenticator { String state = getQueryParamValue(OAuth2Constants.STATE); if (state == null) { log.warn("state parameter was null"); - return challenge(400); + return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null); } if (!state.equals(stateCookieValue)) { log.warn("state parameter invalid"); log.warn("cookie: " + stateCookieValue); log.warn("queryParam: " + state); - return challenge(400); + return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null); } return null; @@ -235,7 +230,7 @@ public class OAuthRequestAuthenticator { if (error != null) { // todo how do we send a response? log.warn("There was an error: " + error); - challenge = challenge(400); + challenge = challenge(400, OIDCAuthenticationError.Reason.OAUTH_ERROR, error); return AuthOutcome.FAILED; } else { log.debug("redirecting to auth server"); @@ -253,13 +248,8 @@ public class OAuthRequestAuthenticator { } - protected AuthChallenge challenge(final int code) { + protected AuthChallenge challenge(final int code, final OIDCAuthenticationError.Reason reason, final String description) { return new AuthChallenge() { - @Override - public boolean errorPage() { - return true; - } - @Override public int getResponseCode() { return code; @@ -267,6 +257,8 @@ public class OAuthRequestAuthenticator { @Override public boolean challenge(HttpFacade exchange) { + OIDCAuthenticationError error = new OIDCAuthenticationError(reason, description); + exchange.getRequest().setError(error); exchange.getResponse().sendError(code); return true; } @@ -289,7 +281,7 @@ public class OAuthRequestAuthenticator { // abort if not HTTPS if (!isRequestSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) { log.error("Adapter requires SSL. Request: " + facade.getRequest().getURI()); - return challenge(403); + return challenge(403, OIDCAuthenticationError.Reason.SSL_REQUIRED, null); } log.debug("checking state cookie for after code"); @@ -308,11 +300,11 @@ public class OAuthRequestAuthenticator { if (failure.getStatus() == 400 && failure.getError() != null) { log.error(" " + failure.getError()); } - return challenge(403); + return challenge(403, OIDCAuthenticationError.Reason.CODE_TO_TOKEN_FAILURE, null); } catch (IOException e) { log.error("failed to turn code into token", e); - return challenge(403); + return challenge(403, OIDCAuthenticationError.Reason.CODE_TO_TOKEN_FAILURE, null); } tokenString = tokenResponse.getToken(); @@ -331,14 +323,14 @@ public class OAuthRequestAuthenticator { log.debug("Token Verification succeeded!"); } catch (VerificationException e) { log.error("failed verification of token: " + e.getMessage()); - return challenge(403); + return challenge(403, OIDCAuthenticationError.Reason.INVALID_TOKEN, null); } if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) { deployment.setNotBefore(tokenResponse.getNotBeforePolicy()); } if (token.getIssuedAt() < deployment.getNotBefore()) { log.error("Stale token"); - return challenge(403); + return challenge(403, OIDCAuthenticationError.Reason.STALE_TOKEN, null); } log.debug("successful authenticated"); return null; diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/OIDCAuthenticationError.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/OIDCAuthenticationError.java new file mode 100755 index 0000000000..5b3f45d67e --- /dev/null +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/OIDCAuthenticationError.java @@ -0,0 +1,39 @@ +package org.keycloak.adapters; + +import org.keycloak.adapters.spi.AuthenticationError; + +/** + * Object that describes the OIDC error that happened. + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class OIDCAuthenticationError implements AuthenticationError { + public static enum Reason { + NO_BEARER_TOKEN, + NO_REDIRECT_URI, + INVALID_STATE_COOKIE, + OAUTH_ERROR, + SSL_REQUIRED, + CODE_TO_TOKEN_FAILURE, + INVALID_TOKEN, + STALE_TOKEN, + NO_AUTHORIZATION_HEADER + } + + private Reason reason; + private String description; + + public OIDCAuthenticationError(Reason reason, String description) { + this.reason = reason; + this.description = description; + } + + public Reason getReason() { + return reason; + } + + public String getDescription() { + return description; + } +} diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthChallenge.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthChallenge.java index ff0960f5e8..8bb2f71b4a 100755 --- a/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthChallenge.java +++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthChallenge.java @@ -13,15 +13,7 @@ public interface AuthChallenge { boolean challenge(HttpFacade exchange); /** - * Whether or not an error page should be displayed if possible along with the challenge - * - * @return - */ - boolean errorPage(); - - /** - * If errorPage is true, this is the response code the challenge will send. This is used by platforms - * that call HttpServletResponse.sendError() to forward to error page. + * Some platforms need the error code that will be sent (i.e. Undertow) * * @return */ diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthenticationError.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthenticationError.java new file mode 100755 index 0000000000..238a7d8f0a --- /dev/null +++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthenticationError.java @@ -0,0 +1,13 @@ +package org.keycloak.adapters.spi; + +/** + * Common marker interface used by keycloak client adapters when there is an error. For servlets, you'll be able + * to extract this error from the HttpServletRequest.getAttribute(AuthenticationError.class.getName()). Each protocol + * will have their own subclass of this interface. + * + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface AuthenticationError { +} diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/HttpFacade.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/HttpFacade.java index cf6e0d5a4d..ead316a0df 100755 --- a/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/HttpFacade.java +++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/HttpFacade.java @@ -47,6 +47,8 @@ public interface HttpFacade { InputStream getInputStream(); String getRemoteAddr(); + void setError(AuthenticationError error); + void setError(LogoutError error); } interface Response { diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/LogoutError.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/LogoutError.java new file mode 100755 index 0000000000..2d846ec822 --- /dev/null +++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/LogoutError.java @@ -0,0 +1,12 @@ +package org.keycloak.adapters.spi; + +/** + * Common marker interface used by keycloak client adapters when there is an error. For servlets, you'll be able + * to extract this error from the HttpServletRequest.getAttribute(LogoutError.class.getName()). Each protocol + * will have their own subclass of this interface. + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface LogoutError { +} diff --git a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java index 29f483b43c..4af3b90474 100755 --- a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java +++ b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java @@ -12,6 +12,8 @@ import javax.ws.rs.core.SecurityContext; import org.keycloak.KeycloakSecurityContext; import org.keycloak.adapters.OIDCHttpFacade; +import org.keycloak.adapters.spi.AuthenticationError; +import org.keycloak.adapters.spi.LogoutError; import org.keycloak.common.util.HostUtils; /** @@ -93,6 +95,17 @@ public class JaxrsHttpFacade implements OIDCHttpFacade { // TODO: implement properly return HostUtils.getIpAddress(); } + + @Override + public void setError(AuthenticationError error) { + requestContext.setProperty(AuthenticationError.class.getName(), error); + } + + @Override + public void setError(LogoutError error) { + requestContext.setProperty(LogoutError.class.getName(), error); + + } } protected class ResponseFacade implements OIDCHttpFacade.Response { diff --git a/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyHttpFacade.java b/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyHttpFacade.java index c1008fd3f3..42ff20103d 100755 --- a/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyHttpFacade.java +++ b/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyHttpFacade.java @@ -1,6 +1,8 @@ package org.keycloak.adapters.jetty.spi; +import org.keycloak.adapters.spi.AuthenticationError; import org.keycloak.adapters.spi.HttpFacade; +import org.keycloak.adapters.spi.LogoutError; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.UriUtils; @@ -125,6 +127,18 @@ public class JettyHttpFacade implements HttpFacade { public String getRemoteAddr() { return request.getRemoteAddr(); } + + @Override + public void setError(AuthenticationError error) { + request.setAttribute(AuthenticationError.class.getName(), error); + + } + + @Override + public void setError(LogoutError error) { + request.setAttribute(LogoutError.class.getName(), error); + } + } protected class ResponseFacade implements Response { diff --git a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java index d3790afa0c..dad7570414 100755 --- a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java +++ b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java @@ -265,15 +265,6 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica AuthChallenge challenge = authenticator.getChallenge(); if (challenge != null) { challenge.challenge(facade); - if (challenge.errorPage() && errorPage != null) { - Response response = (Response)res; - try { - response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), errorPage))); - } catch (IOException e) { - throw new RuntimeException(e); - } - - } } return Authentication.SEND_CONTINUE; } diff --git a/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java b/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java index eb487f5f9b..6d32bdafa9 100755 --- a/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java +++ b/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java @@ -1,6 +1,8 @@ package org.keycloak.adapters.servlet; +import org.keycloak.adapters.spi.AuthenticationError; import org.keycloak.adapters.spi.HttpFacade; +import org.keycloak.adapters.spi.LogoutError; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.ServerCookie; import org.keycloak.common.util.UriUtils; @@ -111,6 +113,18 @@ public class ServletHttpFacade implements HttpFacade { public String getRemoteAddr() { return request.getRemoteAddr(); } + + + @Override + public void setError(AuthenticationError error) { + request.setAttribute(AuthenticationError.class.getName(), error); + + } + + @Override + public void setError(LogoutError error) { + request.setAttribute(LogoutError.class.getName(), error); + } } public boolean isEnded() { return responseFacade.isEnded(); diff --git a/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/KeycloakOIDCFilter.java b/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/KeycloakOIDCFilter.java index 57bd93d608..3cfc871f4d 100755 --- a/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/KeycloakOIDCFilter.java +++ b/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/KeycloakOIDCFilter.java @@ -142,11 +142,6 @@ public class KeycloakOIDCFilter implements Filter { if (challenge != null) { log.fine("challenge"); challenge.challenge(facade); - if (challenge.errorPage()) { - response.sendError(challenge.getResponseCode()); - return; - } - log.fine("sending challenge"); return; } response.sendError(403); diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java index c3f1dfd3d5..9c54ab1051 100755 --- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java +++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java @@ -6,6 +6,8 @@ import org.keycloak.adapters.AdapterDeploymentContext; import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.OIDCHttpFacade; import org.keycloak.adapters.ServerRequest; +import org.keycloak.adapters.spi.AuthenticationError; +import org.keycloak.adapters.spi.LogoutError; import org.keycloak.jose.jws.JWSInput; import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.IDToken; @@ -237,6 +239,18 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient { public String getRemoteAddr() { return servletRequest.getRemoteAddr(); } + + @Override + public void setError(AuthenticationError error) { + servletRequest.setAttribute(AuthenticationError.class.getName(), error); + + } + + @Override + public void setError(LogoutError error) { + servletRequest.setAttribute(LogoutError.class.getName(), error); + } + }; } diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java index 79074da098..ba9fa1aa4d 100755 --- a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java +++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java @@ -1,7 +1,9 @@ package org.keycloak.adapters.springsecurity.facade; +import org.keycloak.adapters.spi.AuthenticationError; import org.keycloak.adapters.spi.HttpFacade.Cookie; import org.keycloak.adapters.spi.HttpFacade.Request; +import org.keycloak.adapters.spi.LogoutError; import org.springframework.util.Assert; import javax.servlet.http.HttpServletRequest; @@ -109,4 +111,17 @@ class WrappedHttpServletRequest implements Request { public String getRemoteAddr() { return request.getRemoteAddr(); } + + @Override + public void setError(AuthenticationError error) { + request.setAttribute(AuthenticationError.class.getName(), error); + + } + + @Override + public void setError(LogoutError error) { + request.setAttribute(LogoutError.class.getName(), error); + } + + } diff --git a/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java b/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java index ba0b3764d7..36387598d1 100755 --- a/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java +++ b/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java @@ -1,6 +1,8 @@ package org.keycloak.adapters.tomcat; +import org.keycloak.adapters.spi.AuthenticationError; import org.keycloak.adapters.spi.HttpFacade; +import org.keycloak.adapters.spi.LogoutError; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.ServerCookie; import org.keycloak.common.util.UriUtils; @@ -125,6 +127,18 @@ public class CatalinaHttpFacade implements HttpFacade { public String getRemoteAddr() { return request.getRemoteAddr(); } + + @Override + public void setError(AuthenticationError error) { + request.setAttribute(AuthenticationError.class.getName(), error); + + } + + @Override + public void setError(LogoutError error) { + request.setAttribute(LogoutError.class.getName(), error); + } + } protected class ResponseFacade implements Response { diff --git a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java index 89bee43e3e..20c87bd4dd 100755 --- a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java +++ b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java @@ -195,12 +195,6 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat } AuthChallenge challenge = authenticator.getChallenge(); if (challenge != null) { - if (loginConfig == null) { - loginConfig = request.getContext().getLoginConfig(); - } - if (challenge.errorPage()) { - if (forwardToErrorPageInternal(request, response, loginConfig))return false; - } challenge.challenge(facade); } return false; diff --git a/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java index 0b10188c44..815b1ce52e 100755 --- a/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java +++ b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java @@ -2,9 +2,13 @@ package org.keycloak.adapters.undertow; import io.undertow.server.HttpServerExchange; import io.undertow.servlet.handlers.ServletRequestContext; +import org.keycloak.adapters.spi.AuthenticationError; +import org.keycloak.adapters.spi.HttpFacade; +import org.keycloak.adapters.spi.LogoutError; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; /** * @author Bill Burke @@ -18,6 +22,7 @@ public class ServletHttpFacade extends UndertowHttpFacade { super(exchange); final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); request = (HttpServletRequest)servletRequestContext.getServletRequest(); + response = (HttpServletResponse)servletRequestContext.getServletResponse(); } protected class RequestFacade extends UndertowHttpFacade.RequestFacade { @@ -26,6 +31,47 @@ public class ServletHttpFacade extends UndertowHttpFacade { return request.getParameter(param); } + @Override + public void setError(AuthenticationError error) { + request.setAttribute(AuthenticationError.class.getName(), error); + + } + + @Override + public void setError(LogoutError error) { + request.setAttribute(LogoutError.class.getName(), error); + } + + + } + + protected class ResponseFacade extends UndertowHttpFacade.ResponseFacade { + // can't call sendError from a challenge. Undertow ends up calling send error. + /* + @Override + public void sendError(int code) { + try { + response.sendError(code); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void sendError(int code, String message) { + try { + response.sendError(code, message); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + */ + + } + + @Override + public Response getResponse() { + return new ResponseFacade(); } @Override diff --git a/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java index f420533449..d38fe554a4 100755 --- a/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java +++ b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java @@ -2,9 +2,12 @@ package org.keycloak.adapters.undertow; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.CookieImpl; +import io.undertow.util.AttachmentKey; import io.undertow.util.Headers; import io.undertow.util.HttpString; +import org.keycloak.adapters.spi.AuthenticationError; import org.keycloak.adapters.spi.HttpFacade; +import org.keycloak.adapters.spi.LogoutError; import org.keycloak.common.util.KeycloakUriBuilder; import javax.security.cert.X509Certificate; @@ -22,6 +25,9 @@ import java.util.Map; * @version $Revision: 1 $ */ public class UndertowHttpFacade implements HttpFacade { + public static final AttachmentKey AUTH_ERROR_ATTACHMENT_KEY = AttachmentKey.create(AuthenticationError.class); + public static final AttachmentKey LOGOUT_ERROR_ATTACHMENT_KEY = AttachmentKey.create(LogoutError.class); + protected HttpServerExchange exchange; protected RequestFacade requestFacade = new RequestFacade(); protected ResponseFacade responseFacade = new ResponseFacade(); @@ -127,6 +133,17 @@ public class UndertowHttpFacade implements HttpFacade { } return address.getHostAddress(); } + + @Override + public void setError(AuthenticationError error) { + exchange.putAttachment(AUTH_ERROR_ATTACHMENT_KEY, error); + } + + @Override + public void setError(LogoutError error) { + exchange.putAttachment(LOGOUT_ERROR_ATTACHMENT_KEY, error); + + } } protected class ResponseFacade implements Response { @@ -173,7 +190,6 @@ public class UndertowHttpFacade implements HttpFacade { @Override public void sendError(int code) { exchange.setResponseCode(code); - exchange.endExchange(); } @Override diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java index aded0efc1b..36e8aa42f8 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java @@ -56,11 +56,7 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) { AuthChallenge challenge = exchange.getAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY); if (challenge != null) { - if (challenge.errorPage() && errorPage != null) { - Integer code = servePage(exchange, errorPage); - return new ChallengeResult(true, code); - } - UndertowHttpFacade facade = new UndertowHttpFacade(exchange); + UndertowHttpFacade facade = createFacade(exchange); if (challenge.challenge(facade)) { return new ChallengeResult(true, exchange.getResponseCode()); } @@ -68,6 +64,10 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication return new ChallengeResult(false); } + public UndertowHttpFacade createFacade(HttpServerExchange exchange) { + return new OIDCUndertowHttpFacade(exchange); + } + protected Integer servePage(final HttpServerExchange exchange, final String location) { sendRedirect(exchange, location); return StatusCodes.TEMPORARY_REDIRECT; @@ -89,7 +89,7 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication if (notification.getEventType() != SecurityNotification.EventType.LOGGED_OUT) return; HttpServerExchange exchange = notification.getExchange(); - UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange); + UndertowHttpFacade facade = createFacade(exchange); KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade); KeycloakSecurityContext ksc = exchange.getAttachment(OIDCUndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY); if (ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) { diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCServletUndertowHttpFacade.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCServletUndertowHttpFacade.java new file mode 100755 index 0000000000..ddf0f414e7 --- /dev/null +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCServletUndertowHttpFacade.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.keycloak.adapters.undertow; + +import io.undertow.server.HttpServerExchange; +import io.undertow.util.AttachmentKey; +import org.keycloak.KeycloakSecurityContext; +import org.keycloak.adapters.OIDCHttpFacade; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class OIDCServletUndertowHttpFacade extends ServletHttpFacade implements OIDCHttpFacade { + public static final AttachmentKey KEYCLOAK_SECURITY_CONTEXT_KEY = AttachmentKey.create(KeycloakSecurityContext.class); + + public OIDCServletUndertowHttpFacade(HttpServerExchange exchange) { + super(exchange); + } + + @Override + public KeycloakSecurityContext getSecurityContext() { + return exchange.getAttachment(KEYCLOAK_SECURITY_CONTEXT_KEY); + } + +} diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java index b5984a491f..b546e76e50 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java @@ -79,7 +79,7 @@ public class ServletKeycloakAuthMech extends AbstractUndertowKeycloakAuthMech { @Override public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) { - UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange); + UndertowHttpFacade facade = createFacade(exchange); KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade); if (!deployment.isConfigured()) { return AuthenticationMechanismOutcome.NOT_ATTEMPTED; @@ -119,4 +119,8 @@ public class ServletKeycloakAuthMech extends AbstractUndertowKeycloakAuthMech { } } + @Override + public UndertowHttpFacade createFacade(HttpServerExchange exchange) { + return new OIDCServletUndertowHttpFacade(exchange); + } } diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java index 362cdb06e2..05672db630 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java @@ -61,11 +61,13 @@ public class ServletPreAuthActionsHandler implements HttpHandler { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { - UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange); + UndertowHttpFacade facade = new OIDCServletUndertowHttpFacade(exchange); final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, servletRequestContext.getDeployment().getSessionManager()); PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deploymentContext, facade); if (handler.handleRequest()) return; next.handleRequest(exchange); } + + } diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java index 85b75813e1..88ba70545f 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java @@ -25,7 +25,7 @@ public class UndertowAuthenticationMechanism extends AbstractUndertowKeycloakAut @Override public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) { - UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange); + UndertowHttpFacade facade = createFacade(exchange); KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade); if (!deployment.isConfigured()) { return AuthenticationMechanismOutcome.NOT_ATTEMPTED; diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java index 77194e1dc0..8ad97efd75 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java @@ -47,10 +47,14 @@ public class UndertowPreAuthActionsHandler implements HttpHandler { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { - UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange); + UndertowHttpFacade facade = createFacade(exchange); SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, sessionManager); PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deploymentContext, facade); if (handler.handleRequest()) return; next.handleRequest(exchange); } + + public UndertowHttpFacade createFacade(HttpServerExchange exchange) { + return new OIDCUndertowHttpFacade(exchange); + } } diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java index a34fb1b847..158bae89b9 100755 --- a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java +++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java @@ -26,11 +26,6 @@ public class InitiateLogin implements AuthChallenge { this.sessionStore = sessionStore; } - @Override - public boolean errorPage() { - return false; - } - @Override public int getResponseCode() { return 0; @@ -87,6 +82,7 @@ public class InitiateLogin implements AuthChallenge { Document document = authnRequestBuilder.toDocument(); SamlDeployment.Binding samlBinding = deployment.getIDP().getSingleSignOnService().getRequestBinding(); SamlUtil.sendSaml(true, httpFacade, actionUrl, binding, document, samlBinding); + sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.LOGGING_IN); } catch (Exception e) { throw new RuntimeException("Could not create authentication request.", e); } diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticationError.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticationError.java new file mode 100755 index 0000000000..8b631031cb --- /dev/null +++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticationError.java @@ -0,0 +1,43 @@ +package org.keycloak.adapters.saml; + +import org.keycloak.adapters.spi.AuthenticationError; +import org.keycloak.dom.saml.v2.protocol.StatusResponseType; +import org.keycloak.dom.saml.v2.protocol.StatusType; + +/** + * Object that describes the SAML error that happened. + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class SamlAuthenticationError implements AuthenticationError { + public static enum Reason { + EXTRACTION_FAILURE, + INVALID_SIGNATURE, + ERROR_STATUS + } + + private Reason reason; + + private StatusResponseType status; + + public SamlAuthenticationError(Reason reason) { + this.reason = reason; + } + + public SamlAuthenticationError(Reason reason, StatusResponseType status) { + this.reason = reason; + this.status = status; + } + + public SamlAuthenticationError(StatusResponseType statusType) { + this.status = statusType; + } + + public Reason getReason() { + return reason; + } + public StatusResponseType getStatus() { + return status; + } +} diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java index 39f026ac2b..1d289d764e 100755 --- a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java +++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java @@ -1,10 +1,12 @@ package org.keycloak.adapters.saml; import org.jboss.logging.Logger; -import org.keycloak.common.VerificationException; import org.keycloak.adapters.spi.AuthChallenge; import org.keycloak.adapters.spi.AuthOutcome; import org.keycloak.adapters.spi.HttpFacade; +import org.keycloak.common.VerificationException; +import org.keycloak.common.util.KeycloakUriBuilder; +import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.dom.saml.v2.assertion.AssertionType; import org.keycloak.dom.saml.v2.assertion.AttributeStatementType; import org.keycloak.dom.saml.v2.assertion.AttributeType; @@ -29,8 +31,6 @@ import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature; import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder; import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil; import org.keycloak.saml.processing.web.util.PostBindingUtil; -import org.keycloak.common.util.KeycloakUriBuilder; -import org.keycloak.common.util.MultivaluedHashMap; import org.w3c.dom.Document; import org.w3c.dom.Node; @@ -74,7 +74,7 @@ public abstract class SamlAuthenticator { return handleSamlRequest(samlRequest, relayState); } else if (samlResponse != null) { return handleSamlResponse(samlResponse, relayState); - } else if (sessionStore.isLoggedIn()) { + } else if (sessionStore.isLoggedIn()) { if (globalLogout) { return globalLogout(); } @@ -106,6 +106,7 @@ public abstract class SamlAuthenticator { try { SamlUtil.sendSaml(true, facade, deployment.getIDP().getSingleLogoutService().getRequestBindingUrl(), binding, logoutBuilder.buildDocument(), deployment.getIDP().getSingleLogoutService().getRequestBinding()); + sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.LOGGING_OUT); } catch (Exception e) { log.error("Could not send global logout SAML request", e); return AuthOutcome.FAILED; @@ -155,7 +156,7 @@ public abstract class SamlAuthenticator { protected AuthOutcome logoutRequest(LogoutRequestType request, String relayState) { if (request.getSessionIndex() == null || request.getSessionIndex().isEmpty()) { sessionStore.logoutByPrincipal(request.getNameID().getValue()); - } else { + } else { sessionStore.logoutBySsoId(request.getSessionIndex()); } @@ -169,7 +170,8 @@ public abstract class SamlAuthenticator { binding.signatureAlgorithm(deployment.getSignatureAlgorithm()) .signWith(deployment.getSigningKeyPair()) .signDocument(); - if (deployment.getSignatureCanonicalizationMethod() != null) binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod()); + if (deployment.getSignatureCanonicalizationMethod() != null) + binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod()); } @@ -199,34 +201,80 @@ public abstract class SamlAuthenticator { postBinding = true; holder = extractPostBindingResponse(samlResponse); } - StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject(); + final StatusResponseType statusResponse = (StatusResponseType) holder.getSamlObject(); // validate destination if (!requestUri.equals(statusResponse.getDestination())) { log.error("Request URI does not match SAML request destination"); return AuthOutcome.FAILED; } if (statusResponse instanceof ResponseType) { - if (deployment.getIDP().getSingleSignOnService().validateResponseSignature()) { - try { - validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY); - } catch (VerificationException e) { - log.error("Failed to verify saml response signature", e); - return AuthOutcome.FAILED; + try { + if (deployment.getIDP().getSingleSignOnService().validateResponseSignature()) { + try { + validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY); + } catch (VerificationException e) { + log.error("Failed to verify saml response signature", e); + + challenge = new AuthChallenge() { + @Override + public boolean challenge(HttpFacade exchange) { + SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE); + exchange.getRequest().setError(error); + exchange.getResponse().sendError(403); + return true; + } + + @Override + public int getResponseCode() { + return 403; + } + }; + return AuthOutcome.FAILED; + } } + return handleLoginResponse((ResponseType) statusResponse); + } finally { + sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE); } - return handleLoginResponse((ResponseType)statusResponse); } else { - if (deployment.getIDP().getSingleLogoutService().validateResponseSignature()) { + if (sessionStore.isLoggingOut()) { try { - validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY); - } catch (VerificationException e) { - log.error("Failed to verify saml response signature", e); + if (deployment.getIDP().getSingleLogoutService().validateResponseSignature()) { + try { + validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY); + } catch (VerificationException e) { + log.error("Failed to verify saml response signature", e); + return AuthOutcome.FAILED; + } + } + return handleLogoutResponse(holder, statusResponse, relayState); + } finally { + sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE); + } + + } else if (sessionStore.isLoggingIn()) { + try { + challenge = new AuthChallenge() { + @Override + public boolean challenge(HttpFacade exchange) { + SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.ERROR_STATUS, statusResponse); + exchange.getRequest().setError(error); + exchange.getResponse().sendError(403); + return true; + } + + @Override + public int getResponseCode() { + return 403; + } + }; return AuthOutcome.FAILED; + } finally { + sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE); } } - // todo need to check that it is actually a LogoutResponse - return handleLogoutResponse(holder, statusResponse, relayState); + return AuthOutcome.NOT_ATTEMPTED; } } @@ -239,7 +287,7 @@ public abstract class SamlAuthenticator { } } - protected AuthOutcome handleLoginResponse(ResponseType responseType) { + protected AuthOutcome handleLoginResponse(ResponseType responseType) { AssertionType assertion = null; try { assertion = AssertionUtil.getAssertion(responseType, deployment.getDecryptionKey()); @@ -248,7 +296,20 @@ public abstract class SamlAuthenticator { } } catch (Exception e) { log.error("Error extracting SAML assertion, e"); - return AuthOutcome.FAILED; + challenge = new AuthChallenge() { + @Override + public boolean challenge(HttpFacade exchange) { + SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.EXTRACTION_FAILURE); + exchange.getRequest().setError(error); + exchange.getResponse().sendError(403); + return true; + } + + @Override + public int getResponseCode() { + return 403; + } + }; } SubjectType subject = assertion.getSubject(); @@ -308,14 +369,14 @@ public abstract class SamlAuthenticator { AuthnStatementType authn = null; for (Object statement : assertion.getStatements()) { if (statement instanceof AuthnStatementType) { - authn = (AuthnStatementType)statement; + authn = (AuthnStatementType) statement; break; } } URI nameFormat = subjectNameID.getFormat(); - String nameFormatString = nameFormat == null ? JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get() : nameFormat.toString(); + String nameFormatString = nameFormat == null ? JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get() : nameFormat.toString(); final SamlPrincipal principal = new SamlPrincipal(principalName, principalName, nameFormatString, attributes, friendlyAttributes); String index = authn == null ? null : authn.getSessionIndex(); final String sessionIndex = index; @@ -341,9 +402,9 @@ public abstract class SamlAuthenticator { protected abstract void completeAuthentication(SamlSession account); private String getAttributeValue(Object attrValue) { - String value = null; + String value = null; if (attrValue instanceof String) { - value = (String)attrValue; + value = (String) attrValue; } else if (attrValue instanceof Node) { Node roleNode = (Node) attrValue; value = roleNode.getFirstChild().getNodeValue(); @@ -372,6 +433,7 @@ public abstract class SamlAuthenticator { protected SAMLDocumentHolder extractRedirectBindingResponse(String response) { return SAMLRequestParser.parseRequestRedirectBinding(response); } + protected SAMLDocumentHolder extractPostBindingResponse(String response) { byte[] samlBytes = PostBindingUtil.base64Decode(response); String xml = new String(samlBytes); @@ -379,7 +441,6 @@ public abstract class SamlAuthenticator { } - protected AuthOutcome initiateLogin() { challenge = new InitiateLogin(deployment, sessionStore); return AuthOutcome.NOT_ATTEMPTED; @@ -445,5 +506,4 @@ public abstract class SamlAuthenticator { } - } diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSessionStore.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSessionStore.java index da200264ac..1a19464a99 100755 --- a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSessionStore.java +++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSessionStore.java @@ -1,6 +1,8 @@ package org.keycloak.adapters.saml; import org.keycloak.adapters.spi.AdapterSessionStore; +import org.keycloak.dom.saml.v2.protocol.StatusResponseType; +import org.keycloak.dom.saml.v2.protocol.StatusType; import java.util.List; @@ -9,6 +11,19 @@ import java.util.List; * @version $Revision: 1 $ */ public interface SamlSessionStore extends AdapterSessionStore { + public static final String CURRENT_ACTION = "SAML_CURRENT_ACTION"; + public static final String SAML_LOGIN_ERROR_STATUS = "SAML_LOGIN_ERROR_STATUS"; + public static final String SAML_LOGOUT_ERROR_STATUS = "SAML_LOGOUT_ERROR_STATUS"; + + enum CurrentAction { + NONE, + LOGGING_IN, + LOGGING_OUT + } + void setCurrentAction(CurrentAction action); + boolean isLoggingIn(); + boolean isLoggingOut(); + boolean isLoggedIn(); SamlSession getAccount(); void saveAccount(SamlSession account); diff --git a/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java b/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java index 60cea893ee..2df0fad988 100755 --- a/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java +++ b/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java @@ -260,15 +260,6 @@ public abstract class AbstractSamlAuthenticator extends LoginAuthenticator { AuthChallenge challenge = authenticator.getChallenge(); if (challenge != null) { challenge.challenge(facade); - if (challenge.errorPage() && errorPage != null) { - Response response = (Response)res; - try { - response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), errorPage))); - } catch (IOException e) { - throw new RuntimeException(e); - } - - } } return Authentication.SEND_CONTINUE; } diff --git a/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/JettySamlSessionStore.java b/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/JettySamlSessionStore.java index d86184b8ae..6e51f110f8 100755 --- a/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/JettySamlSessionStore.java +++ b/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/JettySamlSessionStore.java @@ -8,6 +8,7 @@ import org.keycloak.adapters.spi.SessionIdMapper; import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement; import org.keycloak.adapters.saml.SamlSession; import org.keycloak.adapters.saml.SamlSessionStore; +import org.keycloak.dom.saml.v2.protocol.StatusType; import javax.servlet.http.HttpSession; @@ -37,6 +38,28 @@ public class JettySamlSessionStore implements SamlSessionStore { this.sessionManagement = sessionManagement; } + @Override + public void setCurrentAction(CurrentAction action) { + if (action == CurrentAction.NONE && request.getSession(false) == null) return; + request.getSession().setAttribute(CURRENT_ACTION, action); + } + + @Override + public boolean isLoggingIn() { + HttpSession session = request.getSession(false); + if (session == null) return false; + CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION); + return action == CurrentAction.LOGGING_IN; + } + + @Override + public boolean isLoggingOut() { + HttpSession session = request.getSession(false); + if (session == null) return false; + CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION); + return action == CurrentAction.LOGGING_OUT; + } + @Override public void logoutAccount() { HttpSession session = request.getSession(false); diff --git a/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java b/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java index cbd036e882..e690db5d6f 100755 --- a/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java +++ b/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java @@ -7,6 +7,7 @@ import org.keycloak.adapters.spi.SessionIdMapper; import org.keycloak.adapters.saml.SamlSession; import org.keycloak.adapters.saml.SamlSessionStore; import org.keycloak.adapters.servlet.FilterSessionStore; +import org.keycloak.dom.saml.v2.protocol.StatusType; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; @@ -29,6 +30,28 @@ public class FilterSamlSessionStore extends FilterSessionStore implements SamlSe this.idMapper = idMapper; } + @Override + public void setCurrentAction(CurrentAction action) { + if (action == CurrentAction.NONE && request.getSession(false) == null) return; + request.getSession().setAttribute(CURRENT_ACTION, action); + } + + @Override + public boolean isLoggingIn() { + HttpSession session = request.getSession(false); + if (session == null) return false; + CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION); + return action == CurrentAction.LOGGING_IN; + } + + @Override + public boolean isLoggingOut() { + HttpSession session = request.getSession(false); + if (session == null) return false; + CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION); + return action == CurrentAction.LOGGING_OUT; + } + @Override public void logoutAccount() { HttpSession session = request.getSession(false); diff --git a/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java b/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java index bce4dbc10a..b2a0deda68 100755 --- a/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java +++ b/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java @@ -137,11 +137,6 @@ public class SamlFilter implements Filter { if (challenge != null) { log.fine("challenge"); challenge.challenge(facade); - if (challenge.errorPage()) { - response.sendError(challenge.getResponseCode()); - return; - } - log.fine("sending challenge"); return; } if (!facade.isEnded()) { diff --git a/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java b/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java index d231265623..3119ba7c9e 100755 --- a/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java +++ b/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java @@ -213,10 +213,6 @@ public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator i loginConfig = request.getContext().getLoginConfig(); } challenge.challenge(facade); - if (challenge.errorPage()) { - log.fine("error page"); - if (forwardToErrorPageInternal(request, response, loginConfig))return false; - } } return false; } diff --git a/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlSessionStore.java b/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlSessionStore.java index adbc441586..804acf36e1 100755 --- a/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlSessionStore.java +++ b/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlSessionStore.java @@ -9,6 +9,8 @@ import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.SessionIdMapper; import org.keycloak.adapters.tomcat.CatalinaUserSessionManagement; import org.keycloak.adapters.tomcat.GenericPrincipalFactory; +import org.keycloak.dom.saml.v2.protocol.StatusResponseType; +import org.keycloak.dom.saml.v2.protocol.StatusType; import javax.servlet.http.HttpSession; import java.io.IOException; @@ -41,6 +43,28 @@ public class CatalinaSamlSessionStore implements SamlSessionStore { this.facade = facade; } + @Override + public void setCurrentAction(CurrentAction action) { + if (action == CurrentAction.NONE && request.getSession(false) == null) return; + request.getSession().setAttribute(CURRENT_ACTION, action); + } + + @Override + public boolean isLoggingIn() { + HttpSession session = request.getSession(false); + if (session == null) return false; + CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION); + return action == CurrentAction.LOGGING_IN; + } + + @Override + public boolean isLoggingOut() { + HttpSession session = request.getSession(false); + if (session == null) return false; + CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION); + return action == CurrentAction.LOGGING_OUT; + } + @Override public void logoutAccount() { Session sessionInternal = request.getSessionInternal(false); diff --git a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java index 721f0c0ac4..8f1929af58 100755 --- a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java +++ b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java @@ -55,10 +55,6 @@ public abstract class AbstractSamlAuthMech implements AuthenticationMechanism { public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) { AuthChallenge challenge = exchange.getAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY); if (challenge != null) { - if (challenge.errorPage() && errorPage != null) { - Integer code = servePage(exchange, errorPage); - return new ChallengeResult(true, code); - } UndertowHttpFacade facade = createFacade(exchange); if (challenge.challenge(facade)) { return new ChallengeResult(true, exchange.getResponseCode()); diff --git a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java index 8afcc1f40e..34d718b59d 100755 --- a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java +++ b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java @@ -13,9 +13,12 @@ import org.keycloak.adapters.saml.SamlSession; import org.keycloak.adapters.saml.SamlSessionStore; import org.keycloak.adapters.undertow.UndertowUserSessionManagement; import org.keycloak.common.util.KeycloakUriBuilder; +import org.keycloak.dom.saml.v2.protocol.StatusType; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import java.io.IOException; import java.security.Principal; import java.util.LinkedList; import java.util.List; @@ -34,6 +37,7 @@ public class ServletSamlSessionStore implements SamlSessionStore { private final SecurityContext securityContext; private final SessionIdMapper idMapper; + public ServletSamlSessionStore(HttpServerExchange exchange, UndertowUserSessionManagement sessionManagement, SecurityContext securityContext, SessionIdMapper idMapper) { @@ -43,6 +47,28 @@ public class ServletSamlSessionStore implements SamlSessionStore { this.idMapper = idMapper; } + @Override + public void setCurrentAction(CurrentAction action) { + if (action == CurrentAction.NONE && getRequest().getSession(false) == null) return; + getRequest().getSession().setAttribute(CURRENT_ACTION, action); + } + + @Override + public boolean isLoggingIn() { + HttpSession session = getRequest().getSession(false); + if (session == null) return false; + CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION); + return action == CurrentAction.LOGGING_IN; + } + + @Override + public boolean isLoggingOut() { + HttpSession session = getRequest().getSession(false); + if (session == null) return false; + CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION); + return action == CurrentAction.LOGGING_OUT; + } + @Override public void logoutAccount() { HttpSession session = getSession(false); @@ -170,8 +196,18 @@ public class ServletSamlSessionStore implements SamlSessionStore { } protected HttpSession getSession(boolean create) { - final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); - HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest(); + HttpServletRequest req = getRequest(); return req.getSession(create); } + + private HttpServletResponse getResponse() { + final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + return (HttpServletResponse)servletRequestContext.getServletResponse(); + + } + + private HttpServletRequest getRequest() { + final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + return (HttpServletRequest) servletRequestContext.getServletRequest(); + } } diff --git a/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusCodeType.java b/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusCodeType.java index 2ce85eb6b9..482834b873 100755 --- a/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusCodeType.java +++ b/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusCodeType.java @@ -17,6 +17,7 @@ */ package org.keycloak.dom.saml.v2.protocol; +import java.io.Serializable; import java.net.URI; /** @@ -39,7 +40,7 @@ import java.net.URI; * </complexType> * */ -public class StatusCodeType { +public class StatusCodeType implements Serializable { protected StatusCodeType statusCode; protected URI value; diff --git a/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusType.java b/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusType.java index 9918879e43..2e2eab97ff 100755 --- a/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusType.java +++ b/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusType.java @@ -17,6 +17,8 @@ */ package org.keycloak.dom.saml.v2.protocol; +import java.io.Serializable; + /** *

* Java class for StatusType complex type. @@ -38,7 +40,7 @@ package org.keycloak.dom.saml.v2.protocol; * </complexType> * */ -public class StatusType { +public class StatusType implements Serializable { protected String statusMessage; protected StatusCodeType statusCode; diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java b/saml/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java index db7ac7ebf0..237365673b 100755 --- a/saml/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java +++ b/saml/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java @@ -1,6 +1,12 @@ package org.keycloak.saml; +import org.keycloak.dom.saml.v2.assertion.NameIDType; +import org.keycloak.dom.saml.v2.protocol.StatusCodeType; +import org.keycloak.dom.saml.v2.protocol.StatusResponseType; +import org.keycloak.dom.saml.v2.protocol.StatusType; import org.keycloak.saml.common.constants.JBossSAMLURIConstants; +import org.keycloak.saml.common.exceptions.ConfigurationException; +import org.keycloak.saml.common.exceptions.ParsingException; import org.keycloak.saml.common.exceptions.ProcessingException; import org.keycloak.saml.processing.api.saml.v2.response.SAML2Response; import org.keycloak.saml.processing.core.saml.v2.common.IDGenerator; @@ -9,8 +15,11 @@ import org.keycloak.saml.processing.core.saml.v2.holders.IDPInfoHolder; import org.keycloak.saml.processing.core.saml.v2.holders.IssuerInfoHolder; import org.keycloak.saml.processing.core.saml.v2.holders.SPInfoHolder; import org.keycloak.dom.saml.v2.protocol.ResponseType; +import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil; import org.w3c.dom.Document; +import java.net.URI; + /** * @author Bill Burke * @version $Revision: 1 $ @@ -38,29 +47,25 @@ public class SAML2ErrorResponseBuilder { public Document buildDocument() throws ProcessingException { - Document samlResponse = null; - ResponseType responseType = null; - SAML2Response saml2Response = new SAML2Response(); + try { + StatusResponseType statusResponse = new StatusResponseType(IDGenerator.create("ID_"), XMLTimeUtil.getIssueInstant()); - // Create a response type - String id = IDGenerator.create("ID_"); + statusResponse.setStatus(JBossSAMLAuthnResponseFactory.createStatusTypeForResponder(status)); + NameIDType issuer = new NameIDType(); + issuer.setValue(this.issuer); - IssuerInfoHolder issuerHolder = new IssuerInfoHolder(issuer); - issuerHolder.setStatusCode(status); + statusResponse.setIssuer(issuer); + statusResponse.setDestination(destination); - IDPInfoHolder idp = new IDPInfoHolder(); - idp.setNameIDFormatValue(null); - idp.setNameIDFormat(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get()); + SAML2Response saml2Response = new SAML2Response(); + return saml2Response.convert(statusResponse); + } catch (ConfigurationException e) { + throw new ProcessingException(e); + } catch (ParsingException e) { + throw new ProcessingException(e); + } - SPInfoHolder sp = new SPInfoHolder(); - sp.setResponseDestinationURI(destination); - - responseType = saml2Response.createResponseType(id); - responseType.setStatus(JBossSAMLAuthnResponseFactory.createStatusTypeForResponder(status)); - responseType.setDestination(destination); - - return samlResponse; } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java index 816be92945..31b05508d8 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java @@ -24,6 +24,7 @@ package org.keycloak.testsuite.adapter; import org.junit.Assert; import org.junit.rules.ExternalResource; import org.keycloak.OAuth2Constants; +import org.keycloak.adapters.OIDCAuthenticationError; import org.keycloak.common.Version; import org.keycloak.representations.VersionRepresentation; import org.keycloak.admin.client.Keycloak; @@ -42,6 +43,7 @@ import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.pages.AccountSessionsPage; import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.rule.AbstractKeycloakRule; +import org.keycloak.testsuite.rule.ErrorServlet; import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.WebResource; import org.keycloak.testsuite.rule.WebRule; @@ -385,6 +387,7 @@ public class AdapterTestStrategy extends ExternalResource { * @throws Exception */ public void testNullBearerTokenCustomErrorPage() throws Exception { + ErrorServlet.authError = null; Client client = ClientBuilder.newClient(); WebTarget target = client.target(APP_SERVER_BASE_URL + "/customer-db-error-page/"); @@ -396,11 +399,15 @@ public class AdapterTestStrategy extends ExternalResource { response.close(); response = client.target(location).request().get(); } - Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(401, response.getStatus()); String errorPageResponse = response.readEntity(String.class); Assert.assertTrue(errorPageResponse.contains("Error Page")); response.close(); + Assert.assertNotNull(ErrorServlet.authError); + OIDCAuthenticationError error = (OIDCAuthenticationError)ErrorServlet.authError; + Assert.assertEquals(OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, error.getReason()); + ErrorServlet.authError = null; response = target.request().header(HttpHeaders.AUTHORIZATION, "Bearer null").get(); // TODO: follow redirects automatically if possible if (response.getStatus() == 302) { @@ -408,10 +415,13 @@ public class AdapterTestStrategy extends ExternalResource { response.close(); response = client.target(location).request().get(); } - Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(401, response.getStatus()); errorPageResponse = response.readEntity(String.class); Assert.assertTrue(errorPageResponse.contains("Error Page")); response.close(); + Assert.assertNotNull(ErrorServlet.authError); + error = (OIDCAuthenticationError)ErrorServlet.authError; + Assert.assertEquals(OIDCAuthenticationError.Reason.INVALID_TOKEN, error.getReason()); client.close(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTest.java index f97a05ebd7..0d20f7a7d3 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTest.java @@ -48,12 +48,7 @@ public class SamlAdapterTest { @Test public void testPostBadRealmSignature() { - testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() { - @Override - public void check(WebDriver driver) { - Assert.assertTrue(driver.getPageSource().contains("Forbidden")); - } - }); + testStrategy.testPostBadRealmSignature(); } @Test @@ -61,11 +56,16 @@ public class SamlAdapterTest { testStrategy.testPostSimpleUnauthorized( new SamlAdapterTestStrategy.CheckAuthError() { @Override public void check(WebDriver driver) { - Assert.assertTrue(driver.getPageSource().contains("Forbidden")); + String pageSource = driver.getPageSource(); + Assert.assertTrue(pageSource.contains("Error Page")); } }); } + @Test + public void testErrorHandling() throws Exception { + testStrategy.testErrorHandling(); + } @Test public void testMetadataPostSignedLoginLogout() throws Exception { testStrategy.testMetadataPostSignedLoginLogout(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java index 4791c2a70a..b63c9609cb 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java @@ -1,26 +1,17 @@ package org.keycloak.testsuite.keycloaksaml; import org.apache.commons.io.IOUtils; -import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput; import org.junit.Assert; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; import org.junit.rules.ExternalResource; -import org.keycloak.Config; +import org.keycloak.adapters.saml.SamlAuthenticationError; import org.keycloak.adapters.saml.SamlPrincipal; import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientSessionModel; import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.protocol.oidc.OIDCLoginProtocol; -import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.saml.mappers.AttributeStatementHelper; import org.keycloak.protocol.saml.mappers.GroupMembershipMapper; import org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper; @@ -28,33 +19,28 @@ import org.keycloak.protocol.saml.mappers.HardcodedRole; import org.keycloak.protocol.saml.mappers.RoleListMapper; import org.keycloak.protocol.saml.mappers.RoleNameMapper; import org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper; -import org.keycloak.representations.AccessToken; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.saml.BaseSAML2BindingBuilder; +import org.keycloak.saml.SAML2ErrorResponseBuilder; +import org.keycloak.saml.common.constants.JBossSAMLURIConstants; import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants; import org.keycloak.services.managers.RealmManager; -import org.keycloak.services.resources.admin.AdminRoot; import org.keycloak.testsuite.KeycloakServer; import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.rule.AbstractKeycloakRule; +import org.keycloak.testsuite.rule.ErrorServlet; import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.WebResource; import org.keycloak.testsuite.rule.WebRule; -import org.keycloak.util.JsonSerialization; import org.openqa.selenium.WebDriver; +import org.w3c.dom.Document; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.ClientRequestContext; -import javax.ws.rs.client.ClientRequestFilter; -import javax.ws.rs.client.Entity; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriBuilder; import java.io.IOException; -import java.io.InputStream; +import java.net.URI; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -115,6 +101,33 @@ public class SamlAdapterTestStrategy extends ExternalResource { Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml")); } + public void testErrorHandling() throws Exception { + ErrorServlet.authError = null; + Client client = ClientBuilder.newClient(); + // make sure + Response response = client.target(APP_SERVER_BASE_URL + "/employee-sig/").request().get(); + response.close(); + SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder() + .destination(APP_SERVER_BASE_URL + "/employee-sig/") + .issuer(AUTH_SERVER_URL + "/realms/demo") + .status(JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get()); + BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder() + .relayState(null); + Document document = builder.buildDocument(); + URI uri = binding.redirectBinding(document).generateURI(APP_SERVER_BASE_URL + "/employee-sig/", false); + response = client.target(uri).request().get(); + String errorPage = response.readEntity(String.class); + response.close(); + Assert.assertTrue(errorPage.contains("Error Page")); + client.close(); + Assert.assertNotNull(ErrorServlet.authError); + SamlAuthenticationError error = (SamlAuthenticationError)ErrorServlet.authError; + Assert.assertEquals(SamlAuthenticationError.Reason.ERROR_STATUS, error.getReason()); + Assert.assertNotNull(error.getStatus()); + ErrorServlet.authError = null; + + } + public void testPostSimpleLoginLogout() { driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post/"); assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml"); @@ -383,13 +396,17 @@ public class SamlAdapterTestStrategy extends ExternalResource { void check(WebDriver driver); } - public void testPostBadRealmSignature(CheckAuthError error) { + public void testPostBadRealmSignature() { + ErrorServlet.authError = null; driver.navigate().to(APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/"); assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml"); loginPage.login("bburke", "password"); assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/"); System.out.println(driver.getPageSource()); - error.check(driver); + Assert.assertNotNull(ErrorServlet.authError); + SamlAuthenticationError error = (SamlAuthenticationError)ErrorServlet.authError; + Assert.assertEquals(SamlAuthenticationError.Reason.INVALID_SIGNATURE, error.getReason()); + ErrorServlet.authError = null; } public void testMetadataPostSignedLoginLogout() throws Exception { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlKeycloakRule.java index 34d17f0aff..5a1d01f807 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlKeycloakRule.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlKeycloakRule.java @@ -115,6 +115,7 @@ public abstract class SamlKeycloakRule extends AbstractKeycloakRule { .addServlets(regularServletInfo) .addSecurityConstraint(constraint) .addServletExtension(new SamlServletExtension()); + addErrorPage("/error.html", deploymentInfo); server.getServer().deploy(deploymentInfo); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java index fc2dc4106d..155bdf117f 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java @@ -1,6 +1,7 @@ package org.keycloak.testsuite.rule; import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.ErrorPage; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.SecurityConstraint; @@ -156,7 +157,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource { return new DeploymentBuilder(); } - public void addErrorPage(DeploymentInfo di) { + public void addErrorPage(String errorPage, DeploymentInfo di) { ServletInfo servlet = new ServletInfo("Error Page", ErrorServlet.class); servlet.addMapping("/error.html"); SecurityConstraint constraint = new SecurityConstraint(); @@ -166,6 +167,11 @@ public abstract class AbstractKeycloakRule extends ExternalResource { constraint.setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.PERMIT); di.addSecurityConstraint(constraint); di.addServlet(servlet); + di + .addErrorPage(new ErrorPage(errorPage, 400)) + .addErrorPage(new ErrorPage(errorPage, 401)) + .addErrorPage(new ErrorPage(errorPage, 403)) + .addErrorPage(new ErrorPage(errorPage, 500)); } public void deployJaxrsApplication(String name, String contextPath, Class applicationClass, Map initParams) { @@ -346,9 +352,9 @@ public abstract class AbstractKeycloakRule extends ExternalResource { constraint.addRoleAllowed(role); di.addSecurityConstraint(constraint); } - LoginConfig loginConfig = new LoginConfig("KEYCLOAK", "demo", null, errorPage); + LoginConfig loginConfig = new LoginConfig("KEYCLOAK", "demo", null, null); di.setLoginConfig(loginConfig); - addErrorPage(di); + addErrorPage(errorPage, di); server.getServer().deploy(di); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/ErrorServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/ErrorServlet.java index 47f7135be6..68410d48bf 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/ErrorServlet.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/ErrorServlet.java @@ -1,5 +1,7 @@ package org.keycloak.testsuite.rule; +import org.keycloak.adapters.spi.AuthenticationError; + import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -12,10 +14,11 @@ import java.io.PrintWriter; * @author Marek Posolda */ public class ErrorServlet extends HttpServlet { + public static AuthenticationError authError; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - + authError = (AuthenticationError)req.getAttribute(AuthenticationError.class.getName()); resp.setContentType("text/html"); PrintWriter pw = resp.getWriter(); @@ -25,4 +28,9 @@ public class ErrorServlet extends HttpServlet { } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + doGet(req, resp); + } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlAdapterTest.java index 88f97a6e01..9a47b4414e 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlAdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlAdapterTest.java @@ -53,12 +53,7 @@ public class SamlAdapterTest { @Test public void testPostBadRealmSignature() { - testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() { - @Override - public void check(WebDriver driver) { - Assert.assertTrue(driver.getPageSource().contains("Forbidden")); - } - }); + testStrategy.testPostBadRealmSignature(); } @Test @@ -72,7 +67,7 @@ public class SamlAdapterTest { testStrategy.testPostSimpleUnauthorized(new SamlAdapterTestStrategy.CheckAuthError() { @Override public void check(WebDriver driver) { - Assert.assertTrue(driver.getPageSource().contains("Forbidden")); + Assert.assertTrue(driver.getPageSource().contains("Error Page")); } }); } finally { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlKeycloakRule.java index e1fd3c85ff..a348408ce6 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlKeycloakRule.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlKeycloakRule.java @@ -114,6 +114,7 @@ public abstract class SamlKeycloakRule extends AbstractKeycloakRule { .addFilter(samlFilter) .addFilterUrlMapping("saml-filter", "/*", DispatcherType.REQUEST) .addServletExtension(new SamlServletExtension()); + addErrorPage("/error.html", deploymentInfo); server.getServer().deploy(deploymentInfo); } diff --git a/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/JettySamlTest.java b/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/JettySamlTest.java index 1506b0ce6a..6228a688ef 100755 --- a/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/JettySamlTest.java +++ b/testsuite/jetty/jetty81/src/test/java/org/keycloak/testsuite/JettySamlTest.java @@ -104,6 +104,11 @@ public class JettySamlTest { } catch (Exception e) {} } + @Test + public void testErrorHandling() throws Exception { + testStrategy.testErrorHandling(); + } + @Test public void testPostSimpleLoginLogout() { testStrategy.testPostSimpleLoginLogout(); @@ -166,12 +171,7 @@ public class JettySamlTest { @Test public void testPostBadRealmSignature() { - testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() { - @Override - public void check(WebDriver driver) { - Assert.assertEquals(driver.getPageSource(), ""); - } - }); + testStrategy.testPostBadRealmSignature(); } @Test diff --git a/testsuite/jetty/jetty81/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml b/testsuite/jetty/jetty81/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml index f44a60bba0..e41448a81c 100755 --- a/testsuite/jetty/jetty81/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml +++ b/testsuite/jetty/jetty81/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml @@ -25,6 +25,26 @@ /error.html + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + + Users diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml index 86db4a46a4..71eff5281a 100755 --- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml +++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml @@ -10,11 +10,40 @@ SendUsernameServlet org.keycloak.testsuite.keycloaksaml.SendUsernameServlet + + Error Servlet + org.keycloak.testsuite.rule.ErrorServlet + SendUsernameServlet /* + + Error Servlet + /error.html + + + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + + Users diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml index 86db4a46a4..ed4f018749 100755 --- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml +++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml @@ -10,11 +10,39 @@ SendUsernameServlet org.keycloak.testsuite.keycloaksaml.SendUsernameServlet + + Error Servlet + org.keycloak.testsuite.rule.ErrorServlet + SendUsernameServlet /* + + Error Servlet + /error.html + + + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + Users diff --git a/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/JettySamlTest.java b/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/JettySamlTest.java index e71887e203..40edb45d0a 100755 --- a/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/JettySamlTest.java +++ b/testsuite/jetty/jetty91/src/test/java/org/keycloak/testsuite/JettySamlTest.java @@ -103,6 +103,11 @@ public class JettySamlTest { } catch (Exception e) {} } + @Test + public void testErrorHandling() throws Exception { + testStrategy.testErrorHandling(); + } + @Test public void testPostSimpleLoginLogout() { testStrategy.testPostSimpleLoginLogout(); @@ -165,12 +170,7 @@ public class JettySamlTest { @Test public void testPostBadRealmSignature() { - testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() { - @Override - public void check(WebDriver driver) { - Assert.assertEquals(driver.getPageSource(), ""); - } - }); + testStrategy.testPostBadRealmSignature(); } @Test diff --git a/testsuite/jetty/jetty91/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml b/testsuite/jetty/jetty91/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml index f44a60bba0..e41448a81c 100755 --- a/testsuite/jetty/jetty91/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml +++ b/testsuite/jetty/jetty91/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml @@ -25,6 +25,26 @@ /error.html + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + + Users diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml index 86db4a46a4..2f7ef22b7f 100755 --- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml +++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml @@ -10,12 +10,39 @@ SendUsernameServlet org.keycloak.testsuite.keycloaksaml.SendUsernameServlet + + Error Servlet + org.keycloak.testsuite.rule.ErrorServlet + SendUsernameServlet /* - + + Error Servlet + /error.html + + + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + Users /* diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml index 86db4a46a4..ed4f018749 100755 --- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml +++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml @@ -10,11 +10,39 @@ SendUsernameServlet org.keycloak.testsuite.keycloaksaml.SendUsernameServlet + + Error Servlet + org.keycloak.testsuite.rule.ErrorServlet + SendUsernameServlet /* + + Error Servlet + /error.html + + + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + Users diff --git a/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/JettySamlTest.java b/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/JettySamlTest.java index e71887e203..cd3c11ace2 100755 --- a/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/JettySamlTest.java +++ b/testsuite/jetty/jetty92/src/test/java/org/keycloak/testsuite/JettySamlTest.java @@ -103,6 +103,11 @@ public class JettySamlTest { } catch (Exception e) {} } + @Test + public void testErrorHandling() throws Exception { + testStrategy.testErrorHandling(); + } + @Test public void testPostSimpleLoginLogout() { testStrategy.testPostSimpleLoginLogout(); @@ -165,12 +170,7 @@ public class JettySamlTest { @Test public void testPostBadRealmSignature() { - testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() { - @Override - public void check(WebDriver driver) { - Assert.assertEquals(driver.getPageSource(), ""); - } - }); + testStrategy.testPostBadRealmSignature( ); } @Test diff --git a/testsuite/jetty/jetty92/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml b/testsuite/jetty/jetty92/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml index f44a60bba0..e41448a81c 100755 --- a/testsuite/jetty/jetty92/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml +++ b/testsuite/jetty/jetty92/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml @@ -25,6 +25,26 @@ /error.html + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + + Users diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml index 86db4a46a4..ed4f018749 100755 --- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml +++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml @@ -10,11 +10,39 @@ SendUsernameServlet org.keycloak.testsuite.keycloaksaml.SendUsernameServlet + + Error Servlet + org.keycloak.testsuite.rule.ErrorServlet + SendUsernameServlet /* + + Error Servlet + /error.html + + + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + Users diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml index 86db4a46a4..ed4f018749 100755 --- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml +++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml @@ -10,11 +10,39 @@ SendUsernameServlet org.keycloak.testsuite.keycloaksaml.SendUsernameServlet + + Error Servlet + org.keycloak.testsuite.rule.ErrorServlet + SendUsernameServlet /* + + Error Servlet + /error.html + + + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + Users diff --git a/testsuite/tomcat6/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java b/testsuite/tomcat6/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java index c72ab019d9..0e6973a083 100755 --- a/testsuite/tomcat6/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java +++ b/testsuite/tomcat6/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java @@ -112,6 +112,11 @@ public class TomcatSamlTest { testStrategy.testPostSignedLoginLogoutPersistentNameID(); } + @Test + public void testErrorHandling() throws Exception { + testStrategy.testErrorHandling(); + } + @Test public void testPostSignedLoginLogoutEmailNameID() { testStrategy.testPostSignedLoginLogoutEmailNameID(); @@ -149,12 +154,7 @@ public class TomcatSamlTest { @Test public void testPostBadRealmSignature() { - testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() { - @Override - public void check(WebDriver driver) { - Assert.assertEquals(driver.getPageSource(), ""); - } - }); + testStrategy.testPostBadRealmSignature(); } @Test diff --git a/testsuite/tomcat6/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml b/testsuite/tomcat6/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml index c2cef86e5f..fa6a47b406 100755 --- a/testsuite/tomcat6/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml +++ b/testsuite/tomcat6/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml @@ -25,6 +25,26 @@ /error.html + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + + Users @@ -44,11 +64,7 @@ BASIC demo - - /error.html - /error.html - - + admin diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml index 86db4a46a4..2f7ef22b7f 100755 --- a/testsuite/tomcat6/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml +++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml @@ -10,12 +10,39 @@ SendUsernameServlet org.keycloak.testsuite.keycloaksaml.SendUsernameServlet + + Error Servlet + org.keycloak.testsuite.rule.ErrorServlet + SendUsernameServlet /* - + + Error Servlet + /error.html + + + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + Users /* diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml index 86db4a46a4..ed4f018749 100755 --- a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml +++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml @@ -10,11 +10,39 @@ SendUsernameServlet org.keycloak.testsuite.keycloaksaml.SendUsernameServlet + + Error Servlet + org.keycloak.testsuite.rule.ErrorServlet + SendUsernameServlet /* + + Error Servlet + /error.html + + + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + Users diff --git a/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java b/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java index f9cb8536ee..2483333c96 100755 --- a/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java +++ b/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java @@ -92,6 +92,11 @@ public class TomcatSamlTest { } + @Test + public void testErrorHandling() throws Exception { + testStrategy.testErrorHandling(); + } + @Test public void testPostSimpleLoginLogout() { testStrategy.testPostSimpleLoginLogout(); @@ -154,12 +159,7 @@ public class TomcatSamlTest { @Test public void testPostBadRealmSignature() { - testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() { - @Override - public void check(WebDriver driver) { - Assert.assertEquals(driver.getPageSource(), ""); - } - }); + testStrategy.testPostBadRealmSignature(); } @Test diff --git a/testsuite/tomcat7/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml b/testsuite/tomcat7/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml index c2cef86e5f..fdd69a37d3 100755 --- a/testsuite/tomcat7/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml +++ b/testsuite/tomcat7/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml @@ -25,6 +25,26 @@ /error.html + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + + Users diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml index 86db4a46a4..ed4f018749 100755 --- a/testsuite/tomcat7/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml +++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml @@ -10,11 +10,39 @@ SendUsernameServlet org.keycloak.testsuite.keycloaksaml.SendUsernameServlet + + Error Servlet + org.keycloak.testsuite.rule.ErrorServlet + SendUsernameServlet /* + + Error Servlet + /error.html + + + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + Users diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml index 86db4a46a4..ed4f018749 100755 --- a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml +++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml @@ -10,11 +10,39 @@ SendUsernameServlet org.keycloak.testsuite.keycloaksaml.SendUsernameServlet + + Error Servlet + org.keycloak.testsuite.rule.ErrorServlet + SendUsernameServlet /* + + Error Servlet + /error.html + + + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + Users diff --git a/testsuite/tomcat8/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java b/testsuite/tomcat8/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java index 405c6ee9db..a4a38299af 100755 --- a/testsuite/tomcat8/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java +++ b/testsuite/tomcat8/src/test/java/org/keycloak/testsuite/TomcatSamlTest.java @@ -92,6 +92,11 @@ public class TomcatSamlTest { @Rule public SamlAdapterTestStrategy testStrategy = new SamlAdapterTestStrategy("http://localhost:8081/auth", "http://localhost:8082", keycloakRule); + @Test + public void testErrorHandling() throws Exception { + testStrategy.testErrorHandling(); + } + @Test public void testPostSimpleLoginLogout() { testStrategy.testPostSimpleLoginLogout(); @@ -154,12 +159,7 @@ public class TomcatSamlTest { @Test public void testPostBadRealmSignature() { - testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() { - @Override - public void check(WebDriver driver) { - Assert.assertEquals(driver.getPageSource(), ""); - } - }); + testStrategy.testPostBadRealmSignature(); } @Test diff --git a/testsuite/tomcat8/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml b/testsuite/tomcat8/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml index c2cef86e5f..fdd69a37d3 100755 --- a/testsuite/tomcat8/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml +++ b/testsuite/tomcat8/src/test/resources/adapter-test/customer-db-error-page/WEB-INF/web.xml @@ -25,6 +25,26 @@ /error.html + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + + Users diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml index 86db4a46a4..ed4f018749 100755 --- a/testsuite/tomcat8/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml +++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/web.xml @@ -10,11 +10,39 @@ SendUsernameServlet org.keycloak.testsuite.keycloaksaml.SendUsernameServlet + + Error Servlet + org.keycloak.testsuite.rule.ErrorServlet + SendUsernameServlet /* + + Error Servlet + /error.html + + + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + Users diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml index 86db4a46a4..ed4f018749 100755 --- a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml +++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-get/WEB-INF/web.xml @@ -10,11 +10,39 @@ SendUsernameServlet org.keycloak.testsuite.keycloaksaml.SendUsernameServlet + + Error Servlet + org.keycloak.testsuite.rule.ErrorServlet + SendUsernameServlet /* + + Error Servlet + /error.html + + + + 400 + /error.html + + + + 401 + /error.html + + + + 403 + /error.html + + + + 500 + /error.html + Users