-
Notifications
You must be signed in to change notification settings - Fork 0
/
Jaspic.java
385 lines (323 loc) · 16.9 KB
/
Jaspic.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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
/*
* Copyright 2013 OmniFaces.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.omnifaces.security.jaspic.core;
import org.omnifaces.security.jaspic.config.AuthStacks;
import org.omnifaces.security.jaspic.config.AuthStacksBuilder;
import org.omnifaces.security.jaspic.factory.OmniAuthConfigProvider;
import javax.faces.context.FacesContext;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.callback.CallerPrincipalCallback;
import javax.security.auth.message.callback.GroupPrincipalCallback;
import javax.security.auth.message.config.AuthConfigFactory;
import javax.security.auth.message.module.ServerAuthModule;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import static java.lang.Boolean.TRUE;
import static javax.security.auth.message.AuthStatus.SUCCESS;
import static org.omnifaces.security.jaspic.Utils.isEmpty;
import static org.omnifaces.security.jaspic.Utils.isOneOf;
import static org.omnifaces.security.jaspic.config.ControlFlag.REQUIRED;
/**
* A set of utility methods for using the JASPIC API, specially in combination with
* the OmniServerAuthModule.
* <p/>
* Note that this contains various methods that assume being called from a JSF context.
*
* @author Arjan Tijms
*/
public final class Jaspic {
public static final String IS_AUTHENTICATION = "org.omnifaces.javax.security.message.request.authentication";
public static final String IS_AUTHENTICATION_FROM_FILTER = "org.omnifaces.javax.security.message.request.authenticationFromFilter";
public static final String IS_SECURE_RESPONSE = "org.omnifaces.javax.security.message.request.secureResponse";
public static final String IS_REFRESH = "org.omnifaces.javax.security.message.request.isRefresh";
public static final String DID_AUTHENTICATION = "org.omnifaces.javax.security.message.request.didAuthentication";
public static final String AUTH_PARAMS = "org.omnifaces.javax.security.message.request.authParams";
public static final String LOGGEDIN_USERNAME = "org.omnifaces.javax.security.message.loggedin.username";
public static final String LOGGEDIN_ROLES = "org.omnifaces.javax.security.message.loggedin.roles";
public static final String LAST_AUTH_STATUS = "org.omnifaces.javax.security.message.authStatus";
public static final String CONTEXT_REGISTRATION_ID = "org.omnifaces.javax.security.message.registrationId";
// Key in the MessageInfo Map that when present AND set to true indicated a protected resource is being accessed.
// When the resource is not protected, GlassFish omits the key altogether. WebSphere does insert the key and sets
// it to false.
private static final String IS_MANDATORY = "javax.javax.security.auth.message.MessagePolicy.isMandatory";
private static final String REGISTER_SESSION = "javax.servlet.http.registerSession";
private Jaspic() {
}
public static boolean authenticate(HttpServletRequest request, HttpServletResponse response, AuthParameters authParameters) {
try {
request.setAttribute(IS_AUTHENTICATION, true);
if (authParameters != null) {
request.setAttribute(AUTH_PARAMS, authParameters);
}
return request.authenticate(response);
} catch (ServletException | IOException e) {
throw new IllegalArgumentException(e);
} finally {
request.removeAttribute(IS_AUTHENTICATION);
if (authParameters != null) {
request.removeAttribute(AUTH_PARAMS);
}
}
}
public static boolean authenticateFromFilter(HttpServletRequest request, HttpServletResponse response) {
try {
request.setAttribute(IS_AUTHENTICATION_FROM_FILTER, true);
return request.authenticate(response);
} catch (ServletException e) {
// Really problematic case, some servers (particularly JBoss Undertow since 1.1.0) throw a
// ServletException when there's isn't actually an error, but just to indicate "nothing" has happened.
return false;
} catch (IOException e) {
throw new IllegalArgumentException(e);
} finally {
request.removeAttribute(IS_AUTHENTICATION_FROM_FILTER);
}
}
public static boolean refreshAuthentication(HttpServletRequest request, HttpServletResponse response, AuthParameters authParameters) {
try {
request.setAttribute(IS_REFRESH, true);
// Doing an explicit logout is actually not really nice, as it has some side-effects that we need to counter
// (like a SAM supporting remember-me clearing its remember-me cookie, etc). But there doesn't seem to be another
// way in JASPIC
request.logout();
return authenticate(request, response, authParameters);
} catch (ServletException e) {
throw new IllegalArgumentException(e);
} finally {
request.removeAttribute(IS_REFRESH);
}
}
public static AuthParameters getAuthParameters(HttpServletRequest request) {
AuthParameters authParameters = (AuthParameters) request.getAttribute(AUTH_PARAMS);
if (authParameters == null) {
authParameters = new AuthParameters();
}
return authParameters;
}
public static void logout(HttpServletRequest request, HttpServletResponse response) {
try {
request.logout();
// Need to invalidate the session to really logout - request.logout only logs the user out for the *current request*
// This is nearly always unwanted. Although the SAM's cleanSubject method can clear any session data too if needed,
// invalidating the session is pretty much the safest way.
request.getSession().invalidate();
} catch (ServletException e) {
throw new IllegalArgumentException(e);
}
}
public static AuthResult validateRequest(ServerAuthModule serverAuthModule, MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) {
AuthResult authResult = new AuthResult();
try {
AuthStatus status = serverAuthModule.validateRequest(messageInfo, clientSubject, serviceSubject);
// TODO: not 100% sure about this; need mechanism for wrappers to abort the chain and signal to "do nothing"
// TODO: use handler for "do nothing here"?
if (status == null) {
status = SUCCESS;
}
authResult.setAuthStatus(status);
} catch (Exception exception) {
authResult.setException(exception);
}
return authResult;
}
public static void cleanSubject(Subject subject) {
if (subject != null) {
subject.getPrincipals().clear();
}
}
public static boolean isRegisterSession(MessageInfo messageInfo) {
return Boolean.valueOf((String) messageInfo.getMap().get(REGISTER_SESSION));
}
public static boolean isProtectedResource(MessageInfo messageInfo) {
return Boolean.valueOf((String) messageInfo.getMap().get(IS_MANDATORY));
}
@SuppressWarnings("unchecked")
public static void setRegisterSession(MessageInfo messageInfo, String username, List<String> roles) {
messageInfo.getMap().put("javax.servlet.http.registerSession", TRUE.toString());
HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();
request.setAttribute(LOGGEDIN_USERNAME, username);
// TODO: check for existing roles and add
request.setAttribute(LOGGEDIN_ROLES, roles);
}
public static boolean isAuthenticationRequest(HttpServletRequest request) {
return TRUE.equals(request.getAttribute(IS_AUTHENTICATION));
}
public static boolean isAuthenticationFromFilterRequest(HttpServletRequest request) {
return TRUE.equals(request.getAttribute(IS_AUTHENTICATION_FROM_FILTER));
}
public static boolean isSecureResponseRequest(HttpServletRequest request) {
return TRUE.equals(request.getAttribute(IS_SECURE_RESPONSE));
}
public static boolean isRefresh(HttpServletRequest request) {
return TRUE.equals(request.getAttribute(IS_REFRESH));
}
/**
* Returns true if authorization was explicitly called for via this class (e.g. by calling {@link Jaspic#authenticate()},
* false if authorization was called automatically by the runtime at the start of the request or directly via e.g.
* {@link HttpServletRequest#authenticate(HttpServletResponse)}
*
* @param request
* @return true if authorization was initiated via this class, false otherwise
*/
public static boolean isExplicitAuthCall(HttpServletRequest request) {
return isOneOf(TRUE,
request.getAttribute(IS_AUTHENTICATION),
request.getAttribute(IS_AUTHENTICATION_FROM_FILTER),
request.getAttribute(IS_SECURE_RESPONSE)
);
}
public static void notifyContainerAboutLogin(Subject clientSubject, CallbackHandler handler, String username, List<String> roles) {
try {
// 1. Create a handler (kind of directive) to add the caller principal (AKA user principal =basically user name, or user id) that
// the authenticator provides.
//
// This will be the name of the principal returned by e.g. HttpServletRequest#getUserPrincipal
//
// 2 Execute the handler right away
//
// This will typically eventually (NOT right away) add the provided principal in an application server specific way to the JAAS
// Subject.
// (it could become entries in a hash table inside the subject, or individual principles, or nested group principles etc.)
handler.handle(new Callback[]{new CallerPrincipalCallback(clientSubject, username)});
if (!isEmpty(roles)) {
// 1. Create a handler to add the groups (AKA roles) that the authenticator provides.
//
// This is what e.g. HttpServletRequest#isUserInRole and @RolesAllowed for
//
// 2. Execute the handler right away
//
// This will typically eventually (NOT right away) add the provided roles in an application server specific way to the JAAS
// Subject.
// (it could become entries in a hash table inside the subject, or individual principles, or nested group principles etc.)
handler.handle(new Callback[]{new GroupPrincipalCallback(clientSubject, roles.toArray(new String[roles.size()]))});
}
} catch (IOException | UnsupportedCallbackException e) {
// Should not happen
throw new IllegalStateException(e);
}
}
public static void setLastStatus(HttpServletRequest request, AuthStatus status) {
request.setAttribute(LAST_AUTH_STATUS, status);
}
public static AuthStatus getLastStatus(HttpServletRequest request) {
return (AuthStatus) request.getAttribute(LAST_AUTH_STATUS);
}
/**
* Should be called when the callback handler is used with the intention that an actual
* user is going to be authenticated (as opposed to using the handler for the "do nothing" protocol
* which uses the unauthenticated identity).
*/
public static void setDidAuthentication(HttpServletRequest request) {
request.setAttribute(DID_AUTHENTICATION, TRUE);
}
/**
* Returns true if a SAM has indicated that it intended authentication to be happening during
* the current request.
* Does not necessarily mean that authentication has indeed succeeded, for this
* the actual user/caller principal should be checked as well.
*/
public static boolean isDidAuthentication(HttpServletRequest request) {
return TRUE.equals(request.getAttribute(DID_AUTHENTICATION));
}
public static boolean isDidAuthenticationAndSucceeded(HttpServletRequest request) {
return TRUE.equals(request.getAttribute(DID_AUTHENTICATION)) && request.getUserPrincipal() != null;
}
/**
* Gets the app context ID from the servlet context.
* <p/>
* <p/>
* The app context ID is the ID that JASPIC associates with the given application.
* In this case that given application is the web application corresponding to the
* ServletContext.
*
* @param context the servlet context for which to obtain the JASPIC app context ID
* @return the app context ID for the web application corresponding to the given context
*/
public static String getAppContextID(ServletContext context) {
return context.getVirtualServerName() + " " + context.getContextPath();
}
/**
* Registers a server auth module as the one and only module for the application corresponding to
* the given servlet context.
* <p/>
* <p/>
* This will override any other modules that have already been registered, either via proprietary
* means or using the standard API.
*
* @param serverAuthModule the server auth module to be registered
* @param servletContext the context of the app for which the module is registered
* @return A String identifier assigned by an underlying factory corresponding to an underlying factory-factory-factory registration
*/
public static String registerServerAuthModule(ServerAuthModule serverAuthModule, ServletContext servletContext) {
AuthStacks stacks = new AuthStacksBuilder()
.stack()
.name(serverAuthModule.getClass().getSimpleName())
.setDefault()
.module()
.serverAuthModule(serverAuthModule)
.controlFlag(REQUIRED)
.add()
.add()
.build();
// Register the factory-factory-factory for the SAM
String registrationId = AuthConfigFactory.getFactory().registerConfigProvider(
new OmniAuthConfigProvider(stacks),
"HttpServlet", getAppContextID(servletContext), "OmniSecurity authentication config provider"
);
// Remember the registration ID returned by the factory, so we can unregister the JASPIC module when the web module
// is undeployed. JASPIC being the low level API that it is won't do this automatically.
servletContext.setAttribute(CONTEXT_REGISTRATION_ID, registrationId);
return registrationId;
}
/**
* Deregisters the server auth module (and encompassing wrappers/factories) that was previously registered via a call
* to registerServerAuthModule.
*
* @param servletContext the context of the app for which the module is deregistered
*/
public static void deregisterServerAuthModule(ServletContext servletContext) {
String registrationId = (String) servletContext.getAttribute(CONTEXT_REGISTRATION_ID);
if (!isEmpty(registrationId)) {
AuthConfigFactory.getFactory().removeRegistration(registrationId);
}
}
// Couple of convenience methods for usage in JSF - may remove these as its too tightly coupled
public static boolean authenticate() {
return authenticate(getRequest(), getResponse(), null);
}
public static boolean authenticate(AuthParameters authParameters) {
return authenticate(getRequest(), getResponse(), authParameters);
}
public static boolean refreshAuthentication(AuthParameters authParameters) {
return refreshAuthentication(getRequest(), getResponse(), authParameters);
}
public static void logout() {
logout(getRequest(), getResponse());
}
// End Couple of convenience methods for usage in JSF - may remove these as its too tightly coupled
public static HttpServletRequest getRequest() {
return (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
}
public static HttpServletResponse getResponse() {
return (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
}
}