Skip to content

Commit

Permalink
fix: on RootProvider always rely on the Error page if unexpected erro…
Browse files Browse the repository at this point in the history
…r is raised

fixes gravitee-io/issues#9000

fixes AM-535

(cherry picked from commit 39355e4)
  • Loading branch information
leleueri authored and aurelien-pacaud committed Apr 13, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent be1a306 commit 036f627
Showing 6 changed files with 121 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -21,6 +21,8 @@
import io.gravitee.am.common.web.UriBuilder;
import io.gravitee.am.gateway.handler.common.vertx.utils.RequestUtils;
import io.gravitee.am.gateway.handler.common.vertx.utils.UriBuilderRequest;
import io.gravitee.am.gateway.handler.root.RootProvider;
import io.gravitee.am.gateway.handler.root.resources.handler.error.AbstractErrorHandler;
import io.gravitee.am.service.AuthenticationFlowContextService;
import io.vertx.core.Handler;
import io.vertx.rxjava3.core.MultiMap;
@@ -36,22 +38,21 @@
* @author GraviteeSource Team
*/

public class MFAChallengeFailureHandler implements Handler<RoutingContext> {
public class MFAChallengeFailureHandler extends AbstractErrorHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(MFAChallengeFailureHandler.class);
private static final String ERROR_CODE_VALUE = "send_challenge_failed";

private final AuthenticationFlowContextService authenticationFlowContextService;

public MFAChallengeFailureHandler(AuthenticationFlowContextService authenticationFlowContextService) {
super(RootProvider.PATH_ERROR);
this.authenticationFlowContextService = authenticationFlowContextService;
}

@Override
public void handle(RoutingContext routingContext) {
if (routingContext.failed()) {
Throwable throwable = routingContext.failure();
handleException(routingContext, throwable == null ? "MFA Challenge failed for unexpected reason" : throwable.getMessage());
}
public void doHandle(RoutingContext routingContext) {
Throwable throwable = routingContext.failure();
handleException(routingContext, throwable == null ? "MFA Challenge failed for unexpected reason" : throwable.getMessage());
}

private void handleException(RoutingContext context, String errorDescription) {
@@ -70,7 +71,7 @@ private MultiMap updateQueryParams(RoutingContext context, String errorDescripti

if (context.request().getParam(Parameters.LOGIN_HINT) != null) {
// encode login_hint parameter (to not replace '+' sign by a space ' ')
queryParams.set(Parameters.LOGIN_HINT, UriBuilder.encodeURIComponent(context.request().getParam(ConstantKeys.USERNAME_PARAM_KEY)));
queryParams.set(Parameters.LOGIN_HINT, UriBuilder.encodeURIComponent(context.request().getParam(Parameters.LOGIN_HINT)));
}

return queryParams;
@@ -87,11 +88,4 @@ private void logoutUser(RoutingContext context) {
context.session().destroy();
}
}

private void doRedirect(HttpServerResponse response, String url) {
response
.putHeader(HttpHeaders.LOCATION, url)
.setStatusCode(302)
.end();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Copyright (C) 2015 The Gravitee team (http://gravitee.io)
*
* 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 io.gravitee.am.gateway.handler.root.resources.handler.error;

import io.gravitee.am.common.oauth2.Parameters;
import io.gravitee.am.common.utils.ConstantKeys;
import io.gravitee.am.gateway.handler.common.vertx.utils.RequestUtils;
import io.gravitee.am.gateway.handler.common.vertx.utils.UriBuilderRequest;
import io.gravitee.am.model.oidc.Client;
import io.gravitee.common.http.HttpHeaders;
import io.vertx.core.Handler;
import io.vertx.rxjava3.core.MultiMap;
import io.vertx.rxjava3.core.http.HttpServerRequest;
import io.vertx.rxjava3.core.http.HttpServerResponse;
import io.vertx.rxjava3.ext.web.RoutingContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static io.gravitee.am.common.utils.ConstantKeys.CLIENT_CONTEXT_KEY;
import static io.gravitee.am.gateway.handler.common.vertx.utils.UriBuilderRequest.CONTEXT_PATH;

/**
* @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
* @author GraviteeSource Team
*/
public abstract class AbstractErrorHandler implements Handler<RoutingContext> {

protected final Logger logger = LoggerFactory.getLogger(this.getClass());
protected final String errorPage;

public AbstractErrorHandler(String errorPage) {
this.errorPage = errorPage;
}

@Override
public final void handle(RoutingContext routingContext) {
if (routingContext.failed()) {
try {
doHandle(routingContext);
} catch (Exception e) {
logger.error("Unable to handle error response", e);
String errorPageURL = routingContext.get(CONTEXT_PATH) + errorPage;

final HttpServerRequest request = routingContext.request();
// prepare query parameters
MultiMap parameters = RequestUtils.getCleanedQueryParams(routingContext.request());
// get client if exists
Client client = routingContext.get(CLIENT_CONTEXT_KEY);
if (client != null) {
parameters.set(Parameters.CLIENT_ID, client.getClientId());
} else if (request.getParam(Parameters.CLIENT_ID) != null) {
parameters.set(Parameters.CLIENT_ID, (request.getParam(Parameters.CLIENT_ID)));
}

// append error information
parameters.set(ConstantKeys.ERROR_PARAM_KEY, "server_error");
parameters.set(ConstantKeys.ERROR_DESCRIPTION_PARAM_KEY, "Unexpected error occurred");

// redirect
String proxiedErrorPage = UriBuilderRequest.resolveProxyRequest(request, errorPageURL, parameters, true);
doRedirect(routingContext.response(), proxiedErrorPage);
}
}
}

protected abstract void doHandle(RoutingContext routingContext);

protected void doRedirect(HttpServerResponse response, String url) {
response.putHeader(HttpHeaders.LOCATION, url).setStatusCode(302).end();
}
}
Original file line number Diff line number Diff line change
@@ -23,17 +23,11 @@
import io.gravitee.am.gateway.policy.PolicyChainException;
import io.gravitee.am.model.oidc.Client;
import io.gravitee.am.service.exception.AbstractManagementException;
import io.gravitee.common.http.HttpHeaders;
import io.gravitee.common.http.HttpStatusCode;
import io.vertx.core.Handler;
import io.vertx.ext.auth.webauthn.impl.attestation.AttestationException;
import io.vertx.ext.web.handler.HttpException;
import io.vertx.rxjava3.core.MultiMap;
import io.vertx.rxjava3.core.http.HttpServerRequest;
import io.vertx.rxjava3.core.http.HttpServerResponse;
import io.vertx.rxjava3.ext.web.RoutingContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static io.gravitee.am.common.utils.ConstantKeys.CLIENT_CONTEXT_KEY;
import static io.gravitee.am.common.utils.ConstantKeys.TOKEN_CONTEXT_KEY;
@@ -44,17 +38,14 @@
* @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
* @author GraviteeSource Team
*/
public class ErrorHandler implements Handler<RoutingContext> {

private static final Logger logger = LoggerFactory.getLogger(ErrorHandler.class);
private final String errorPage;
public class ErrorHandler extends AbstractErrorHandler {

public ErrorHandler(String errorPage) {
this.errorPage = errorPage;
super(errorPage);
}

@Override
public void handle(RoutingContext routingContext) {
protected void doHandle(RoutingContext routingContext) {
if (routingContext.failed()) {
Throwable throwable = routingContext.failure();
// management exception (resource not found, server error, ...)
@@ -75,17 +66,7 @@ public void handle(RoutingContext routingContext) {
handleException(routingContext, "technical_error", "Invalid WebAuthn attestation, make sure your device is compliant with the platform requirements");
} else {
logger.error("An exception occurs while handling incoming request", throwable);
if (routingContext.statusCode() != -1) {
routingContext
.response()
.setStatusCode(routingContext.statusCode())
.end();
} else {
routingContext
.response()
.setStatusCode(HttpStatusCode.INTERNAL_SERVER_ERROR_500)
.end();
}
handleException(routingContext, "unexpected_error", "An unexpected error occurs");
}
}
}
@@ -125,7 +106,4 @@ private void handleException(RoutingContext routingContext, String errorCode, St
}
}

private void doRedirect(HttpServerResponse response, String url) {
response.putHeader(HttpHeaders.LOCATION, url).setStatusCode(302).end();
}
}
Original file line number Diff line number Diff line change
@@ -40,14 +40,12 @@
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.*;

import static io.gravitee.am.common.utils.ConstantKeys.PARAM_CONTEXT_KEY;
import static io.gravitee.am.service.utils.ResponseTypeUtils.isHybridFlow;
import static io.gravitee.am.service.utils.ResponseTypeUtils.isImplicitFlow;
import static java.util.Objects.nonNull;

/**
* @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
@@ -174,7 +172,7 @@ private void handleException(RoutingContext context, String errorCode, String er
if (errorDescription != null) {
queryParams.set(ConstantKeys.ERROR_DESCRIPTION_PARAM_KEY, errorDescription);
}
if (context.request().getParam(Parameters.LOGIN_HINT) != null) {
if (nonNull(context.request().getParam(Parameters.LOGIN_HINT)) && nonNull(context.request().getParam(ConstantKeys.USERNAME_PARAM_KEY))) {
// encode login_hint parameter (to not replace '+' sign by a space ' ')
queryParams.set(Parameters.LOGIN_HINT, UriBuilder.encodeURIComponent(context.request().getParam(ConstantKeys.USERNAME_PARAM_KEY)));
}
Original file line number Diff line number Diff line change
@@ -18,6 +18,8 @@
import io.gravitee.am.common.utils.ConstantKeys;
import io.gravitee.am.gateway.handler.common.vertx.utils.RequestUtils;
import io.gravitee.am.gateway.handler.common.vertx.utils.UriBuilderRequest;
import io.gravitee.am.gateway.handler.root.RootProvider;
import io.gravitee.am.gateway.handler.root.resources.handler.error.AbstractErrorHandler;
import io.gravitee.am.service.exception.EmailFormatInvalidException;
import io.gravitee.am.service.exception.InvalidUserException;
import io.gravitee.common.http.HttpHeaders;
@@ -34,27 +36,27 @@
* @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
* @author GraviteeSource Team
*/
public class RegisterFailureHandler implements Handler<RoutingContext> {
public class RegisterFailureHandler extends AbstractErrorHandler {

private static final Logger logger = LoggerFactory.getLogger(RegisterFailureHandler.class);
public RegisterFailureHandler() {
super(RootProvider.PATH_ERROR);
}

@Override
public void handle(RoutingContext context) {
if (context.failed()) {
// prepare response
final MultiMap queryParams = RequestUtils.getCleanedQueryParams(context.request());
// if failure, return to the register page with an error
Throwable cause = context.failure();
if (cause instanceof InvalidUserException) {
queryParams.set(ConstantKeys.WARNING_PARAM_KEY, "invalid_user_information");
} else if (cause instanceof EmailFormatInvalidException) {
queryParams.set(ConstantKeys.WARNING_PARAM_KEY, "invalid_email");
} else {
logger.error("An error occurs while ending user registration", cause);
queryParams.set(ConstantKeys.ERROR_PARAM_KEY, "registration_failed");
}
redirectToPage(context, queryParams, cause);
protected void doHandle(RoutingContext context) {
// prepare response
final MultiMap queryParams = RequestUtils.getCleanedQueryParams(context.request());
// if failure, return to the register page with an error
Throwable cause = context.failure();
if (cause instanceof InvalidUserException) {
queryParams.set(ConstantKeys.WARNING_PARAM_KEY, "invalid_user_information");
} else if (cause instanceof EmailFormatInvalidException) {
queryParams.set(ConstantKeys.WARNING_PARAM_KEY, "invalid_email");
} else {
logger.error("An error occurs while ending user registration", cause);
queryParams.set(ConstantKeys.ERROR_PARAM_KEY, "registration_failed");
}
redirectToPage(context, queryParams, cause);
}

private void redirectToPage(RoutingContext context, MultiMap queryParams, Throwable... exceptions) {
@@ -72,11 +74,4 @@ private void redirectToPage(RoutingContext context, MultiMap queryParams, Throwa
.end();
}
}

private void doRedirect(HttpServerResponse response, String url) {
response
.putHeader(HttpHeaders.LOCATION, url)
.setStatusCode(302)
.end();
}
}
Original file line number Diff line number Diff line change
@@ -25,7 +25,6 @@
import io.gravitee.am.gateway.handler.root.service.user.UserService;
import io.gravitee.am.model.Domain;
import io.gravitee.am.model.Factor;
import io.gravitee.am.model.ReferenceType;
import io.gravitee.am.model.User;
import io.gravitee.am.model.factor.EnrolledFactor;
import io.gravitee.am.model.factor.EnrolledFactorSecurity;
@@ -95,6 +94,7 @@ public class MFAChallengeEndpointTest extends RxWebTestBase {
@Mock
private AuthenticationFlowContextService authenticationFlowContextService;


private LocalSessionStore localSessionStore;

@Override
@@ -153,8 +153,7 @@ public void shouldVerify_fido2Factor() throws Exception {
ctx.setUser(io.vertx.rxjava3.ext.auth.User.newInstance(new io.gravitee.am.gateway.handler.common.vertx.web.auth.user.User(user)));
ctx.put(ConstantKeys.CLIENT_CONTEXT_KEY, client);
ctx.next();
})
.handler(new MFAChallengeEndpoint(factorManager, userService, templateEngine, deviceService, applicationContext, domain, credentialService, factorService, rateLimiterService, verifyAttemptService, emailService));
});

testRequest(
HttpMethod.POST,
@@ -179,6 +178,7 @@ public void shouldVerify_fido2Factor() throws Exception {
},
HttpStatusCode.FOUND_302, "Found", null);
}

@Test
public void shouldNotVerifyCode_noUser() throws Exception {
testRequest(HttpMethod.POST,

0 comments on commit 036f627

Please sign in to comment.