• 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

46.15
/Source/JNA/waffle-shiro/src/main/java/waffle/shiro/negotiate/NegotiateAuthenticationFilter.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.shiro.negotiate;
8

9
import java.io.IOException;
10
import java.util.ArrayList;
11
import java.util.Base64;
12
import java.util.List;
13
import java.util.Locale;
14

15
import javax.servlet.ServletRequest;
16
import javax.servlet.ServletResponse;
17
import javax.servlet.http.HttpServletRequest;
18
import javax.servlet.http.HttpServletResponse;
19

20
import org.apache.shiro.authc.AuthenticationException;
21
import org.apache.shiro.authc.AuthenticationToken;
22
import org.apache.shiro.subject.Subject;
23
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
24
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
25
import org.apache.shiro.web.util.WebUtils;
26
import org.slf4j.Logger;
27
import org.slf4j.LoggerFactory;
28

29
import waffle.util.AuthorizationHeader;
30
import waffle.util.NtlmServletRequest;
31

32
/**
33
 * A authentication filter that implements the HTTP Negotiate mechanism. The current user is authenticated, providing
34
 * single-sign-on. Derived from net.skorgenes.security.jsecurity.negotiate.NegotiateAuthenticationFilter. see:
35
 * https://bitbucket.org/lothor
36
 * /shiro-negotiate/src/7b25efde130b9cbcacf579b3f926c532d919aa23/src/main/java/net/skorgenes/
37
 * security/jsecurity/negotiate/NegotiateAuthenticationFilter.java?at=default
38
 *
39
 * @since 1.0.0
40
 */
