-
Notifications
You must be signed in to change notification settings - Fork 683
/
Secure.java
211 lines (186 loc) · 7.02 KB
/
Secure.java
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
package controllers;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Date;
import play.Play;
import play.mvc.*;
import play.data.validation.*;
import play.libs.*;
import play.utils.*;
public class Secure extends Controller {
@Before(unless={"login", "authenticate", "logout"})
static void checkAccess() throws Throwable {
// Authent
if(!session.contains("username")) {
flash.put("url", "GET".equals(request.method) ? request.url : Play.ctxPath + "/"); // seems a good default
login();
}
// Checks
Check check = getActionAnnotation(Check.class);
if(check != null) {
check(check);
}
check = getControllerInheritedAnnotation(Check.class);
if(check != null) {
check(check);
}
}
private static void check(Check check) throws Throwable {
for(String profile : check.value()) {
boolean hasProfile = (Boolean)Security.invoke("check", profile);
if(!hasProfile) {
Security.invoke("onCheckFailed", profile);
}
}
}
// ~~~ Login
public static void login() throws Throwable {
Http.Cookie remember = request.cookies.get("rememberme");
if(remember != null) {
int firstIndex = remember.value.indexOf("-");
int lastIndex = remember.value.lastIndexOf("-");
if (lastIndex > firstIndex) {
String sign = remember.value.substring(0, firstIndex);
String restOfCookie = remember.value.substring(firstIndex + 1);
String username = remember.value.substring(firstIndex + 1, lastIndex);
String time = remember.value.substring(lastIndex + 1);
Date expirationDate = new Date(Long.parseLong(time)); // surround with try/catch?
Date now = new Date();
if (expirationDate == null || expirationDate.before(now)) {
logout();
}
if(Crypto.sign(restOfCookie).equals(sign)) {
session.put("username", username);
redirectToOriginalURL();
}
}
}
flash.keep("url");
render();
}
public static void authenticate(@Required String username, String password, boolean remember) throws Throwable {
// Check tokens
Boolean allowed = false;
try {
// This is the deprecated method name
allowed = (Boolean)Security.invoke("authentify", username, password);
} catch (UnsupportedOperationException e ) {
// This is the official method name
allowed = (Boolean)Security.invoke("authenticate", username, password);
}
if(validation.hasErrors() || !allowed) {
flash.keep("url");
flash.error("secure.error");
params.flash();
login();
}
// Mark user as connected
session.put("username", username);
// Remember if needed
if(remember) {
Date expiration = new Date();
String duration = "30d"; // maybe make this override-able
expiration.setTime(expiration.getTime() + Time.parseDuration(duration));
response.setCookie("rememberme", Crypto.sign(username + "-" + expiration.getTime()) + "-" + username + "-" + expiration.getTime(), duration);
}
// Redirect to the original URL (or /)
redirectToOriginalURL();
}
public static void logout() throws Throwable {
Security.invoke("onDisconnect");
session.clear();
response.removeCookie("rememberme");
Security.invoke("onDisconnected");
flash.success("secure.logout");
login();
}
// ~~~ Utils
static void redirectToOriginalURL() throws Throwable {
Security.invoke("onAuthenticated");
String url = flash.get("url");
if(url == null) {
url = Play.ctxPath + "/";
}
redirect(url);
}
public static class Security extends Controller {
/**
* @Deprecated
*
* @param username
* @param password
* @return
*/
static boolean authentify(String username, String password) {
throw new UnsupportedOperationException();
}
/**
* This method is called during the authentication process. This is where you check if
* the user is allowed to log in into the system. This is the actual authentication process
* against a third party system (most of the time a DB).
*
* @param username
* @param password
* @return true if the authentication process succeeded
*/
static boolean authenticate(String username, String password) {
return true;
}
/**
* This method checks that a profile is allowed to view this page/method. This method is called prior
* to the method's controller annotated with the @Check method.
*
* @param profile
* @return true if you are allowed to execute this controller method.
*/
static boolean check(String profile) {
return true;
}
/**
* This method returns the current connected username
* @return
*/
static String connected() {
return session.get("username");
}
/**
* Indicate if a user is currently connected
* @return true if the user is connected
*/
static boolean isConnected() {
return session.contains("username");
}
/**
* This method is called after a successful authentication.
* You need to override this method if you with to perform specific actions (eg. Record the time the user signed in)
*/
static void onAuthenticated() {
}
/**
* This method is called before a user tries to sign off.
* You need to override this method if you wish to perform specific actions (eg. Record the name of the user who signed off)
*/
static void onDisconnect() {
}
/**
* This method is called after a successful sign off.
* You need to override this method if you wish to perform specific actions (eg. Record the time the user signed off)
*/
static void onDisconnected() {
}
/**
* This method is called if a check does not succeed. By default it shows the not allowed page (the controller forbidden method).
* @param profile
*/
static void onCheckFailed(String profile) {
forbidden();
}
private static Object invoke(String m, Object... args) throws Throwable {
try {
return Java.invokeChildOrStatic(Security.class, m, args);
} catch(InvocationTargetException e) {
throw e.getTargetException();
}
}
}
}