Skip to content

Commit 95528e0

Browse files
committed
Pac4j module implementation
1 parent 316f406 commit 95528e0

35 files changed

+1152
-137
lines changed

TODO

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
* map exception names to HTTP Status Code (Forbidden => 403, Unauthorized => 401, etc...)
12
* review toString on Value API (removal of QueryStringValue?)
23
* tests and coverage
3-
* make reset headers on error configurable
44

55
Jetty close exception:
66
MvcTest#mvcDispatch

examples/pom.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,23 @@
5656
<artifactId>jooby-netty</artifactId>
5757
<version>${jooby.version}</version>
5858
</dependency>
59+
<dependency>
60+
<groupId>io.jooby</groupId>
61+
<artifactId>jooby-pac4j</artifactId>
62+
<version>${jooby.version}</version>
63+
</dependency>
64+
<!-- https://mvnrepository.com/artifact/org.pac4j/pac4j-oauth -->
65+
<dependency>
66+
<groupId>org.pac4j</groupId>
67+
<artifactId>pac4j-oauth</artifactId>
68+
<version>${pac4j.version}</version>
69+
</dependency>
70+
<!-- https://mvnrepository.com/artifact/org.pac4j/pac4j-oidc -->
71+
<dependency>
72+
<groupId>org.pac4j</groupId>
73+
<artifactId>pac4j-oidc</artifactId>
74+
<version>${pac4j.version}</version>
75+
</dependency>
5976
<dependency>
6077
<groupId>io.jooby</groupId>
6178
<artifactId>jooby-hikari</artifactId>
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package examples;
7+
8+
import io.jooby.Context;
9+
import io.jooby.Jooby;
10+
import io.jooby.ModelAndView;
11+
import io.jooby.Session;
12+
import io.jooby.handlebars.HandlebarsModule;
13+
import io.jooby.pac4j.Pac4jContext;
14+
import io.jooby.json.JacksonModule;
15+
import io.jooby.pac4j.Pac4jModule;
16+
import org.pac4j.core.context.Pac4jConstants;
17+
import org.pac4j.core.exception.http.WithLocationAction;
18+
import org.pac4j.http.client.indirect.FormClient;
19+
import org.pac4j.http.credentials.authenticator.test.SimpleTestUsernamePasswordAuthenticator;
20+
import org.pac4j.oauth.client.FacebookClient;
21+
import org.pac4j.oauth.client.TwitterClient;
22+
import org.pac4j.oidc.client.OidcClient;
23+
import org.pac4j.oidc.config.OidcConfiguration;
24+
25+
import java.util.Optional;
26+
27+
public class Pac4jApp extends Jooby {
28+
29+
{
30+
final FacebookClient facebookClient = new FacebookClient("145278422258960",
31+
"be21409ba8f39b5dae2a7de525484da8");
32+
TwitterClient twitterClient = new TwitterClient("CoxUiYwQOSFDReZYdjigBA",
33+
"2kAzunH5Btc4gRSaMr7D7MkyoJ5u1VzbOOzE8rBofs");
34+
final OidcConfiguration oidcConfiguration = new OidcConfiguration();
35+
oidcConfiguration
36+
.setClientId("343992089165-sp0l1km383i8cbm2j5nn20kbk5dk8hor.apps.googleusercontent.com");
37+
oidcConfiguration.setSecret("uR3D8ej1kIRPbqAFaxIE3HWh");
38+
oidcConfiguration
39+
.setDiscoveryURI("https://accounts.google.com/.well-known/openid-configuration");
40+
oidcConfiguration.setUseNonce(true);
41+
//oidcClient.setPreferredJwsAlgorithm(JWSAlgorithm.RS256);
42+
oidcConfiguration.addCustomParam("prompt", "consent");
43+
final OidcClient oidcClient = new OidcClient(oidcConfiguration);
44+
45+
decorator(next -> ctx -> {
46+
try {
47+
getLog().info("{} {} sid: {} {}", ctx.getMethod(), ctx.getRequestURL(),
48+
ctx.cookie("jooby.sid").toOptional().orElse(""), requestedUrl(ctx));
49+
return next.apply(ctx);
50+
} finally {
51+
getLog().info(" {} {} {} sid: {} {}", ctx.getMethod(), ctx.getRequestURL(),
52+
ctx.getResponseCode(),
53+
Optional.ofNullable(ctx.sessionOrNull()).map(Session::getId).orElse(""), requestedUrl(ctx));
54+
}
55+
});
56+
57+
// setContextPath("/myapp");
58+
59+
install(new HandlebarsModule());
60+
61+
get("/login", ctx -> new ModelAndView("login.hbs"));
62+
63+
install(new Pac4jModule()
64+
// .client("/google", conf -> oidcClient)
65+
// .client("/twitter", conf -> twitterClient)
66+
// .client(conf -> new FormClient("/login", new SimpleTestUsernamePasswordAuthenticator()))
67+
);
68+
69+
install(new JacksonModule());
70+
71+
get("/", ctx -> new ModelAndView("pac4j.hbs").put("user", ctx.getUser()));
72+
73+
get("/api", ctx -> ctx.getUser());
74+
75+
get("/api/v1", ctx -> ctx.getUser());
76+
}
77+
78+
private Object requestedUrl(Context ctx) {
79+
Pac4jContext pac4jContext = Pac4jContext.create(ctx);
80+
return pac4jContext.getSessionStore().get(pac4jContext, Pac4jConstants.REQUESTED_URL).map(it-> {
81+
if (it instanceof WithLocationAction) {
82+
return ((WithLocationAction) it).getLocation();
83+
} else {
84+
return it.toString();
85+
}
86+
}).orElse("null");
87+
}
88+
89+
public static void main(String[] args) {
90+
runApp(args, Pac4jApp::new);
91+
}
92+
}