41
public class NegotiateAuthenticationFilter extends AuthenticatingFilter {
42

43
    /**
44
     * This class's private logger.
45
     */
46
    private static final Logger LOGGER = LoggerFactory.getLogger(NegotiateAuthenticationFilter.class);
2✔
47

48
    // TODO things (sometimes) break, depending on what user account is running tomcat:
49
    // related to setSPN and running tomcat server as NT Service account vs. as normal user account.
50
    // https://waffle.codeplex.com/discussions/254748
51
    // setspn -A HTTP/<server-fqdn> <user_tomcat_running_under>
52
    /** The Constant PROTOCOLS. */
53
    private static final List<String> PROTOCOLS = new ArrayList<>();
2✔
54

55
    /** The failure key attribute. */
56
    private String failureKeyAttribute = FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME;
2✔
57

58
    /** The remember me param. */
59
    private String rememberMeParam = FormAuthenticationFilter.DEFAULT_REMEMBER_ME_PARAM;
2✔
60

61
    /**
62
     * Instantiates a new negotiate authentication filter.
63
     */
64
    public NegotiateAuthenticationFilter() {
2✔
65
        NegotiateAuthenticationFilter.PROTOCOLS.add("Negotiate");
2✔
66
        NegotiateAuthenticationFilter.PROTOCOLS.add("NTLM");
2✔
67
    }
2✔
68

69
    /**
70
     * Gets the remember me param.
71
     *
72
     * @return the remember me param
73
     */
74
    public String getRememberMeParam() {
75
        return this.rememberMeParam;
×
76
    }
77

78
    /**
79
     * Sets the request parameter name to look for when acquiring the rememberMe boolean value. Unless overridden by
80
     * calling this method, the default is <code>rememberMe</code>. <br>
81
     * <br>
82
     * RememberMe will be <code>true</code> if the parameter value equals any of those supported by
83
     * {@link org.apache.shiro.web.util.WebUtils#isTrue(javax.servlet.ServletRequest, String)
84
     * WebUtils.isTrue(request,value)}, <code>false</code> otherwise.
85
     *
86
     * @param value
87
     *            the name of the request param to check for acquiring the rememberMe boolean value.
88
     */
89
    public void setRememberMeParam(final String value) {
90
        this.rememberMeParam = value;
×
91
    }
×
92

93
    @Override
94
    protected boolean isRememberMe(final ServletRequest request) {
95
        return WebUtils.isTrue(request, this.getRememberMeParam());
×
96
    }
97

98
    @Override
99
    protected AuthenticationToken createToken(final ServletRequest request, final ServletResponse response) {
100
        final String authorization = this.getAuthzHeader(request);
×
101
        final String[] elements = authorization.split(" ", -1);
×
102
        final byte[] inToken = Base64.getDecoder().decode(elements[1]);
×
103

104
        // maintain a connection-based session for NTLM tokens
105
        // TODO see about changing this parameter to ServletRequest in waffle
106
        final String connectionId = NtlmServletRequest.getConnectionId((HttpServletRequest) request);
×
107
        final String securityPackage = elements[0];
×
108

109
        // TODO see about changing this parameter to ServletRequest in waffle
110
        final AuthorizationHeader authorizationHeader = new AuthorizationHeader((HttpServletRequest) request);
×
111
        final boolean ntlmPost = authorizationHeader.isNtlmType1PostAuthorizationHeader();
×
112

113
        NegotiateAuthenticationFilter.LOGGER.debug("security package: {}, connection id: {}, ntlmPost: {}",
×
114
                securityPackage, connectionId, Boolean.valueOf(ntlmPost));
×
115

116
        final boolean rememberMe = this.isRememberMe(request);
×
117
        final String host = this.getHost(request);
×
118

119
        return new NegotiateToken(inToken, new byte[0], connectionId, securityPackage, ntlmPost, rememberMe, host);
×
120
    }
121

122
    @Override
123
    protected boolean onLoginSuccess(final AuthenticationToken token, final Subject subject,
124
            final ServletRequest request, final ServletResponse response) throws Exception {
125
        request.setAttribute("MY_SUBJECT", ((NegotiateToken) token).getSubject());
×
126
        return true;
×
127
    }
128

129
    @Override
130
    protected boolean onLoginFailure(final AuthenticationToken token, final AuthenticationException e,
131
            final ServletRequest request, final ServletResponse response) {
132
        if (e instanceof AuthenticationInProgressException) {
×
133
            // negotiate is processing
134
            final String protocol = this.getAuthzHeaderProtocol(request);
×
135
            NegotiateAuthenticationFilter.LOGGER.debug("Negotiation in progress for protocol: {}", protocol);
×
136
            this.sendChallengeDuringNegotiate(protocol, response, ((NegotiateToken) token).getOut());
×
137
            return false;
×
138
        }
139
        NegotiateAuthenticationFilter.LOGGER.warn("login exception: {}", e.getMessage());
×
140

141
        // do not send token.out bytes, this was a login failure.
142
        this.sendChallengeOnFailure(response);
×
143

144
        this.setFailureAttribute(request, e);
×
145
        return true;
×
146
    }
147

148
    /**
149
     * Sets the failure attribute.
150
     *
151
     * @param request
152
     *            the request
153
     * @param ae
154
     *            the ae
155
     */
156
    protected void setFailureAttribute(final ServletRequest request, final AuthenticationException ae) {
157
        final String className = ae.getClass().getName();
×
158
        request.setAttribute(this.getFailureKeyAttribute(), className);
×
159
    }
×
160

161
    /**
162
     * Gets the failure key attribute.
163
     *
164
     * @return the failure key attribute
165
     */
166
    public String getFailureKeyAttribute() {
167
        return this.failureKeyAttribute;
×
168
    }
169

170
    /**
171
     * Sets the failure key attribute.
172
     *
173
     * @param value
174
     *            the new failure key attribute
175
     */
176
    public void setFailureKeyAttribute(final String value) {
177
        this.failureKeyAttribute = value;
×
178
    }
×
179

180
    @Override
181
    protected boolean onAccessDenied(final ServletRequest request, final ServletResponse response) throws Exception {
182
        // false by default or we wouldn't be in
183
        boolean loggedIn = false;
×
184
        // this method
185
        if (this.isLoginAttempt(request)) {
×
186
            loggedIn = this.executeLogin(request, response);
×
187
        } else {
188
            NegotiateAuthenticationFilter.LOGGER.debug("authorization required, supported protocols: {}",
×
189
                    NegotiateAuthenticationFilter.PROTOCOLS);
190
            this.sendChallengeInitiateNegotiate(response);
×
191
        }
192
        return loggedIn;
×
193
    }
194

195
    /**
196
     * Returns the {@link org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter#AUTHORIZATION_HEADER
197
     * AUTHORIZATION_HEADER} from the specified ServletRequest.
198
     * <p/>
199
     * This implementation merely casts the request to an <code>HttpServletRequest</code> and returns the header:
200
     * <p/>
201
     * <code>HttpServletRequest httpRequest = {@link WebUtils#toHttp(javax.servlet.ServletRequest) toHttp(reaquest)};<br/>
202
     * return httpRequest.getHeader({@link org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter#AUTHORIZATION_HEADER AUTHORIZATION_HEADER});</code>
203
     *
204
     * @param request
205
     *            the incoming <code>ServletRequest</code>
206
     *
207
     * @return the <code>Authorization</code> header's value.
208
     */
209
    private String getAuthzHeader(final ServletRequest request) {
210
        final HttpServletRequest httpRequest = WebUtils.toHttp(request);
×
211
        return httpRequest.getHeader("Authorization");
×
212
    }
213

214
    /**
215
     * Gets the authz header protocol.
216
     *
217
     * @param request
218
     *            the request
219
     *
220
     * @return the authz header protocol
221
     */
222
    private String getAuthzHeaderProtocol(final ServletRequest request) {
223
        final String authzHeader = this.getAuthzHeader(request);
×
224
        return authzHeader.substring(0, authzHeader.indexOf(' '));
×
225
    }
226

227
    /**
228
     * Determines whether the incoming request is an attempt to log in.
229
     * <p/>
230
     * The default implementation obtains the value of the request's
231
     * {@link org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter#AUTHORIZATION_HEADER AUTHORIZATION_HEADER}
232
     * , and if it is not <code>null</code>, delegates to
233
     * {@link org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter#isLoginAttempt(String)
234
     * isLoginAttempt(authzHeaderValue)}. If the header is <code>null</code>, <code>false</code> is returned.
235
     *
236
     * @param request
237
     *            incoming ServletRequest
238
     *
239
     * @return true if the incoming request is an attempt to log in based, false otherwise
240
     */
241
    private boolean isLoginAttempt(final ServletRequest request) {
242
        final String authzHeader = this.getAuthzHeader(request);
×
243
        return authzHeader != null && this.isLoginAttempt(authzHeader);
×
244
    }
245

246
    /**
247
     * Default implementation that returns <code>true</code> if the specified <code>authzHeader</code> starts with the
248
     * same (case-insensitive) characters specified by any of the configured protocols (Negotiate or NTLM),
249
     * <code>false</code> otherwise.
250
     *
251
     * @param authzHeader
252
     *            the 'Authorization' header value (guaranteed to be non-null if the
253
     *            {@link #isLoginAttempt(javax.servlet.ServletRequest)} method is not overriden).
254
     *
255
     * @return <code>true</code> if the authzHeader value matches any of the configured protocols (Negotiate or NTLM).
256
     */
257
    boolean isLoginAttempt(final String authzHeader) {
258
        for (final String protocol : NegotiateAuthenticationFilter.PROTOCOLS) {
2✔
259
            if (authzHeader.toLowerCase(Locale.ENGLISH).startsWith(protocol.toLowerCase(Locale.ENGLISH))) {
2✔
260
                return true;
2✔
261
            }
262
        }
2✔
263
        return false;
2✔
264
    }
265

266
    /**
267
     * Builds the challenge for authorization by setting a HTTP <code>401</code> (Unauthorized) status as well as the
268
     * response's {@link org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter#AUTHENTICATE_HEADER
269
     * AUTHENTICATE_HEADER}.
270
     *
271
     * @param protocols
272
     *            protocols for which to send a challenge. In initial cases, will be all supported protocols. In the
273
     *            midst of negotiation, will be only the protocol being negotiated.
274
     * @param response
275
     *            outgoing ServletResponse
276
     * @param out
277
     *            token.out or null
278
     */
279
    private void sendChallenge(final List<String> protocols, final ServletResponse response, final byte[] out) {
280
        final HttpServletResponse httpResponse = WebUtils.toHttp(response);
2✔
281
        this.sendAuthenticateHeader(protocols, out, httpResponse);
2✔
282
        httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
2✔
283
    }
2✔
284

285
    /**
286
     * Send challenge initiate negotiate.
287
     *
288
     * @param response
289
     *            the response
290
     */
291
    void sendChallengeInitiateNegotiate(final ServletResponse response) {
292
        this.sendChallenge(NegotiateAuthenticationFilter.PROTOCOLS, response, null);
2✔
293
    }
2✔
294

295
    /**
296
     * Send challenge during negotiate.
297
     *
298
     * @param protocol
299
     *            the protocol
300
     * @param response
301
     *            the response
302
     * @param out
303
     *            the out
304
     */
305
    void sendChallengeDuringNegotiate(final String protocol, final ServletResponse response, final byte[] out) {
306
        final List<String> protocolsList = new ArrayList<>();
2✔
307
        protocolsList.add(protocol);
2✔
308
        this.sendChallenge(protocolsList, response, out);
2✔
309
    }
2✔
310

311
    /**
312
     * Send challenge on failure.
313
     *
314
     * @param response
315
     *            the response
316
     */
317
    void sendChallengeOnFailure(final ServletResponse response) {
318
        final HttpServletResponse httpResponse = WebUtils.toHttp(response);
2✔
319
        this.sendUnauthorized(NegotiateAuthenticationFilter.PROTOCOLS, null, httpResponse);
2✔
320
        httpResponse.setHeader("Connection", "close");
2✔
321
        try {
322
            httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
2✔
323
            httpResponse.flushBuffer();
2✔
324
        } catch (final IOException e) {
×
325
            throw new RuntimeException(e);
×
326
        }
2✔
327
    }
2✔
328

329
    /**
330
     * Send authenticate header.
331
     *
332
     * @param protocolsList
333
     *            the protocols list
334
     * @param out
335
     *            the out
336
     * @param httpResponse
337
     *            the http response
338
     */
339
    private void sendAuthenticateHeader(final List<String> protocolsList, final byte[] out,
340
            final HttpServletResponse httpResponse) {
341
        this.sendUnauthorized(protocolsList, out, httpResponse);
2✔
342
        httpResponse.setHeader("Connection", "keep-alive");
2✔
343
    }
2✔
344

345
    /**
346
     * Send unauthorized.
347
     *
348
     * @param protocols
349
     *            the protocols
350
     * @param out
351
     *            the out
352
     * @param response
353
     *            the response
354
     */
355
    private void sendUnauthorized(final List<String> protocols, final byte[] out, final HttpServletResponse response) {
356
        for (final String protocol : protocols) {
2✔
357
            if (out == null || out.length == 0) {
2!
358
                response.addHeader("WWW-Authenticate", protocol);
2✔
359
            } else {
360
                response.setHeader("WWW-Authenticate", protocol + " " + Base64.getEncoder().encodeToString(out));
2✔
361
            }
362
        }
2✔
363
    }
2✔
364

365
}
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