Skip to content

Commit

Permalink
Add Back channel logout test with quarkus.http.root-path
Browse files Browse the repository at this point in the history
  • Loading branch information
sberyozkin committed Sep 4, 2024
1 parent f92fc56 commit c8a7a4f
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,13 @@ class RouteHandler implements Handler<RoutingContext> {
@Override
public void handle(RoutingContext context) {
LOG.debugf("Back channel logout request for the tenant %s received", oidcTenantConfig.getTenantId().get());
final TenantConfigContext tenantContext = getTenantConfigContext(context);
final String requestPath = context.request().path();
final TenantConfigContext tenantContext = getTenantConfigContext(requestPath);
if (tenantContext == null) {
LOG.errorf(
"Tenant configuration for the tenant %s is not available or does not match the backchannel logout path",
oidcTenantConfig.getTenantId().get());
"Tenant configuration for the tenant %s is not available "
+ "or does not match the backchannel logout path %s",
oidcTenantConfig.getTenantId().get(), requestPath);
context.response().setStatusCode(400);
context.response().end();
return;
Expand Down Expand Up @@ -147,8 +149,7 @@ private boolean verifyLogoutTokenClaims(TokenVerificationResult result) {
return true;
}

private TenantConfigContext getTenantConfigContext(RoutingContext context) {
String requestPath = context.request().path();
private TenantConfigContext getTenantConfigContext(final String requestPath) {
if (isMatchingTenant(requestPath, resolver.getTenantConfigBean().getDefaultTenant())) {
return resolver.getTenantConfigBean().getDefaultTenant();
}
Expand Down
7 changes: 7 additions & 0 deletions integration-tests/oidc-wiremock-logout/error.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
1. HTTP root: /service, backchannel-path: back-channel-logout, no http-root at the reg time

Vert.x fails to start a route with no trailing slash

2. HTTP root: /service, backchannel-path: /back-channel-logout, no http-root at the reg time

2024-09-04 18:02:34,697 ERROR [io.qua.oid.run.BackChannelLogoutHandler] (vert.x-eventloop-thread-2) Tenant configuration for the tenant code-flow-form-post is not available or does not match the backchannel logout path /service/service/back-channel-logout
106 changes: 106 additions & 0 deletions integration-tests/oidc-wiremock-logout/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-integration-tests-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-integration-test-oidc-wiremock-logout</artifactId>
<name>Quarkus - Integration Tests - OpenID Connect Adapter WireMock Logout</name>
<description>Module that contains OpenID Connect logout related tests using WireMock</description>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>

<!-- test dependencies -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-oidc-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.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>
<artifactId>quarkus-oidc-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
</plugin>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>generate-code</goal>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>


</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.it.keycloak;

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

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

@Path("/code-flow-form-post")
public class CodeFlowFormPostResource {

@Inject
SecurityIdentity identity;

@GET
@Authenticated
public String access() {
return identity.getPrincipal().getName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

syntax = "proto3";

option java_multiple_files = true;
option java_package = "examples";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

// The greeting service definition.
service Greeter {
// Sends a greeting
// Name is 'Bearer' in order to match tenant name
rpc bearer (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings
message HelloReply {
string message = 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
quarkus.keycloak.devservices.enabled=false

quarkus.oidc.code-flow-form-post.auth-server-url=${keycloak.url}/realms/quarkus-form-post/
quarkus.oidc.code-flow-form-post.client-id=quarkus-web-app
quarkus.oidc.code-flow-form-post.credentials.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow
quarkus.oidc.code-flow-form-post.application-type=web-app
quarkus.oidc.code-flow-form-post.authentication.response-mode=form_post
quarkus.oidc.code-flow-form-post.discovery-enabled=false
# redirect the user to ${keycloak.url}/realms/quarkus-form-post/ which will respond with form post data
quarkus.oidc.code-flow-form-post.authorization-path=/
# reuse the wiremock access token stub for the `quarkus` realm - it is the same for the query and form post response mode
quarkus.oidc.code-flow-form-post.token-path=${keycloak.url}/realms/quarkus/token
# reuse the wiremock JWK endpoint stub for the `quarkus` realm - it is the same for the query and form post response mode
quarkus.oidc.code-flow-form-post.jwks-path=${keycloak.url}/realms/quarkus/protocol/openid-connect/certs
quarkus.oidc.code-flow-form-post.logout.backchannel.path=/back-channel-logout
quarkus.oidc.code-flow-form-post.token.audience=https://server.example.com,https://id.server.example.com

quarkus.http.auth.permission.backchannellogout.paths=/service/back-channel-logout
quarkus.http.auth.permission.backchannellogout.policy=permit

quarkus.native.additional-build-args=-H:IncludeResources=private.*\\.*,-H:IncludeResources=.*\\.p12

quarkus.http.root-path=/service
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"kty":"RSA",
"kid":"1",
"n":"iJw33l1eVAsGoRlSyo-FCimeOc-AaZbzQ2iESA3Nkuo3TFb1zIkmt0kzlnWVGt48dkaIl13Vdefh9hqw_r9yNF8xZqX1fp0PnCWc5M_TX_ht5fm9y0TpbiVmsjeRMWZn4jr3DsFouxQ9aBXUJiu26V0vd2vrECeeAreFT4mtoHY13D2WVeJvboc5mEJcp50JNhxRCJ5UkY8jR_wfUk2Tzz4-fAj5xQaBccXnqJMu_1C6MjoCEiB7G1d13bVPReIeAGRKVJIF6ogoCN8JbrOhc_48lT4uyjbgnd24beatuKWodmWYhactFobRGYo5551cgMe8BoxpVQ4to30cGA0qjQ",
"e":"AQAB",
"d":"AvIDTlsK_priQLTwEQf5IVf2Xl638Q7dHdXyDC-oAAPmv1GcqRVH7Wm5oAPW_CZQfWhV55WRVaJzP8AhksyD5NcslH79hQZT4NT6xgApGYecrvmseuZ4dfR-e1cxXTRNBxaoXvwSiv4LuOPHmC8XGX712AhOoCGKiZp1WFqqkKwTpkgJEApJFVb-XRIKQa0YaRKpJsJ534pLMwTh7LoPLM4BCaBVbRfHzH2H5L3TSJP718kyCuxg3z2p9Y7zIOLTmgFdeR0_kd_xKUFZ2ByN3SKlC0IWlLUSiMPsGYExRpZTMZHKyD939gv-2_Z-bOYfKlYNIvAmQH_8CcX2I039LQ",
"p":"104AjPaxZoi_BiMBODlChnZOvRJT071PdkeZ283uyrdW8qqKD9q8FTMgUXzKoboHtUiHbJbLOobPmPDh93839rq7dTdCNzNVOuLmE-V3_bmaShdzvxEIazwPf6AvjbEZAc-zu2RS4SNkp1LbzgSl9nINSlF7t6Lkl6T28PYULys",
"q":"om5ooyzxa4ZJ-dU0ODsEb-Bmz6xwb27xF9aEhBYJprHeoNs2QM1D64_A39weD9MYwBux4-ivshCJ0dVKEbDujJRLnzf-ssrasA6CFyaaCT4DKtq1oWb9rcG-2LQd5Bm9PttrUrSUNqitr085IYikaLEz7UU6gtXPoC8UOcJ4cSc",
"dp":"DeWE95Q8oweUfMrpmz1m49LjBiUWsAX6CQJaFevWy9LFk-gZ_Sf7F8sy_M93LLUbJkJGK2YYO_DTmWWC0Dyv2gb3bntglLuFdsWKYCJhekjugnW9DMoGpxU7Utt99kFGAe3sBd5V0x47sukQMt3t8FgwL2nO-G1VH8yP-8GGT_0",
"dq":"TGBeE1wuqMCcSD1YMJiPnYuGzF_o_nzMIMldxj4Wi6tXY4uwFwhtx3Xw21JFUGuSV8KuAtyGwNPF-kSwb2Eiyjdw140c1jVMXzxzLy-XfoEKPDxa62niHrHba0pGQ9tWgRfrfxgqGQl3odc-peX6aL_qCsdim-KtnkSE3iPzPkE",
"qi":"Jzp5KnT24y0wOoPUn_11S3ZcYl0i03dkaH4c5zR02G1MJG9K017juurx2aXVTctOzrj7O226EUiL1Qbq3QtnWFDDGY6vNZuqzJM7AMXsvp1djq_6fEVhxCIOgfJbmhb3mkG82rxn4et9o_TNr6mvEmHzG15sHbvZbAnn4GeqToY"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.quarkus.it.keycloak;

import io.quarkus.test.junit.QuarkusIntegrationTest;

@QuarkusIntegrationTest
public class CodeFlowAuthorizationInGraalITCase extends CodeFlowAuthorizationTest {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package io.quarkus.it.keycloak;

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

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

import org.htmlunit.SilentCssErrorHandler;
import org.htmlunit.TextPage;
import org.htmlunit.WebClient;
import org.htmlunit.WebRequest;
import org.htmlunit.WebResponse;
import org.htmlunit.html.HtmlForm;
import org.htmlunit.html.HtmlPage;
import org.htmlunit.util.Cookie;
import org.junit.jupiter.api.Test;

import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.oidc.server.OidcWiremockTestResource;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;

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

@Test
public void testCodeFlowFormPostAndBackChannelLogout() throws IOException {
try (final WebClient webClient = createWebClient()) {
webClient.getOptions().setRedirectEnabled(true);
HtmlPage page = webClient.getPage("http://localhost:8081/service/code-flow-form-post");

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

TextPage textPage = form.getInputByValue("login").click();

assertEquals("alice", textPage.getContent());

assertNotNull(getSessionCookie(webClient, "code-flow-form-post"));

textPage = webClient.getPage("http://localhost:8081/service/code-flow-form-post");
assertEquals("alice", textPage.getContent());

// Session is still active
assertNotNull(getSessionCookie(webClient, "code-flow-form-post"));

// ID token subject is `123456`
// request a back channel logout for some other subject
RestAssured.given()
.when().contentType(ContentType.URLENC)
.body("logout_token=" + OidcWiremockTestResource.getLogoutToken("789"))
.post("http://localhost:8081/service/back-channel-logout")
.then()
.statusCode(200);

// No logout:
textPage = webClient.getPage("http://localhost:8081/service/code-flow-form-post");
assertEquals("alice", textPage.getContent());
// Session is still active
assertNotNull(getSessionCookie(webClient, "code-flow-form-post"));

// request a back channel logout for the same subject
RestAssured.given()
.when().contentType(ContentType.URLENC).body("logout_token="
+ OidcWiremockTestResource.getLogoutToken("123456"))
.post("http://localhost:8081/service/back-channel-logout")
.then()
.statusCode(200);

// Confirm 302 is returned and the session cookie is null
webClient.getOptions().setRedirectEnabled(false);
WebResponse webResponse = webClient
.loadWebResponse(new WebRequest(URI.create("http://localhost:8081/service/code-flow-form-post").toURL()));
assertEquals(302, webResponse.getStatusCode());

assertNull(getSessionCookie(webClient, "code-flow-form-post"));

webClient.getCookieManager().clearCookies();
}
}

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

private Cookie getSessionCookie(WebClient webClient, String tenantId) {
return webClient.getCookieManager().getCookie("q_session" + (tenantId == null ? "" : "_" + tenantId));
}
}
1 change: 1 addition & 0 deletions integration-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@
<module>oidc-code-flow</module>
<module>oidc-tenancy</module>
<module>oidc-wiremock</module>
<module>oidc-wiremock-logout</module>
<module>keycloak-authorization</module>
<module>rest-csrf</module>
<module>reactive-db2-client</module>
Expand Down

0 comments on commit c8a7a4f

Please sign in to comment.