examples/src/main/resources/logback.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
</encoder>
77
</appender>
88

9+
<logger name="org.pac4j" level="DEBUG" />
10+
911
<root level="INFO">
1012
<appender-ref ref="STDOUT"/>
1113
</root>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Login Page</title>
5+
<script type="text/javascript">
6+
7+
function submitForm() {
8+
document.form.submit();
9+
}
10+
11+
function onKeyPressEvent(event) {
12+
var key = event.keyCode || event.which;
13+
if (key === 13) {
14+
if (event.preventDefault) {
15+
event.preventDefault();
16+
} else {
17+
event.returnValue = false;
18+
}
19+
20+
submitForm('');
21+
return false;
22+
}
23+
}
24+
</script>
25+
</head>
26+
<body onload="document.form.username.focus();">
27+
<h3>Login</h3>
28+
<p style="color: red;">
29+
{{error}}
30+
</p>
31+
<label>Login with username and password:</label>
32+
<form name="form" action="/callback?client_name=FormClient" method="POST">
33+
<input name="username" onkeypress="onKeyPressEvent(event)" value="{{username}}" />
34+
<p></p>
35+
<input type="password" name="password" onkeypress="onKeyPressEvent(event)" />
36+
<p></p>
37+
<input type="submit" value="Submit" />
38+
</form>
39+
<label>Or login using:</label>
40+
<ul>
41+
<li><a href="/google">Google</a></li>
42+
<li><a href="/twitter">Twitter</a></li>
43+
</ul>
44+
</body>
45+
</html>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Welcome {{user}}</title>
5+
</head>
6+
<body>
7+
<h3>Welcome</h3>
8+
<p>{{user.id}} {{user.name}}</p>
9+
<a href="/logout">Logout</a>
10+
</body>
11+
</html>

jooby/src/main/java/io/jooby/AssetHandler.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,10 @@ private Asset resolve(String filepath) {
169169
return null;
170170
}
171171

