package org.pac4j.vertx; import io.vertx.core.AbstractVerticle; import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.http.HttpMethod; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.handler.BodyHandler; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.UUID; /** * Http server implementation to mimic an OAuth2 provider's possible authentication outcomes. By default this will * be based on the endpoint used for authentication, and we will construct different handlers in the tests to * redirect to different endpoints to mimic the possible outcomes. * * @author Jeremy Prime * @since 2.0.0 */ public class OAuth2ProviderMimic extends AbstractVerticle { public static final int OAUTH2PROVIDER_PORT = 9292; public static final String OAUTH2_PROVIDER_SUCCESS_ENDPOINT = "/authSuccess"; public static final String OAUTH2_PROVIDER_TOKEN_ENDPOINT = "/authToken"; public static final String OAUTH2_PROVIDER_PROFILE_ENDPOINT = "/profile"; private Map<String, String> pendingCodes = new HashMap<>(); private final String userIdToReturn; public OAuth2ProviderMimic(final String userIdToReturn) { this.userIdToReturn = userIdToReturn; } @Override public void start() throws Exception { vertx.createHttpServer().requestHandler(router()::accept).listen(OAUTH2PROVIDER_PORT); } private Router router() { Router router = Router.router(vertx); router.route(HttpMethod.GET, OAUTH2_PROVIDER_SUCCESS_ENDPOINT).handler(authSuccessHandler()); router.route(HttpMethod.POST, OAUTH2_PROVIDER_TOKEN_ENDPOINT).handler(bodyHandlerForTokenRequest()); router.route(HttpMethod.GET, OAUTH2_PROVIDER_TOKEN_ENDPOINT).handler(tokenRequestHandler()); router.route(HttpMethod.GET, OAUTH2_PROVIDER_PROFILE_ENDPOINT).handler(profileHandler()); return router; } private Handler<RoutingContext> authSuccessHandler() { // Create a full URL for redirection return rc -> { final MultiMap requestParams = rc.request().params(); final String redirectUrl = requestParams.get("redirect_uri"); final String responseType = requestParams.get("response_type"); final String clientId = requestParams.get("client_id"); final String state = requestParams.get("state"); // We're not going to be sent scope as we default that final StringBuilder sb = new StringBuilder(redirectUrl); sb.append(redirectUrl.contains("?") ? "&" : "?"); sb.append("state").append("=").append(state); sb.append("&").append("code").append("=").append(newCode(clientId, redirectUrl)); rc.response().putHeader("location", sb.toString()).setStatusCode(302).end(); }; } private Handler<RoutingContext> bodyHandlerForTokenRequest() { return BodyHandler.create(); } private Handler<RoutingContext> tokenRequestHandler() { return rc -> { // Extract parameters from request body - body handler should make these accessible from params collection final Optional<String> grantType = Optional.ofNullable(rc.request().getParam("grant_type")); final String code = rc.request().getParam("code"); final String redirectUri = rc.request().getParam("redirect_uri"); final String clientId = rc.request().getParam("client_id"); Optional<String> token = grantType.flatMap(s -> { if (code == null || redirectUri == null || clientId == null) { return Optional.empty(); } return Optional.of(s.equals("authorization_code")).flatMap(b -> b ? accessToken(clientId, redirectUri) : Optional.empty() ) ; }); if (token.isPresent()) { JsonObject responseBody = new JsonObject().put("access_token", token.get()) .put("token_type", "Bearer") .put("expires_in", 5000); rc.response().setStatusCode(200).end(responseBody.toString()); } else { rc.fail(401); // We couldn't resolve to a token } }; } private Handler<RoutingContext> profileHandler() { return rc -> { final JsonObject responseBody = new JsonObject().put("id", userIdToReturn); rc.response().setStatusCode(200).end(responseBody.toString()); }; } private Optional<String> accessToken(final String clientId, final String redirectUri) { Optional<String> token = Optional.ofNullable(pendingCodes.get(getKey(clientId, redirectUri))).flatMap(code -> Optional.of(UUID.randomUUID().toString()) ); return token; } private String getKey(final String clientId, final String redirectUri) { return clientId + "::" + redirectUri; } private String newCode(final String clientId, final String redirectUri) { final String newCode = UUID.randomUUID().toString(); pendingCodes.put(getKey(clientId, redirectUri), newCode); return newCode; } }