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]>

Added code & grant_type to login success redirect

Signed-off-by: Cem Nura <[email protected]>

Added token mock for code flow authentication

Signed-off-by: Cem Nura <[email protected]>

Corrected token mock for code flow authentication

Signed-off-by: Cem Nura <[email protected]>
  • Loading branch information
cemnura committed Mar 11, 2021
1 parent ba778a7 commit a972c78
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 21 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 @@ -48,6 +48,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,21 @@
package io.quarkus.it.keycloak;

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

import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;

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

@Inject
SecurityIdentity identity;

@GET
public String access() {
return identity.getPrincipal().getName();
}
}
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
Expand Up @@ -2,7 +2,14 @@
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

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.log.category."io.quarkus.oidc.runtime.CodeAuthenticationMechanism".min-level=TRACE
quarkus.log.category."io.quarkus.oidc.runtime.CodeAuthenticationMechanism".level=TRACE

quarkus.oidc.token.audience=https://service.example.com

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.quarkus.it.keycloak;

import com.gargoylesoftware.htmlunit.SilentCssErrorHandler;
import com.gargoylesoftware.htmlunit.WebClient;
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 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(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,28 @@
package io.quarkus.it.keycloak;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.common.ConsoleNotifier;
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.containing;
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 +32,11 @@ public class KeycloakTestResource implements QuarkusTestResourceLifecycleManager
@Override
public Map<String, String> start() {

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

server.stubFor(
Expand All @@ -41,10 +46,13 @@ public Map<String, String> start() {
.withBody("{\n" +
" \"jwks_uri\": \"" + server.baseUrl()
+ "/auth/realms/quarkus/protocol/openid-connect/certs\",\n" +
" \"introspection_endpoint\": \"" + server.baseUrl()
+ "/auth/realms/quarkus/protocol/openid-connect/token/introspect\",\n"
+ "\"issuer\" : \"https://server.example.com\""
+ "}")));
" \"token_introspection_endpoint\": \"" + server.baseUrl()
+ "/auth/realms/quarkus/protocol/openid-connect/token/introspect\",\n" +
" \"authorization_endpoint\": \"" + server.baseUrl() + "/auth/realms/quarkus\"," +
" \"token_endpoint\": \"" + server.baseUrl() + "/auth/realms/quarkus/token\"," +
" \"issuer\" : \"https://server.example.com\"" +
" \"introspection_endpoint\": \"" + server.baseUrl() +
"}")));

server.stubFor(
get(urlEqualTo("/auth/realms/quarkus/protocol/openid-connect/certs"))
Expand Down Expand Up @@ -72,8 +80,45 @@ public Map<String, String> start() {
// Invalid
defineInvalidIntrospectionMockTokenStubForUserWithRoles("expired", emptySet());

// Code Flow Authorization Mock
defineCodeFlowAuthorizationMockTokenStub();

// 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}}?state={{request.query.state}}&code=58af24f2-9093-4674-a431-4a9d66be719c.50437113-cd78-48a2-838e-b936fe458c5d.0ac5df91-e044-4051-bd03-106a3a5fb9cc")
.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 All @@ -100,6 +145,20 @@ private void defineInvalidIntrospectionMockTokenStubForUserWithRoles(String user
+ "\",\"iat\":1562315654,\"exp\":1,\"expires_in\":1,\"client_id\":\"my_client_id\"}")));
}

private void defineCodeFlowAuthorizationMockTokenStub() {
server.stubFor(WireMock.post("/auth/realms/quarkus/token")
.withRequestBody(containing("authorization_code"))
.willReturn(WireMock.aResponse()
.withHeader("Content-Type", MediaType.APPLICATION_JSON)
.withBody("{\n" +
" \"access_token\": \"eyJraWQiOiIxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhbGljZSIsImdyb3VwcyI6WyJhZG1pbiIsInVzZXIiXSwiaWF0IjoxNjE1NDIzMDgxLCJleHAiOjE2MTU0MjMzODEsImp0aSI6IjIwMTgyNTMxLTQ5ZWUtNDI2OC04YjM5LWQwN2Q5NDQ1OGYyMSJ9.OvSV8ZVY5cUj7nmqJRmlMz6S--wqedm3Fnz8lMPGUkeyAUbPm8_N6aQi4VgduSPbCrH-2-Kgppoh4w1Oy203kVaG_-KV5PgS7Yesrq6SDp3g9I3WL6UYXdM1nRSoDDGYxhWCdJ1TMwhjV8a7HV_M1PtbLfaR_7Do8_BDLhmc_lSWt_ZLiUqweXf-RJw7hm8aNuZ_OaYhiDd_tII6Ldn5CJfXpWf1Sm8x09cieNzvGDxxP9Dn7FSSMX0I9JPsCCPV6pvmrPDNej2bonZPrHG1QU6ChmO6WSwTAyNE0Cj9z_n7UxltI6AAhk8DlGo18voqogasg4uEgp1FhUdmWenaNQ\",\n"
+
" \"refresh_token\": \"07e08903-1263-4dd1-9fd1-4a59b0db5283\",\n" +
" \"id_token\": \"eyJraWQiOiIxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhbGljZSIsImdyb3VwcyI6WyJhZG1pbiIsInVzZXIiXSwiaWF0IjoxNjE1NDIzMDgxLCJleHAiOjE2MTU0MjMzODEsImp0aSI6IjIwMTgyNTMxLTQ5ZWUtNDI2OC04YjM5LWQwN2Q5NDQ1OGYyMSJ9.OvSV8ZVY5cUj7nmqJRmlMz6S--wqedm3Fnz8lMPGUkeyAUbPm8_N6aQi4VgduSPbCrH-2-Kgppoh4w1Oy203kVaG_-KV5PgS7Yesrq6SDp3g9I3WL6UYXdM1nRSoDDGYxhWCdJ1TMwhjV8a7HV_M1PtbLfaR_7Do8_BDLhmc_lSWt_ZLiUqweXf-RJw7hm8aNuZ_OaYhiDd_tII6Ldn5CJfXpWf1Sm8x09cieNzvGDxxP9Dn7FSSMX0I9JPsCCPV6pvmrPDNej2bonZPrHG1QU6ChmO6WSwTAyNE0Cj9z_n7UxltI6AAhk8DlGo18voqogasg4uEgp1FhUdmWenaNQ\"\n"
+
"}")));
}

@Override
public synchronized void stop() {
if (server != null) {
Expand Down

0 comments on commit a972c78

Please sign in to comment.