172-
@Override public Route.Handler setRoute(Route route) {
172+
@Override public void setRoute(Route route) {
173173
List<String> keys = route.getPathKeys();
174174
this.filekey = keys.size() == 0 ? route.getPattern().substring(1) : keys.get(0);
175175
// NOTE: It send an inputstream we don't need a renderer
176176
route.setReturnType(Context.class);
177-
return this;
178177
}
179178
}

jooby/src/main/java/io/jooby/Context.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,13 +398,19 @@ public interface Context extends Registry {
398398
*/
399399
long getRequestLength();
400400

401+
@Nullable <T> T getUser();
402+
403+
@Nonnull Context setUser(@Nullable Object user);
404+
401405
/**
402406
* Recreates full/entire request url using the <code>Host</code> header.
403407
*
404408
* @return Full/entire request url using the <code>Host</code> header.
405409
*/
406410
@Nonnull String getRequestURL();
407411

412+
@Nonnull String getRequestURL(@Nonnull String path);
413+
408414
/**
409415
* Recreates full/entire request url using the <code>X-Forwarded-Host</code> when present
410416
* or fallback to <code>Host</code> header when missing.
@@ -415,6 +421,8 @@ public interface Context extends Registry {
415421
*/
416422
@Nonnull String getRequestURL(boolean useProxy);
417423

424+
@Nonnull String getRequestURL(@Nonnull String path, boolean useProxy);
425+
418426
/**
419427
* The IP address of the client or last proxy that sent the request.
420428
*

jooby/src/main/java/io/jooby/CorsHandler.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,8 @@ private static void simple(final Context ctx, final Cors options, final String o
134134
}
135135
}
136136

137-
@Nonnull @Override public Route.Decorator setRoute(@Nonnull Route route) {
137+
@Nonnull @Override public void setRoute(@Nonnull Route route) {
138138
route.setHttpOptions(true);
139-
return this;
140139
}
141140

142141
private boolean isPreflight(final Context ctx) {

jooby/src/main/java/io/jooby/DefaultContext.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ public interface DefaultContext extends Context {
5050
return getRouter().require(key);
5151
}
5252

53+
@Nullable @Override default <T> T getUser() {
54+
return (T) getAttributes().get("user");
55+
}
56+
57+
@Nonnull @Override default Context setUser(@Nullable Object user) {
58+
getAttributes().put("user", user);
59+
return this;
60+
}
61+
5362
/**
5463
* Get an attribute by his key. This is just an utility method around {@link #getAttributes()}.
5564
* This method look first in current context and fallback to application attributes.
@@ -200,7 +209,15 @@ public interface DefaultContext extends Context {
200209
return getRequestURL(false);
201210
}
202211

212+
@Override default @Nonnull String getRequestURL(@Nonnull String path) {
213+
return getRequestURL(path, false);
214+
}
215+
203216
@Override default @Nonnull String getRequestURL(boolean useProxy) {
217+
return getRequestURL("", useProxy);
218+
}
219+
220+
@Override default @Nonnull String getRequestURL(@Nonnull String path, boolean useProxy) {
204221
String scheme, hostAndPort;
205222
if (useProxy && !header("X-Forwarded-Host").isMissing()) {
206223
scheme = header("X-Forwarded-Proto").value(getScheme());
@@ -223,7 +240,19 @@ public interface DefaultContext extends Context {
223240
if (port.length() > 0 && !port.equals("80") && !port.equals("443")) {
224241
url.append(":").append(port);
225242
}
226-
url.append(pathString());
243+
if (path == null || path.length() == 0) {
244+
url.append(pathString());
245+
} else {
246+
String contextPath = getContextPath();
247+
if (contextPath.equals("/")) {
248+
url.append(path);
249+
} else {
250+
if (!path.startsWith(contextPath)) {
251+
url.append(contextPath);
252+
}
253+
url.append(path);
254+
}
255+
}
227256
url.append(queryString());
228257

229258
return url.toString();

0 commit comments

Comments
 (0)