• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Waffle / waffle / 6364

01 Feb 2026 02:07AM UTC coverage: 46.217%. Remained the same
6364

push

github

web-flow
Merge pull request #3206 from Waffle/renovate/checkstyle.version

Update dependency com.puppycrawl.tools:checkstyle to v13.1.0

276 of 734 branches covered (37.6%)

Branch coverage included in aggregate %.

1019 of 2068 relevant lines covered (49.27%)

1.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

30.47
/Source/JNA/waffle-jna/src/main/java/waffle/servlet/NegotiateSecurityFilter.java
1
/*
2
 * SPDX-License-Identifier: MIT
3
 * See LICENSE file for details.
4
 *
5
 * Copyright 2010-2026 The Waffle Project Contributors: https://github.com/Waffle/waffle/graphs/contributors
6
 */
7
package waffle.servlet;
8

9
import java.io.IOException;
10
import java.lang.reflect.InvocationTargetException;
11
import java.security.Principal;
12
import java.util.Collections;
13
import java.util.HashMap;
14
import java.util.List;
15
import java.util.Locale;
16
import java.util.Map;
17

18
import javax.security.auth.Subject;
19
import javax.servlet.Filter;
20
import javax.servlet.FilterChain;
21
import javax.servlet.FilterConfig;
22
import javax.servlet.ServletException;
23
import javax.servlet.ServletRequest;
24
import javax.servlet.ServletResponse;
25
import javax.servlet.http.HttpServletRequest;
26
import javax.servlet.http.HttpServletResponse;
27
import javax.servlet.http.HttpSession;
28

29
import org.slf4j.Logger;
30
import org.slf4j.LoggerFactory;
31

32
import waffle.servlet.spi.SecurityFilterProvider;
33
import waffle.servlet.spi.SecurityFilterProviderCollection;
34
import waffle.util.AuthorizationHeader;
35
import waffle.util.CorsPreFlightCheck;
36
import waffle.windows.auth.IWindowsAuthProvider;
37
import waffle.windows.auth.IWindowsIdentity;
38
import waffle.windows.auth.IWindowsImpersonationContext;
39
import waffle.windows.auth.PrincipalFormat;
40
import waffle.windows.auth.impl.WindowsAuthProviderImpl;
41

42
/**
43
 * A Negotiate (NTLM/Kerberos) Security Filter.
44
 */
