๐ (OIDC): Disable RP-Initiated Logout for kanidm, add local POST /logout
Changes
6 files changed, +79 -4
MODIFY
README.md
+1 -1
@@ -15,7 +15,7 @@
15
15
- Clone/fetch/push over `ssh://git@<host>:2222/<owner>/<repo>.git`
16
16
- public-key authentication only; keys managed per user in the UI
17
17
- Web UI: landing page with login CTA for visitors (`/`), repository list for authenticated users (`/`), public repository browse at `/explore`, file/tree browser, commit log (paginated), branches & tags
18
-- OIDC login (authorization code flow) via `GET /login`; users provisioned on first login
18
+- OIDC login (authorization code flow) via `GET /login`; users provisioned on first login. Logout is local-session only via `POST /logout` (the kanidm provider advertises no `end_session_endpoint`, so RP-Initiated Logout is disabled)
19
19
- Single access policy on all paths: owner read/write, public world-readable, private owner-only
20
20
- **Federation (ForgeFed / ActivityPub)** โ *opt-in, off by default.* Public repositories are
21
21
exposed as ForgeFed `Repository` actors that remote instances can follow and receive `Push`
ADD
src/main/java/de/workaround/account/LogoutResource.java
+31 -0
@@ -0,0 +1,31 @@
1
+package de.workaround.account;
2
+
3
+import java.net.URI;
4
+
5
+import io.quarkus.oidc.OidcSession;
6
+import io.smallrye.mutiny.Uni;
7
+import jakarta.inject.Inject;
8
+import jakarta.ws.rs.Consumes;
9
+import jakarta.ws.rs.POST;
10
+import jakarta.ws.rs.Path;
11
+import jakarta.ws.rs.core.MediaType;
12
+import jakarta.ws.rs.core.Response;
13
+
14
+/**
15
+ * Local logout. kanidm advertises no end_session_endpoint, so Quarkus RP-Initiated Logout is disabled
16
+ * (no quarkus.oidc.logout.path). This endpoint clears the local OIDC session cookie and redirects home,
17
+ * which is the most logout the provider allows.
18
+ */
19
+@Path("/logout")
20
+public class LogoutResource
21
+{
22
+ @Inject
23
+ OidcSession oidcSession;
24
+
25
+ @POST
26
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
27
+ public Uni<Response> logout()
28
+ {
29
+ return oidcSession.logout().replaceWith(Response.seeOther(URI.create("/")).build());
30
+ }
31
+}
MODIFY
src/main/resources/application.properties
+3 -2
@@ -20,8 +20,9 @@
20
20
%dev,test.quarkus.oidc.client-id=quarkus-app
21
21
%dev,test.quarkus.oidc.credentials.secret=secret
22
22
quarkus.oidc.application-type=web-app
23
-quarkus.oidc.logout.path=/logout
24
-quarkus.oidc.logout.post-logout-path=/
23
+# No RP-Initiated Logout: kanidm does not advertise an end_session_endpoint, and setting
24
+# quarkus.oidc.logout.path makes Quarkus validate that endpoint at startup and abort boot when
25
+# it is missing. Logout is therefore local session only (handled by the app).
25
26
26
27
# PKCE: kanidm enforces PKCE for the authorization code flow. The code_verifier rides through the
27
28
# redirect inside the state cookie, which must be encrypted with a stable, explicitly-set secret so
MODIFY
src/main/resources/templates/layout.html
+1 -1
@@ -18,7 +18,7 @@
18
18
{#insert nav}
19
19
<a href="/settings/keys">SSH keys</a>
20
20
<a href="/settings/tokens">Access tokens</a>
21
- <a href="/logout">Logout</a>
21
+ <form class="logout" method="post" action="/logout"><button type="submit">Logout</button></form>
22
22
{/}
23
23
</nav>
24
24
</header>
ADD
src/test/java/de/workaround/account/LogoutResourceTest.java
+29 -0
@@ -0,0 +1,29 @@
1
+package de.workaround.account;
2
+
3
+import static io.restassured.RestAssured.given;
4
+import static org.hamcrest.Matchers.endsWith;
5
+import static org.hamcrest.Matchers.anyOf;
6
+import static org.hamcrest.Matchers.is;
7
+
8
+import org.junit.jupiter.api.Test;
9
+
10
+import io.quarkus.test.junit.QuarkusTest;
11
+
12
+/**
13
+ * Local logout: kanidm advertises no end_session_endpoint, so Quarkus RP-Initiated Logout is disabled.
14
+ * /logout must instead clear the local OIDC session and redirect home, so the template's Logout link works.
15
+ */
16
+@QuarkusTest
17
+class LogoutResourceTest
18
+{
19
+ @Test
20
+ void logoutClearsSessionAndRedirectsHome()
21
+ {
22
+ given()
23
+ .redirects().follow(false)
24
+ .when().post("/logout")
25
+ .then()
26
+ .statusCode(anyOf(is(302), is(303)))
27
+ .header("Location", endsWith("/"));
28
+ }
29
+}
MODIFY
src/test/java/de/workaround/account/OidcPkceConfigTest.java
+14 -0
@@ -29,4 +29,18 @@
29
29
.orElse("");
30
30
assertTrue(secret.length() >= 32, "state-secret must be >= 32 chars; was " + secret.length());
31
31
}
32
+
33
+ /**
34
+ * RP-Initiated Logout must NOT be configured: kanidm does not advertise an end_session_endpoint, and
35
+ * when quarkus.oidc.logout.path is set Quarkus validates that endpoint at startup and aborts boot if
36
+ * it is missing. Leaving logout.path unset keeps the app booting (local session logout only).
37
+ */
38
+ @Test
39
+ void rpInitiatedLogoutIsNotConfigured() {
40
+ String logoutPath = ConfigProvider.getConfig()
41
+ .getOptionalValue("quarkus.oidc.logout.path", String.class)
42
+ .orElse("");
43
+ assertTrue(logoutPath.isEmpty(),
44
+ "quarkus.oidc.logout.path must be unset (kanidm has no end_session_endpoint); was " + logoutPath);
45
+ }
32
46
}