Skip to content

Commit

Permalink
Pac4jModule: a way to omit /logout and /callback
Browse files Browse the repository at this point in the history
 - turn off by default or force them with Pac4jOptions
 - fix #3328
  • Loading branch information
Edgar Espina committed Feb 25, 2024
1 parent c8cdbc2 commit 7c45e8e
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 11 deletions.
25 changes: 14 additions & 11 deletions modules/jooby-pac4j/src/main/java/io/jooby/pac4j/Pac4jModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.pac4j.core.authorization.authorizer.Authorizer;
import org.pac4j.core.client.Client;
import org.pac4j.core.client.Clients;
import org.pac4j.core.client.DirectClient;
import org.pac4j.core.config.Config;
import org.pac4j.core.engine.CallbackLogic;
import org.pac4j.core.engine.DefaultCallbackLogic;
Expand Down Expand Up @@ -429,7 +430,8 @@ public void install(@NonNull Jooby application) throws Exception {
clients.setCallbackUrl(
ofNullable(clients.getCallbackUrl()).orElse(contextPath + options.getCallbackPath()));
/** Default URL resolver if none was set at client level: */
clients.setUrlResolver(ofNullable(clients.getUrlResolver()).orElseGet(() -> newUrlResolver()));
clients.setUrlResolver(
ofNullable(clients.getUrlResolver()).orElseGet(Pac4jModule::newUrlResolver));

/** Set resolved clients: */
clients.setClients(
Expand All @@ -442,10 +444,7 @@ public void install(@NonNull Jooby application) throws Exception {

/** Delay setting unresolved clients: */
List<ClientReference> unresolved =
allClients.values().stream()
.flatMap(List::stream)
.filter(r -> !r.isResolved())
.collect(Collectors.toList());
allClients.values().stream().flatMap(List::stream).filter(r -> !r.isResolved()).toList();

if (!unresolved.isEmpty()) {
application.onStarted(
Expand Down Expand Up @@ -497,10 +496,12 @@ public void install(@NonNull Jooby application) throws Exception {
if (callbackLogic == null) {
pac4j.setCallbackLogic(newCallbackLogic(excludes));
}

CallbackFilterImpl callbackFilter = new CallbackFilterImpl(pac4j, options);
application.get(options.getCallbackPath(), callbackFilter);
application.post(options.getCallbackPath(), callbackFilter);
var direct = clients.getClients().stream().allMatch(it -> it instanceof DirectClient);
if (!direct || options.isForceCallbackRoutes()) {
CallbackFilterImpl callbackFilter = new CallbackFilterImpl(pac4j, options);
application.get(options.getCallbackPath(), callbackFilter);
application.post(options.getCallbackPath(), callbackFilter);
}

SecurityLogic securityLogic = pac4j.getSecurityLogic();
if (securityLogic == null) {
Expand Down Expand Up @@ -535,7 +536,7 @@ public void install(@NonNull Jooby application) throws Exception {
}
}

/** Is there is a global client, use it as decorator/filter (default client): */
/* Is there is a global client, use it as decorator/filter (default client): */
List<ClientReference> defaultSecurityFilter = allClients.get("*");
if (defaultSecurityFilter != null) {
if (options.getDefaultClient() == null && defaultSecurityFilter.get(0).isResolved()) {
Expand All @@ -555,7 +556,9 @@ public void install(@NonNull Jooby application) throws Exception {
if (logoutLogic == null) {
pac4j.setLogoutLogic(newLogoutLogic());
}
application.get(options.getLogoutPath(), new LogoutImpl(pac4j, options));
if (!direct || options.isForceLogoutRoutes()) {
application.get(options.getLogoutPath(), new LogoutImpl(pac4j, options));
}

/** Better response code for some errors. */
application.errorCode(UnauthorizedAction.class, StatusCode.UNAUTHORIZED);
Expand Down
36 changes: 36 additions & 0 deletions modules/jooby-pac4j/src/main/java/io/jooby/pac4j/Pac4jOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ public class Pac4jOptions {

private SameSite cookieSameSite;

private boolean forceCallbackRoutes = false;

private boolean forceLogoutRoutes = false;

/**
* Default url to redirect to after successful login. Used by {@link
* org.pac4j.core.engine.CallbackLogic}.
Expand Down Expand Up @@ -268,4 +272,36 @@ public boolean isDestroySession() {
cookieSameSite = sameSite;
return this;
}

/**
* The <code>/callback</code> routes are off when pac4j is configured with {@link
* org.pac4j.core.client.DirectClient} clients only. These routes are not required for direct
* clients. Setting this to <code>true</code> will still add the <code>/callback</code> routes.
*
* @return When the callback route is available.
*/
public boolean isForceCallbackRoutes() {
return forceCallbackRoutes;
}

/**
* The <code>/logout</code> routes are off when pac4j is configured with {@link
* org.pac4j.core.client.DirectClient} clients only. These routes are not required for direct
* clients. Setting this to <code>true</code> will still add the <code>/logout</code> routes.
*
* @return When the logout route is available.
*/
public boolean isForceLogoutRoutes() {
return forceLogoutRoutes;
}

public @NonNull Pac4jOptions setForceCallbackRoutes(boolean forceCallbackRoutes) {
this.forceCallbackRoutes = forceCallbackRoutes;
return this;
}

public @NonNull Pac4jOptions setForceLogoutRoutes(boolean forceLogoutRoutes) {
this.forceLogoutRoutes = forceLogoutRoutes;
return this;
}
}
64 changes: 64 additions & 0 deletions tests/src/test/java/io/jooby/test/Issue3328.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Jooby https://jooby.io
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
* Copyright 2014 Edgar Espina
*/
package io.jooby.test;

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

import java.util.UUID;

import org.pac4j.core.credentials.TokenCredentials;
import org.pac4j.core.profile.BasicUserProfile;
import org.pac4j.http.client.direct.HeaderClient;

import io.jooby.junit.ServerTest;
import io.jooby.junit.ServerTestRunner;
import io.jooby.pac4j.Pac4jModule;
import io.jooby.pac4j.Pac4jOptions;
import okhttp3.Response;

public class Issue3328 {

@ServerTest
public void pac4jShouldWorkWithSignedSession(ServerTestRunner runner) {
runner
.define(
app -> {
app.install(
new Pac4jModule(new Pac4jOptions())
.client(
conf ->
new HeaderClient(
"user-id",
(credentials, context, sessionStore) -> {
var profile = new BasicUserProfile();
profile.setId(((TokenCredentials) credentials).getToken());
credentials.setUserProfile(profile);
})));

app.get("/i3328", ctx -> ((BasicUserProfile) ctx.getUser()).getId());
})
.ready(
http -> {
var userid = UUID.randomUUID().toString();
http.header("user-id", userid)
.get(
"/i3328",
rsp -> {
assertEquals(userid, rsp.body().string());
});
});
}

private String cleanCookie(Response response) {
String value =
response.headers("Set-Cookie").stream()
.filter(it -> it.startsWith("Test="))
.findFirst()
.get();
int i = value.indexOf(";Path");
return i > 0 ? value.substring(0, i) : value;
}
}

0 comments on commit 7c45e8e

Please sign in to comment.