45
public class NegotiateSecurityFilter implements Filter {
46

47
    /** The Constant LOGGER. */
48
    private static final Logger LOGGER = LoggerFactory.getLogger(NegotiateSecurityFilter.class);
1✔
49

1✔
50
    /** The Constant PRINCIPALSESSIONKEY. */
51
    private static final String PRINCIPALSESSIONKEY = NegotiateSecurityFilter.class.getName() + ".PRINCIPAL";
1✔
52

1✔
53
    /** The windows flag. */
54
    private static Boolean windows;
55

56
    /** The principal format. */
57
    private PrincipalFormat principalFormat = PrincipalFormat.FQN;
1✔
58

1✔
59
    /** The role format. */
60
    private PrincipalFormat roleFormat = PrincipalFormat.FQN;
1✔
61

1✔
62
    /** The providers. */
63
    private SecurityFilterProviderCollection providers;
64

65
    /** The auth. */
66
    private IWindowsAuthProvider auth;
67

68
    /** The exclusion filter. */
69
    private String[] excludePatterns;
70

71
    /** The allow guest login. */
72
    private boolean allowGuestLogin = true;
1✔
73

1✔
74
    /** The impersonate. */
75
    private boolean impersonate;
76

77
    /** The exclusion bearer authorization. */
78
    private boolean excludeBearerAuthorization;
79

80
    /** The exclusions cors pre flight. */
81
    private boolean excludeCorsPreflight;
82

83
    /** The disable SSO. */
84
    private boolean disableSSO;
85

86
    /**
87
     * Instantiates a new negotiate security filter.
88
     */
89
    public NegotiateSecurityFilter() {
1✔
90
        NegotiateSecurityFilter.LOGGER.debug("[waffle.servlet.NegotiateSecurityFilter] loaded");
2✔
91
    }
2✔
92

1✔
93
    @Override
94
    public void destroy() {
95
        NegotiateSecurityFilter.LOGGER.info("[waffle.servlet.NegotiateSecurityFilter] stopped");
×
96
    }
×
97

×
98
    @Override
99
    public void doFilter(final ServletRequest sreq, final ServletResponse sres, final FilterChain chain)
100
            throws IOException, ServletException {
101

102
        final HttpServletRequest request = (HttpServletRequest) sreq;
1✔
103
        final HttpServletResponse response = (HttpServletResponse) sres;
2✔
104

1✔
105
        NegotiateSecurityFilter.LOGGER.debug("{} {}, contentlength: {}", request.getMethod(), request.getRequestURI(),
1✔
106
                Integer.valueOf(request.getContentLength()));
2✔
107

1✔
108
        // If we are not in a windows environment, resume filter chain
109
        if (!NegotiateSecurityFilter.isWindows()) {
1!
110
            NegotiateSecurityFilter.LOGGER.debug("Running in a non windows environment, SSO skipped");
1!
111
            chain.doFilter(request, response);
×
112
            return;
×
113
        }
×
114

115
        // If sso is disabled, resume filter chain
116
        if (this.disableSSO) {
1!
117
            NegotiateSecurityFilter.LOGGER.debug("SSO is disabled, resuming filter chain");
1!
118
            chain.doFilter(request, response);
×
119
            return;
×
120
        }
×
121

122
        // If excluded URL, resume the filter chain
123
        if (request.getRequestURL() != null && this.excludePatterns != null) {
1!
124
            final String url = request.getRequestURL().toString();
1!
125
            for (final String pattern : this.excludePatterns) {
×
126
                if (url.matches(pattern)) {
×
127
                    NegotiateSecurityFilter.LOGGER.info("Pattern :{} excluded URL:{}", url, pattern);
×
128
                    chain.doFilter(sreq, sres);
×
129
                    return;
×
130
                }
×
131
            }
132
        }
133

134
        // If exclude cores pre-flight and is pre flight, resume the filter chain
135
        if (this.excludeCorsPreflight && CorsPreFlightCheck.isPreflight(request)) {
1!
136
            NegotiateSecurityFilter.LOGGER.debug("[waffle.servlet.NegotiateSecurityFilter] CORS preflight");
1!
137
            chain.doFilter(sreq, sres);
×
138
            return;
×
139
        }
×
140

141
        final AuthorizationHeader authorizationHeader = new AuthorizationHeader(request);
1✔
142

1✔
143
        // If exclude bearer authorization and is bearer authorization, result the filter chain
144
        if (this.excludeBearerAuthorization && authorizationHeader.isBearerAuthorizationHeader()) {
1!
145
            NegotiateSecurityFilter.LOGGER.debug("[waffle.servlet.NegotiateSecurityFilter] Authorization: Bearer");
2!
146
            chain.doFilter(sreq, sres);
2✔
147
            return;
2✔
148
        }
1✔
149

150
        if (this.doFilterPrincipal(request, response, chain)) {
×
151
            // previously authenticated user
×
152
            return;
×
153
        }
×
154

155
        // authenticate user
156
        if (!authorizationHeader.isNull()) {
×
157

×
158
            // log the user in using the token
159
            IWindowsIdentity windowsIdentity;
160
            try {
161
                windowsIdentity = this.providers.doFilter(request, response);
×
162
                if (windowsIdentity == null) {
×
163
                    return;
×
164
                }
×
165
            } catch (final IOException e) {
×
166
                NegotiateSecurityFilter.LOGGER.warn("error logging in user: {}", e.getMessage());
×
167
                NegotiateSecurityFilter.LOGGER.trace("", e);
×
168
                this.sendUnauthorized(response, true);
×
169
                return;
×
170
            }
×
171

×
172
            IWindowsImpersonationContext ctx = null;
×
173
            try {
×
174
                if (!this.allowGuestLogin && windowsIdentity.isGuest()) {
×
175
                    NegotiateSecurityFilter.LOGGER.warn("guest login disabled: {}", windowsIdentity.getFqn());
×
176
                    this.sendUnauthorized(response, true);
×
177
                    return;
×
178
                }
×
179

180
                NegotiateSecurityFilter.LOGGER.debug("logged in user: {} ({})", windowsIdentity.getFqn(),
×
181
                        windowsIdentity.getSidString());
×
182

×
183
                final HttpSession session = request.getSession(true);
×
184
                if (session == null) {
×
185
                    throw new ServletException("Expected HttpSession");
×
186
                }
×
187

188
                Subject subject = (Subject) session.getAttribute("javax.security.auth.subject");
×
189
                if (subject == null) {
×
190
                    subject = new Subject();
×
191
                }
×
192

193
                WindowsPrincipal windowsPrincipal;
194
                if (this.impersonate) {
×
195
                    windowsPrincipal = new AutoDisposableWindowsPrincipal(windowsIdentity, this.principalFormat,
×
196
                            this.roleFormat);
×
197
                } else {
198
                    windowsPrincipal = new WindowsPrincipal(windowsIdentity, this.principalFormat, this.roleFormat);
×
199
                }
×
200

201
                NegotiateSecurityFilter.LOGGER.debug("roles: {}", windowsPrincipal.getRolesString());
×
202
                subject.getPrincipals().add(windowsPrincipal);
×
203
                request.getSession(false).setAttribute("javax.security.auth.subject", subject);
×
204

×
205
                NegotiateSecurityFilter.LOGGER.info("successfully logged in user: {}", windowsIdentity.getFqn());
×
206

×
207
                request.getSession(false).setAttribute(NegotiateSecurityFilter.PRINCIPALSESSIONKEY, windowsPrincipal);
×
208

×
209
                final NegotiateRequestWrapper requestWrapper = new NegotiateRequestWrapper(request, windowsPrincipal);
×
210

×
211
                if (this.impersonate) {
×
212
                    NegotiateSecurityFilter.LOGGER.debug("impersonating user");
×
213
                    ctx = windowsIdentity.impersonate();
×
214
                }
×
215

216
                chain.doFilter(requestWrapper, response);
×
217
            } finally {
×
218
                if (this.impersonate && ctx != null) {
×
219
                    NegotiateSecurityFilter.LOGGER.debug("terminating impersonation");
×
220
                    ctx.revertToSelf();
×
221
                } else {
×
222
                    windowsIdentity.dispose();
×
223
                }
×
224
            }
225

226
            return;
×
227
        }
×
228

229
        NegotiateSecurityFilter.LOGGER.debug("authorization required");
×
230
        this.sendUnauthorized(response, false);
×
231
    }
×
232

×
233
    /**
234
     * Filter for a previously logged on user.
235
     *
236
     * @param request
237
     *            HTTP request.
238
     * @param response
239
     *            HTTP response.
240
     * @param chain
241
     *            Filter chain.
242
     *
243
     * @return True if a user already authenticated.
244
     *
245
     * @throws IOException
246
     *             Signals that an I/O exception has occurred.
247
     * @throws ServletException
248
     *             the servlet exception
249
     */
250
    private boolean doFilterPrincipal(final HttpServletRequest request, final HttpServletResponse response,
251
            final FilterChain chain) throws IOException, ServletException {
252
        Principal principal = request.getUserPrincipal();
×
253
        if (principal == null) {
×
254
            final HttpSession session = request.getSession(false);
×
255
            if (session != null) {
×
256
                principal = (Principal) session.getAttribute(NegotiateSecurityFilter.PRINCIPALSESSIONKEY);
×
257
            }
×
258
        }
259

260
        if (principal == null) {
×
261
            // no principal in this request
×
262
            return false;
×
263
        }
×
264

265
        if (this.providers.isPrincipalException(request)) {
×
266
            // the providers signal to authenticate despite an existing principal, eg. NTLM post
×
267
            return false;
×
268
        }
×
269

270
        // user already authenticated
271
        if (principal instanceof WindowsPrincipal) {
×
272
            NegotiateSecurityFilter.LOGGER.debug("previously authenticated Windows user: {}", principal.getName());
×
273
            final WindowsPrincipal windowsPrincipal = (WindowsPrincipal) principal;
×
274

×
275
            if (this.impersonate && windowsPrincipal.getIdentity() == null) {
×
276
                // This can happen when the session has been serialized then de-serialized
×
277
                // and because the IWindowsIdentity field is transient. In this case re-ask an
278
                // authentication to get a new identity.
279
                return false;
×
280
            }
×
281

282
            final NegotiateRequestWrapper requestWrapper = new NegotiateRequestWrapper(request, windowsPrincipal);
×
283

×
284
            IWindowsImpersonationContext ctx = null;
×
285
            if (this.impersonate) {
×
286
                NegotiateSecurityFilter.LOGGER.debug("re-impersonating user");
×
287
                ctx = windowsPrincipal.getIdentity().impersonate();
×
288
            }
×
289
            try {
290
                chain.doFilter(requestWrapper, response);
×
291
            } finally {
×
292
                if (this.impersonate && ctx != null) {
×
293
                    NegotiateSecurityFilter.LOGGER.debug("terminating impersonation");
×
294
                    ctx.revertToSelf();
×
295
                }
×
296
            }
297
        } else {
×
298
            NegotiateSecurityFilter.LOGGER.debug("previously authenticated user: {}", principal.getName());
×
299
            chain.doFilter(request, response);
×
300
        }
×
301
        return true;
×
302
    }
×
303

304
    @Override
305
    public void init(final FilterConfig filterConfig) throws ServletException {
306
        final Map<String, String> implParameters = new HashMap<>();
1✔
307

1✔
308
        NegotiateSecurityFilter.LOGGER.debug("[waffle.servlet.NegotiateSecurityFilter] starting");
1✔
309

1✔
310
        String authProvider = null;
1✔
311
        String[] providerNames = null;
2✔
312
        if (filterConfig != null) {
2!
313
            final List<String> parameterNames = Collections.list(filterConfig.getInitParameterNames());
2!
314
            NegotiateSecurityFilter.LOGGER.debug("[waffle.servlet.NegotiateSecurityFilter] processing filterConfig");
2✔
315
            for (String parameterName : parameterNames) {
2✔
316
                final String parameterValue = filterConfig.getInitParameter(parameterName);
2✔
317
                NegotiateSecurityFilter.LOGGER.debug("Init Param: '{}={}'", parameterName, parameterValue);
2✔
318
                switch (parameterName) {
2!
319
                    case "principalFormat":
1!
320
                        this.principalFormat = PrincipalFormat.valueOf(parameterValue.toUpperCase(Locale.ENGLISH));
1✔
321
                        break;
2✔
322
                    case "roleFormat":
1✔
323
                        this.roleFormat = PrincipalFormat.valueOf(parameterValue.toUpperCase(Locale.ENGLISH));
1✔
324
                        break;
2✔
325
                    case "allowGuestLogin":
1✔
326
                        this.allowGuestLogin = Boolean.parseBoolean(parameterValue);
1✔
327
                        break;
2✔
328
                    case "impersonate":
1✔
329
                        this.impersonate = Boolean.parseBoolean(parameterValue);
1✔
330
                        break;
2✔
331
                    case "securityFilterProviders":
1✔
332
                        providerNames = parameterValue.split("\\s+", -1);
1✔
333
                        break;
2✔
334
                    case "authProvider":
1✔
335
                        authProvider = parameterValue;
×
336
                        break;
×
337
                    case "excludePatterns":
×
338
                        this.excludePatterns = parameterValue.split("\\s+", -1);
1✔
339
                        break;
2✔
340
                    case "excludeCorsPreflight":
1✔
341
                        this.excludeCorsPreflight = Boolean.parseBoolean(parameterValue);
1✔
342
                        break;
2✔
343
                    case "excludeBearerAuthorization":
1✔
344
                        this.excludeBearerAuthorization = Boolean.parseBoolean(parameterValue);
1✔
345
                        break;
2✔
346
                    case "disableSSO":
1✔
347
                        this.disableSSO = Boolean.parseBoolean(parameterValue);
×
348
                        break;
×
349
                    default:
×
350
                        implParameters.put(parameterName, parameterValue);
×
351
                        break;
×
352
                }
353
            }
1✔
354
        }
1✔
355

356
        NegotiateSecurityFilter.LOGGER.debug("[waffle.servlet.NegotiateSecurityFilter] authProvider");
1✔
357
        if (authProvider != null) {
2!
358
            try {
1!
359
                this.auth = Class.forName(authProvider).asSubclass(IWindowsAuthProvider.class).getConstructor()
×
360
                        .newInstance();
×
361
            } catch (final ClassNotFoundException | IllegalArgumentException | SecurityException
×
362
                    | InstantiationException | IllegalAccessException | InvocationTargetException
×
363
                    | NoSuchMethodException e) {
364
                throw new ServletException(e);
×
365
            }
×
366
        }
×
367

368
        if (this.auth == null) {
1!
369
            this.auth = new WindowsAuthProviderImpl();
2!
370
        }
1✔
371

372
        if (providerNames != null) {
1!
373
            this.providers = new SecurityFilterProviderCollection(providerNames, this.auth);
2!
374
        }
1✔
375

376
        // create default providers if none specified
377
        if (this.providers == null) {
1!
378
            NegotiateSecurityFilter.LOGGER.debug("initializing default security filter providers");
1!
379
            this.providers = new SecurityFilterProviderCollection(this.auth);
×
380
        }
×
381

382
        // apply provider implementation parameters
383
        NegotiateSecurityFilter.LOGGER.debug("[waffle.servlet.NegotiateSecurityFilter] load provider parameters");
1✔
384
        for (final Map.Entry<String, String> implParameter : implParameters.entrySet()) {
2!
385
            final String[] classAndParameter = implParameter.getKey().split("/", 2);
1!
386
            if (classAndParameter.length == 2) {
×
387
                try {
×
388

389
                    NegotiateSecurityFilter.LOGGER.debug("setting {}, {}={}", classAndParameter[0],
×
390
                            classAndParameter[1], implParameter.getValue());
×
391

×
392
                    final SecurityFilterProvider provider = this.providers.getByClassName(classAndParameter[0]);
×
393
                    provider.initParameter(classAndParameter[1], implParameter.getValue());
×
394

×
395
                } catch (final ClassNotFoundException e) {
×
396
                    NegotiateSecurityFilter.LOGGER.error("invalid class: {} in {}", classAndParameter[0],
×
397
                            implParameter.getKey());
×
398
                    throw new ServletException(e);
×
399
                } catch (final Exception e) {
×
400
                    NegotiateSecurityFilter.LOGGER.error("Error setting {} in {}", classAndParameter[0],
×
401
                            classAndParameter[1]);
×
402
                    throw new ServletException(e);
×
403
                }
×
404
            } else {
×
405
                NegotiateSecurityFilter.LOGGER.error("Invalid parameter: {}", implParameter.getKey());
×
406
                throw new ServletException("Invalid parameter: " + implParameter.getKey());
×
407
            }
×
408
        }
×
409

×
410
        NegotiateSecurityFilter.LOGGER.info("[waffle.servlet.NegotiateSecurityFilter] started");
1✔
411
    }
2✔
412

1✔
413
    /**
414
     * Set the principal format.
415
     *
416
     * @param format
417
     *            Principal format.
418
     */
419
    public void setPrincipalFormat(final String format) {
420
        this.principalFormat = PrincipalFormat.valueOf(format.toUpperCase(Locale.ENGLISH));
×
421
        NegotiateSecurityFilter.LOGGER.info("principal format: {}", this.principalFormat);
×
422
    }
×
423

×
424
    /**
425
     * Principal format.
426
     *
427
     * @return Principal format.
428
     */
429
    public PrincipalFormat getPrincipalFormat() {
430
        return this.principalFormat;
×
431
    }
×
432

433
    /**
434
     * Set the principal format.
435
     *
436
     * @param format
437
     *            Role format.
438
     */
439
    public void setRoleFormat(final String format) {
440
        this.roleFormat = PrincipalFormat.valueOf(format.toUpperCase(Locale.ENGLISH));
×
441
        NegotiateSecurityFilter.LOGGER.info("role format: {}", this.roleFormat);
×
442
    }
×
443

×
444
    /**
445
     * Principal format.
446
     *
447
     * @return Role format.
448
     */
449
    public PrincipalFormat getRoleFormat() {
450
        return this.roleFormat;
×
451
    }
×
452

453
    /**
454
     * Send a 401 Unauthorized along with protocol authentication headers.
455
     *
456
     * @param response
457
     *            HTTP Response
458
     * @param close
459
     *            Close connection.
460
     */
461
    private void sendUnauthorized(final HttpServletResponse response, final boolean close) {
462
        try {
463
            this.providers.sendUnauthorized(response);
×
464
            if (close) {
×
465
                response.setHeader("Connection", "close");
×
466
            } else {
×
467
                response.setHeader("Connection", "keep-alive");
×
468
            }
×
469
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
×
470
            response.flushBuffer();
×
471
        } catch (final IOException e) {
×
472
            throw new RuntimeException(e);
×
473
        }
×
474
    }
×
475

×
476
    /**
477
     * Windows auth provider.
478
     *
479
     * @return IWindowsAuthProvider.
480
     */
481
    public IWindowsAuthProvider getAuth() {
482
        return this.auth;
×
483
    }
×
484

485
    /**
486
     * Set Windows auth provider.
487
     *
488
     * @param provider
489
     *            Class implements IWindowsAuthProvider.
490
     */
491
    public void setAuth(final IWindowsAuthProvider provider) {
492
        this.auth = provider;
×
493
    }
×
494

×
495
    /**
496
     * True if guest login is allowed.
497
     *
498
     * @return True if guest login is allowed, false otherwise.
499
     */
500
    public boolean isAllowGuestLogin() {
501
        return this.allowGuestLogin;
1✔
502
    }
1✔
503

504
    /**
505
     * Enable/Disable impersonation.
506
     *
507
     * @param value
508
     *            true to enable impersonation, false otherwise
509
     */
510
    public void setImpersonate(final boolean value) {
511
        this.impersonate = value;
×
512
    }
×
513

×
514
    /**
515
     * Checks if is impersonate.
516
     *
517
     * @return true if impersonation is enabled, false otherwise
518
     */
519
    public boolean isImpersonate() {
520
        return this.impersonate;
1✔
521
    }
1✔
522

523
    /**
524
     * Security filter providers.
525
     *
526
     * @return A collection of security filter providers.
527
     */
528
    public SecurityFilterProviderCollection getProviders() {
529
        return this.providers;
×
530
    }
×
531

532
    private static boolean isWindows() {
533
        if (NegotiateSecurityFilter.windows == null) {
1!
534
            NegotiateSecurityFilter.windows = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win");
2!
535
        }
1✔
536
        return NegotiateSecurityFilter.windows.booleanValue();
1✔
537
    }
1✔
538

539
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc