1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package waffle.servlet;
25
26 import java.io.IOException;
27 import java.lang.reflect.InvocationTargetException;
28 import java.security.Principal;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Map;
34
35 import javax.security.auth.Subject;
36 import javax.servlet.Filter;
37 import javax.servlet.FilterChain;
38 import javax.servlet.FilterConfig;
39 import javax.servlet.ServletException;
40 import javax.servlet.ServletRequest;
41 import javax.servlet.ServletResponse;
42 import javax.servlet.http.HttpServletRequest;
43 import javax.servlet.http.HttpServletResponse;
44 import javax.servlet.http.HttpSession;
45
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 import waffle.servlet.spi.SecurityFilterProvider;
50 import waffle.servlet.spi.SecurityFilterProviderCollection;
51 import waffle.util.AuthorizationHeader;
52 import waffle.util.CorsPreFlightCheck;
53 import waffle.windows.auth.IWindowsAuthProvider;
54 import waffle.windows.auth.IWindowsIdentity;
55 import waffle.windows.auth.IWindowsImpersonationContext;
56 import waffle.windows.auth.PrincipalFormat;
57 import waffle.windows.auth.impl.WindowsAuthProviderImpl;
58
59
60
61
62 public class NegotiateSecurityFilter implements Filter {
63
64
65 private static final Logger LOGGER = LoggerFactory.getLogger(NegotiateSecurityFilter.class);
66
67
68 private static final String PRINCIPALSESSIONKEY = NegotiateSecurityFilter.class.getName() + ".PRINCIPAL";
69
70
71 private static Boolean windows;
72
73
74 private PrincipalFormat principalFormat = PrincipalFormat.FQN;
75
76
77 private PrincipalFormat roleFormat = PrincipalFormat.FQN;
78
79
80 private SecurityFilterProviderCollection providers;
81
82
83 private IWindowsAuthProvider auth;
84
85
86 private String[] excludePatterns;
87
88
89 private boolean allowGuestLogin = true;
90
91
92 private boolean impersonate;
93
94
95 private boolean excludeBearerAuthorization;
96
97
98 private boolean excludeCorsPreflight;
99
100
101 private boolean disableSSO;
102
103
104
105
106 public NegotiateSecurityFilter() {
107 NegotiateSecurityFilter.LOGGER.debug("[waffle.servlet.NegotiateSecurityFilter] loaded");
108 }
109
110 @Override
111 public void destroy() {
112 NegotiateSecurityFilter.LOGGER.info("[waffle.servlet.NegotiateSecurityFilter] stopped");
113 }
114
115 @Override
116 public void doFilter(final ServletRequest sreq, final ServletResponse sres, final FilterChain chain)
117 throws IOException, ServletException {
118
119 final HttpServletRequest request = (HttpServletRequest) sreq;
120 final HttpServletResponse response = (HttpServletResponse) sres;
121
122 NegotiateSecurityFilter.LOGGER.debug("{} {}, contentlength: {}", request.getMethod(), request.getRequestURI(),
123 Integer.valueOf(request.getContentLength()));
124
125
126 if (!NegotiateSecurityFilter.isWindows()) {
127 NegotiateSecurityFilter.LOGGER.debug("Running in a non windows environment, SSO skipped");
128 chain.doFilter(request, response);
129 return;
130 }
131
132
133 if (this.disableSSO) {
134 NegotiateSecurityFilter.LOGGER.debug("SSO is disabled, resuming filter chain");
135 chain.doFilter(request, response);
136 return;
137 }
138
139
140 if (request.getRequestURL() != null && this.excludePatterns != null) {
141 final String url = request.getRequestURL().toString();
142 for (final String pattern : this.excludePatterns) {
143 if (url.matches(pattern)) {
144 NegotiateSecurityFilter.LOGGER.info("Pattern :{} excluded URL:{}", url, pattern);
145 chain.doFilter(sreq, sres);
146 return;
147 }
148 }
149 }
150
151
152 if (this.excludeCorsPreflight && CorsPreFlightCheck.isPreflight(request)) {
153 NegotiateSecurityFilter.LOGGER.debug("[waffle.servlet.NegotiateSecurityFilter] CORS preflight");
154 chain.doFilter(sreq, sres);
155 return;
156 }
157
158 final AuthorizationHeader authorizationHeader = new AuthorizationHeader(request);
159
160
161 if (this.excludeBearerAuthorization && authorizationHeader.isBearerAuthorizationHeader()) {
162 NegotiateSecurityFilter.LOGGER.debug("[waffle.servlet.NegotiateSecurityFilter] Authorization: Bearer");
163 chain.doFilter(sreq, sres);
164 return;
165 }
166
167 if (this.doFilterPrincipal(request, response, chain)) {
168
169 return;
170 }
171
172
173 if (!authorizationHeader.isNull()) {
174
175
176 IWindowsIdentity windowsIdentity;
177 try {
178 windowsIdentity = this.providers.doFilter(request, response);
179 if (windowsIdentity == null) {
180 return;
181 }
182 } catch (final IOException e) {
183 NegotiateSecurityFilter.LOGGER.warn("error logging in user: {}", e.getMessage());
184 NegotiateSecurityFilter.LOGGER.trace("", e);
185 this.sendUnauthorized(response, true);
186 return;
187 }
188
189 IWindowsImpersonationContext ctx = null;
190 try {
191 if (!this.allowGuestLogin && windowsIdentity.isGuest()) {
192 NegotiateSecurityFilter.LOGGER.warn("guest login disabled: {}", windowsIdentity.getFqn());
193 this.sendUnauthorized(response, true);
194 return;
195 }
196
197 NegotiateSecurityFilter.LOGGER.debug("logged in user: {} ({})", windowsIdentity.getFqn(),
198 windowsIdentity.getSidString());
199
200 final HttpSession session = request.getSession(true);
201 if (session == null) {
202 throw new ServletException("Expected HttpSession");
203 }
204
205 Subject subject = (Subject) session.getAttribute("javax.security.auth.subject");
206 if (subject == null) {
207 subject = new Subject();
208 }
209
210 WindowsPrincipal windowsPrincipal;
211 if (this.impersonate) {
212 windowsPrincipal = new AutoDisposableWindowsPrincipal(windowsIdentity, this.principalFormat,
213 this.roleFormat);
214 } else {
215 windowsPrincipal = new WindowsPrincipal(windowsIdentity, this.principalFormat, this.roleFormat);
216 }
217
218 NegotiateSecurityFilter.LOGGER.debug("roles: {}", windowsPrincipal.getRolesString());
219 subject.getPrincipals().add(windowsPrincipal);
220 request.getSession(false).setAttribute("javax.security.auth.subject", subject);
221
222 NegotiateSecurityFilter.LOGGER.info("successfully logged in user: {}", windowsIdentity.getFqn());
223
224 request.getSession(false).setAttribute(NegotiateSecurityFilter.PRINCIPALSESSIONKEY, windowsPrincipal);
225
226 final NegotiateRequestWrapper requestWrapper = new NegotiateRequestWrapper(request, windowsPrincipal);
227
228 if (this.impersonate) {
229 NegotiateSecurityFilter.LOGGER.debug("impersonating user");
230 ctx = windowsIdentity.impersonate();
231 }
232
233 chain.doFilter(requestWrapper, response);
234 } finally {
235 if (this.impersonate && ctx != null) {
236 NegotiateSecurityFilter.LOGGER.debug("terminating impersonation");
237 ctx.revertToSelf();
238 } else {
239 windowsIdentity.dispose();
240 }
241 }
242
243 return;
244 }
245
246 NegotiateSecurityFilter.LOGGER.debug("authorization required");
247 this.sendUnauthorized(response, false);
248 }
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267 private boolean doFilterPrincipal(final HttpServletRequest request, final HttpServletResponse response,
268 final FilterChain chain) throws IOException, ServletException {
269 Principal principal = request.getUserPrincipal();
270 if (principal == null) {
271 final HttpSession session = request.getSession(false);
272 if (session != null) {
273 principal = (Principal) session.getAttribute(NegotiateSecurityFilter.PRINCIPALSESSIONKEY);
274 }
275 }
276
277 if (principal == null) {
278
279 return false;
280 }
281
282 if (this.providers.isPrincipalException(request)) {
283
284 return false;
285 }
286
287
288 if (principal instanceof WindowsPrincipal) {
289 NegotiateSecurityFilter.LOGGER.debug("previously authenticated Windows user: {}", principal.getName());
290 final WindowsPrincipal windowsPrincipal = (WindowsPrincipal) principal;
291
292 if (this.impersonate && windowsPrincipal.getIdentity() == null) {
293
294
295
296 return false;
297 }
298
299 final NegotiateRequestWrapper requestWrapper = new NegotiateRequestWrapper(request, windowsPrincipal);
300
301 IWindowsImpersonationContext ctx = null;
302 if (this.impersonate) {
303 NegotiateSecurityFilter.LOGGER.debug("re-impersonating user");
304 ctx = windowsPrincipal.getIdentity().impersonate();
305 }
306 try {
307 chain.doFilter(requestWrapper, response);
308 } finally {
309 if (this.impersonate && ctx != null) {
310 NegotiateSecurityFilter.LOGGER.debug("terminating impersonation");
311 ctx.revertToSelf();
312 }
313 }
314 } else {
315 NegotiateSecurityFilter.LOGGER.debug("previously authenticated user: {}", principal.getName());
316 chain.doFilter(request, response);
317 }
318 return true;
319 }
320
321 @Override
322 public void init(final FilterConfig filterConfig) throws ServletException {
323 final Map<String, String> implParameters = new HashMap<>();
324
325 NegotiateSecurityFilter.LOGGER.debug("[waffle.servlet.NegotiateSecurityFilter] starting");
326
327 String authProvider = null;
328 String[] providerNames = null;
329 if (filterConfig != null) {
330 final List<String> parameterNames = Collections.list(filterConfig.getInitParameterNames());
331 NegotiateSecurityFilter.LOGGER.debug("[waffle.servlet.NegotiateSecurityFilter] processing filterConfig");
332 for (String parameterName : parameterNames) {
333 final String parameterValue = filterConfig.getInitParameter(parameterName);
334 NegotiateSecurityFilter.LOGGER.debug("Init Param: '{}={}'", parameterName, parameterValue);
335 switch (parameterName) {
336 case "principalFormat":
337 this.principalFormat = PrincipalFormat.valueOf(parameterValue.toUpperCase(Locale.ENGLISH));
338 break;
339 case "roleFormat":
340 this.roleFormat = PrincipalFormat.valueOf(parameterValue.toUpperCase(Locale.ENGLISH));
341 break;
342 case "allowGuestLogin":
343 this.allowGuestLogin = Boolean.parseBoolean(parameterValue);
344 break;
345 case "impersonate":
346 this.impersonate = Boolean.parseBoolean(parameterValue);
347 break;
348 case "securityFilterProviders":
349 providerNames = parameterValue.split("\\s+", -1);
350 break;
351 case "authProvider":
352 authProvider = parameterValue;
353 break;
354 case "excludePatterns":
355 this.excludePatterns = parameterValue.split("\\s+", -1);
356 break;
357 case "excludeCorsPreflight":
358 this.excludeCorsPreflight = Boolean.parseBoolean(parameterValue);
359 break;
360 case "excludeBearerAuthorization":
361 this.excludeBearerAuthorization = Boolean.parseBoolean(parameterValue);
362 break;
363 case "disableSSO":
364 this.disableSSO = Boolean.parseBoolean(parameterValue);
365 break;
366 default:
367 implParameters.put(parameterName, parameterValue);
368 break;
369 }
370 }
371 }
372
373 NegotiateSecurityFilter.LOGGER.debug("[waffle.servlet.NegotiateSecurityFilter] authProvider");
374 if (authProvider != null) {
375 try {
376 this.auth = (IWindowsAuthProvider) Class.forName(authProvider).getConstructor().newInstance();
377 } catch (final ClassNotFoundException | IllegalArgumentException | SecurityException
378 | InstantiationException | IllegalAccessException | InvocationTargetException
379 | NoSuchMethodException e) {
380 throw new ServletException(e);
381 }
382 }
383
384 if (this.auth == null) {
385 this.auth = new WindowsAuthProviderImpl();
386 }
387
388 if (providerNames != null) {
389 this.providers = new SecurityFilterProviderCollection(providerNames, this.auth);
390 }
391
392
393 if (this.providers == null) {
394 NegotiateSecurityFilter.LOGGER.debug("initializing default security filter providers");
395 this.providers = new SecurityFilterProviderCollection(this.auth);
396 }
397
398
399 NegotiateSecurityFilter.LOGGER.debug("[waffle.servlet.NegotiateSecurityFilter] load provider parameters");
400 for (final Map.Entry<String, String> implParameter : implParameters.entrySet()) {
401 final String[] classAndParameter = implParameter.getKey().split("/", 2);
402 if (classAndParameter.length == 2) {
403 try {
404
405 NegotiateSecurityFilter.LOGGER.debug("setting {}, {}={}", classAndParameter[0],
406 classAndParameter[1], implParameter.getValue());
407
408 final SecurityFilterProvider provider = this.providers.getByClassName(classAndParameter[0]);
409 provider.initParameter(classAndParameter[1], implParameter.getValue());
410
411 } catch (final ClassNotFoundException e) {
412 NegotiateSecurityFilter.LOGGER.error("invalid class: {} in {}", classAndParameter[0],
413 implParameter.getKey());
414 throw new ServletException(e);
415 } catch (final Exception e) {
416 NegotiateSecurityFilter.LOGGER.error("Error setting {} in {}", classAndParameter[0],
417 classAndParameter[1]);
418 throw new ServletException(e);
419 }
420 } else {
421 NegotiateSecurityFilter.LOGGER.error("Invalid parameter: {}", implParameter.getKey());
422 throw new ServletException("Invalid parameter: " + implParameter.getKey());
423 }
424 }
425
426 NegotiateSecurityFilter.LOGGER.info("[waffle.servlet.NegotiateSecurityFilter] started");
427 }
428
429
430
431
432
433
434
435 public void setPrincipalFormat(final String format) {
436 this.principalFormat = PrincipalFormat.valueOf(format.toUpperCase(Locale.ENGLISH));
437 NegotiateSecurityFilter.LOGGER.info("principal format: {}", this.principalFormat);
438 }
439
440
441
442
443
444
445 public PrincipalFormat getPrincipalFormat() {
446 return this.principalFormat;
447 }
448
449
450
451
452
453
454
455 public void setRoleFormat(final String format) {
456 this.roleFormat = PrincipalFormat.valueOf(format.toUpperCase(Locale.ENGLISH));
457 NegotiateSecurityFilter.LOGGER.info("role format: {}", this.roleFormat);
458 }
459
460
461
462
463
464
465 public PrincipalFormat getRoleFormat() {
466 return this.roleFormat;
467 }
468
469
470
471
472
473
474
475
476
477 private void sendUnauthorized(final HttpServletResponse response, final boolean close) {
478 try {
479 this.providers.sendUnauthorized(response);
480 if (close) {
481 response.setHeader("Connection", "close");
482 } else {
483 response.setHeader("Connection", "keep-alive");
484 }
485 response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
486 response.flushBuffer();
487 } catch (final IOException e) {
488 throw new RuntimeException(e);
489 }
490 }
491
492
493
494
495
496
497 public IWindowsAuthProvider getAuth() {
498 return this.auth;
499 }
500
501
502
503
504
505
506
507 public void setAuth(final IWindowsAuthProvider provider) {
508 this.auth = provider;
509 }
510
511
512
513
514
515
516 public boolean isAllowGuestLogin() {
517 return this.allowGuestLogin;
518 }
519
520
521
522
523
524
525
526 public void setImpersonate(final boolean value) {
527 this.impersonate = value;
528 }
529
530
531
532
533
534
535 public boolean isImpersonate() {
536 return this.impersonate;
537 }
538
539
540
541
542
543
544 public SecurityFilterProviderCollection getProviders() {
545 return this.providers;
546 }
547
548 private static boolean isWindows() {
549 if (NegotiateSecurityFilter.windows == null) {
550 NegotiateSecurityFilter.windows = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win");
551 }
552 return NegotiateSecurityFilter.windows.booleanValue();
553 }
554
555 }