Skip to content

Commit

Permalink
Merge pull request #14173 from cemnura/feature/oidc-wiremock-codeflow
Browse files Browse the repository at this point in the history
Added CodeFlowAuthorizationTest
  • Loading branch information
sberyozkin authored Mar 14, 2021
2 parents 57925bf + f987e6a commit 692cbdb
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 7 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,43 @@
package io.quarkus.it.keycloak;

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

import java.io.IOException;

import org.junit.jupiter.api.Test;

import com.gargoylesoftware.htmlunit.SilentCssErrorHandler;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;

import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;

@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("alice", page.getBody().asText());
}
}

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

}
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package io.quarkus.it.keycloak;

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.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

Expand All @@ -18,9 +22,11 @@

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 io.smallrye.jwt.build.Jwt;

public class KeycloakTestResource implements QuarkusTestResourceLifecycleManager {

Expand All @@ -31,7 +37,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 @@ -41,10 +50,15 @@ public Map<String, String> start() {
.withBody("{\n" +
" \"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" +
" \"authorization_endpoint\": \"" + server.baseUrl() + "/auth/realms/quarkus\"," +
" \"token_endpoint\": \"" + server.baseUrl() + "/auth/realms/quarkus/token\"," +
" \"issuer\" : \"https://server.example.com\"," +
" \"introspection_endpoint\": \"" + server.baseUrl()
+ "/auth/realms/quarkus/protocol/openid-connect/token/introspect\",\n"
+ "\"issuer\" : \"https://server.example.com\""
+ "}")));
+ "/auth/realms/quarkus/protocol/openid-connect/token/introspect\""
+
"}")));

server.stubFor(
get(urlEqualTo("/auth/realms/quarkus/protocol/openid-connect/certs"))
Expand Down Expand Up @@ -72,8 +86,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 +151,30 @@ 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\": \""
+ getAccessToken("alice", new HashSet<>(Arrays.asList("user", "admin"))) + "\",\n" +
" \"refresh_token\": \"07e08903-1263-4dd1-9fd1-4a59b0db5283\",\n" +
" \"id_token\": \"" + getAccessToken("alice", new HashSet<>(Arrays.asList("user", "admin")))
+ "\"\n" +
"}")));
}

private String getAccessToken(String userName, Set<String> groups) {
return Jwt.preferredUserName(userName)
.groups(groups)
.issuer("https://server.example.com")
.audience("https://service.example.com")
.jws()
.keyId("1")
.sign("privateKey.jwk");
}

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

0 comments on commit 692cbdb

Please sign in to comment.