Skip to content

Commit

Permalink
Added code flow authorization it test with wiremock stubbing
Browse files Browse the repository at this point in the history
Signed-off-by: Cem Nura <[email protected]>
  • Loading branch information
cemnura committed Feb 23, 2021
1 parent 800ef6e commit dfba954
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 19 deletions.
11 changes: 11 additions & 0 deletions integration-tests/oidc-wiremock/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@
<artifactId>jakarta.servlet-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Minimal test dependencies to *-deployment artifacts for consistent build order -->
<dependency>
<groupId>io.quarkus</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.quarkus.it.keycloak;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import io.quarkus.security.Authenticated;

@Path("/code-flow")
@Authenticated
public class CodeFlowResource {

@GET
public void access() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkus.it.keycloak;

import javax.enterprise.context.ApplicationScoped;

import io.quarkus.oidc.TenantResolver;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
public class CustomTenantResolver implements TenantResolver {

@Override
public String resolve(RoutingContext context) {
String path = context.normalisedPath();
if (path.endsWith("code-flow")) {
return "code-flow";
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Welcome to Test App</title>
</head>
<body>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.secret=secret
quarkus.oidc.token.principal-claim=email
quarkus.oidc.authentication.scopes=profile,email,phone

smallrye.jwt.sign.key-location=privateKey.jwk
quarkus.oidc.code-flow.auth-server-url=${keycloak.url}/realms/quarkus/
quarkus.oidc.code-flow.client-id=quarkus-web-app
quarkus.oidc.code-flow.credentials.secret=secret
quarkus.oidc.code-flow.application-type=web-app
quarkus.oidc.code-flow.authentication.redirect-path=/index.html

smallrye.jwt.sign.key-location=privateKey.jwk
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package io.quarkus.it.keycloak;

import com.gargoylesoftware.htmlunit.SilentCssErrorHandler;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.util.Cookie;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.net.URI;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

@QuarkusTest
@QuarkusTestResource(KeycloakTestResource.class)
public class CodeFlowAuthorizationTest {

@Test
public void testCodeFlow() throws IOException {
try (final WebClient webClient = createWebClient()) {
webClient.getOptions().setRedirectEnabled(false);
WebResponse webResponse = webClient
.loadWebResponse(new WebRequest(URI.create("http://localhost:8081/code-flow").toURL()));
verifyLocationHeader(webClient, webResponse.getResponseHeaderValue("location"), "code-flow", "index.html", false);

webClient.getOptions().setRedirectEnabled(true);
HtmlPage page = webClient.getPage("http://localhost:8081/code-flow");

HtmlForm form = page.getFormByName("form");
form.getInputByName("username").type("alice");
form.getInputByName("password").type("alice");

page = form.getInputByValue("login").click();

assertEquals("Welcome to Test App", page.getTitleText());
}
}

private WebClient createWebClient() {
WebClient webClient = new WebClient();
webClient.setCssErrorHandler(new SilentCssErrorHandler());
return webClient;
}

private void verifyLocationHeader(WebClient webClient, String loc, String tenant, String path, boolean forceHttps) {
assertTrue(loc.contains("/auth"));
String scheme = forceHttps ? "https" : "http";
assertTrue(loc.contains("redirect_uri=" + scheme + "%3A%2F%2Flocalhost%3A8081%2F" + path));
assertTrue(loc.contains("state=" + getStateCookieStateParam(webClient, tenant)));
assertTrue(loc.contains("scope=openid"));
assertTrue(loc.contains("response_type=code"));
assertTrue(loc.contains("client_id=quarkus-web-app"));
}

private Cookie getStateCookie(WebClient webClient, String tenantId) {
return webClient.getCookieManager().getCookie("q_auth" + (tenantId == null ? "" : "_" + tenantId));
}

private String getStateCookieStateParam(WebClient webClient, String tenantId) {
return getStateCookie(webClient, tenantId).getValue().split("\\|")[0];
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
package io.quarkus.it.keycloak;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer;
import com.google.common.collect.ImmutableSet;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
import org.jboss.logging.Logger;

import javax.ws.rs.core.MediaType;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.matching;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.joining;

import java.util.Collections;
import java.util.Map;
import java.util.Set;

import javax.ws.rs.core.MediaType;

import org.jboss.logging.Logger;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.google.common.collect.ImmutableSet;

import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;

public class KeycloakTestResource implements QuarkusTestResourceLifecycleManager {

private static final Logger LOG = Logger.getLogger(KeycloakTestResource.class);
Expand All @@ -31,7 +30,10 @@ public class KeycloakTestResource implements QuarkusTestResourceLifecycleManager
@Override
public Map<String, String> start() {

server = new WireMockServer(wireMockConfig().dynamicPort());
server = new WireMockServer(
wireMockConfig()
.extensions(new ResponseTemplateTransformer(false))
.dynamicPort());
server.start();

server.stubFor(
Expand All @@ -42,7 +44,8 @@ public Map<String, String> start() {
" \"jwks_uri\": \"" + server.baseUrl()
+ "/auth/realms/quarkus/protocol/openid-connect/certs\",\n" +
" \"token_introspection_endpoint\": \"" + server.baseUrl()
+ "/auth/realms/quarkus/protocol/openid-connect/token/introspect\"\n" +
+ "/auth/realms/quarkus/protocol/openid-connect/token/introspect\",\n" +
" \"authorization_endpoint\": \"" + server.baseUrl() + "/auth/realms/quarkus\"" +
"}")));

server.stubFor(
Expand Down Expand Up @@ -71,8 +74,42 @@ public Map<String, String> start() {
// Invalid
defineInvalidIntrospectionMockTokenStubForUserWithRoles("expired", emptySet());

// Login Page
server.stubFor(
get(urlPathMatching("/auth/realms/quarkus"))
.willReturn(aResponse()
.withHeader("Content-Type", MediaType.TEXT_HTML)
.withBody("<html>\n" +
"<body>\n" +
" <form action=\"/login\" name=\"form\">\n" +
" <input type=\"text\" id=\"username\" name=\"username\"/>\n" +
" <input type=\"password\" id=\"password\" name=\"password\"/>\n" +
" <input type=\"hidden\" id=\"state\" name=\"state\" value=\"{{request.query.state}}\"/>\n"
+
" <input type=\"hidden\" id=\"redirect_uri\" name=\"redirect_uri\" value=\"{{request.query.redirect_uri}}\"/>\n"
+
" <input type=\"submit\" id=\"login\" value=\"login\"/>\n" +
"</form>\n" +
"</body>\n" +
"</html> ")
.withTransformers("response-template")));

// Login Request
server.stubFor(
get(urlPathMatching("/login"))
.willReturn(aResponse()
.withHeader("Location", "{{request.query.redirect_uri}}")
.withHeader("state", "{{request.query.state}}")
.withStatus(302)
.withTransformers("response-template")));

LOG.infof("Keycloak started in mock mode: %s", server.baseUrl());
return Collections.singletonMap("quarkus.oidc.auth-server-url", server.baseUrl() + "/auth/realms/quarkus");
Map<String, String> conf = new HashMap<>();
conf.put("quarkus.oidc.auth-server-url", server.baseUrl() + "/auth/realms/quarkus");
conf.put("quarkus.oidc.code-flow.auth-server-url", server.baseUrl() + "/auth/realms/quarkus");
conf.put("keycloak-url", server.baseUrl());

return conf;
}

private void defineValidIntrospectionMockTokenStubForUserWithRoles(String user, Set<String> roles) {
Expand Down

0 comments on commit dfba954

Please sign in to comment.