forked from jooby-project/jooby
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCsrfHandler.java
More file actions
158 lines (142 loc) · 4.36 KB
/
CsrfHandler.java
File metadata and controls
158 lines (142 loc) · 4.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/**
* Jooby https://jooby.io
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
* Copyright 2014 Edgar Espina
*/
package io.jooby;
import io.jooby.exception.InvalidCsrfToken;
import javax.annotation.Nonnull;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* <h1>Cross Site Request Forgery handler</h1>
*
* <pre>
* {
* before(new CsrfHandler());
* }
* </pre>
*
* <p>
* This filter require a token on <code>POST</code>, <code>PUT</code>, <code>PATCH</code> and
* <code>DELETE</code> requests. A custom policy might be provided via:
* {@link #setRequestFilter(Predicate)}.
* </p>
*
* <p>
* Default token generator, use a {@link UUID#randomUUID()}. A custom token generator might be
* provided via: {@link #setTokenGenerator(Function)}.
* </p>
*
* <p>
* Default token name is: <code>csrf</code>. If you want to use a different name, just pass the name
* to the {@link #CsrfHandler(String)} constructor.
* </p>
*
* <h2>Token verification</h2>
* <p>
* The {@link CsrfHandler} handler will read an existing token from {@link Session} (or created a
* new one is necessary) and make available as a request local variable via:
* {@link Context#attribute(String, Object)}.
* </p>
*
* <p>
* If the incoming request require a token verification, it will extract the token from:
* </p>
* <ol>
* <li>HTTP header</li>
* <li>HTTP cookie</li>
* <li>HTTP parameter (query or form)</li>
* </ol>
*
* <p>
* If the extracted token doesn't match the existing token (from {@link Session}) a <code>403</code>
* will be thrown.
* </p>
*
* @author edgar
* @since 2.5.2
*/
public class CsrfHandler implements Route.Before {
/**
* Default request filter. Requires an existing session and only check for POST, DELETE, PUT and
* PATCH methods.
*/
public static final Predicate<Context> DEFAULT_FILTER = ctx -> {
return Router.POST.equals(ctx.getMethod())
|| Router.DELETE.equals(ctx.getMethod())
|| Router.PATCH.equals(ctx.getMethod())
|| Router.PUT.equals(ctx.getMethod());
};
/**
* UUID token generator.
*/
public static final Function<Context, String> DEFAULT_GENERATOR = ctx -> UUID.randomUUID()
.toString();
private String name;
private Function<Context, String> generator = DEFAULT_GENERATOR;
private Predicate<Context> filter = DEFAULT_FILTER;
/**
* Creates a new {@link CsrfHandler} handler and use the given name to save the token in the
* {@link Session} and or extract the token from incoming requests.
*
* @param name Token's name.
*/
public CsrfHandler(String name) {
this.name = name;
}
/**
* Creates a new {@link CsrfHandler} handler and use the given name to save the token in the
* {@link Session} and or extract the token from incoming requests.
*/
public CsrfHandler() {
this("csrf");
}
@Override public void apply(@Nonnull Context ctx) throws Exception {
Session session = ctx.session();
String token = session.get(name).toOptional().orElseGet(() -> {
String newToken = generator.apply(ctx);
session.put(name, newToken);
return newToken;
});
ctx.attribute(name, token);
if (filter.test(ctx)) {
String clientToken = Stream.of(
ctx.header(name).valueOrNull(),
ctx.cookie(name).valueOrNull(),
ctx.form(name).valueOrNull(),
ctx.query(name).valueOrNull()
).filter(Objects::nonNull)
.findFirst()
.orElse(null);
if (!token.equals(clientToken)) {
throw new InvalidCsrfToken(clientToken);
}
}
}
/**
* Set a custom token generator. Default generator use: {@link UUID#randomUUID()}.
*
* @param generator A custom token generator.
* @return This filter.
*/
public @Nonnull CsrfHandler setTokenGenerator(@Nonnull Function<Context, String> generator) {
this.generator = generator;
return this;
}
/**
* Decided whenever or not an incoming request require token verification. Default predicate
* requires verification on: <code>POST</code>, <code>PUT</code>, <code>PATCH</code> and
* <code>DELETE</code> requests.
*
* @param filter Predicate to use.
* @return This filter.
*/
public @Nonnull CsrfHandler setRequestFilter(@Nonnull Predicate<Context> filter) {
this.filter = filter;
return this;
}
}