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