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.apache;
25
26 import com.sun.jna.platform.win32.Win32Exception;
27
28 import java.io.IOException;
29 import java.security.Principal;
30 import java.util.Base64;
31
32 import javax.servlet.RequestDispatcher;
33 import javax.servlet.ServletContext;
34 import javax.servlet.ServletException;
35 import javax.servlet.http.HttpServletResponse;
36 import javax.servlet.http.HttpSession;
37
38 import org.apache.catalina.LifecycleException;
39 import org.apache.catalina.connector.Request;
40 import org.apache.catalina.realm.GenericPrincipal;
41 import org.apache.tomcat.util.descriptor.web.LoginConfig;
42 import org.slf4j.LoggerFactory;
43
44 import waffle.util.AuthorizationHeader;
45 import waffle.util.NtlmServletRequest;
46 import waffle.windows.auth.IWindowsIdentity;
47 import waffle.windows.auth.IWindowsSecurityContext;
48
49
50
51
52 public class MixedAuthenticator extends WaffleAuthenticatorBase {
53
54
55
56
57 public MixedAuthenticator() {
58 super();
59 this.log = LoggerFactory.getLogger(MixedAuthenticator.class);
60 this.info = "waffle.apache.MixedAuthenticator/1.0";
61 this.log.debug("[waffle.apache.MixedAuthenticator] loaded");
62 }
63
64 @Override
65 public synchronized void startInternal() throws LifecycleException {
66 this.log.info("[waffle.apache.MixedAuthenticator] started");
67 super.startInternal();
68 }
69
70 @Override
71 public synchronized void stopInternal() throws LifecycleException {
72 super.stopInternal();
73 this.log.info("[waffle.apache.MixedAuthenticator] stopped");
74 }
75
76 @Override
77 public boolean authenticate(final Request request, final HttpServletResponse response) {
78
79
80 if (this.context == null || this.context.getRealm() == null) {
81 this.log.warn("missing context/realm");
82 this.sendError(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE);
83 return false;
84 }
85
86 this.log.debug("{} {}, contentlength: {}", request.getMethod(), request.getRequestURI(),
87 Integer.valueOf(request.getContentLength()));
88
89 final boolean negotiateCheck = request.getParameter("j_negotiate_check") != null;
90 this.log.debug("negotiateCheck: {}", Boolean.valueOf(negotiateCheck));
91 final boolean securityCheck = request.getParameter("j_security_check") != null;
92 this.log.debug("securityCheck: {}", Boolean.valueOf(securityCheck));
93
94 final Principal principal = request.getUserPrincipal();
95
96 final AuthorizationHeader authorizationHeader = new AuthorizationHeader(request);
97 final boolean ntlmPost = authorizationHeader.isNtlmType1PostAuthorizationHeader();
98 this.log.debug("authorization: {}, ntlm post: {}", authorizationHeader, Boolean.valueOf(ntlmPost));
99
100 final LoginConfig loginConfig = this.context.getLoginConfig();
101
102 if (principal != null && !ntlmPost) {
103 this.log.debug("previously authenticated user: {}", principal.getName());
104 return true;
105 } else if (negotiateCheck) {
106 if (!authorizationHeader.isNull()) {
107 boolean negotiateResult = this.negotiate(request, response, authorizationHeader);
108 if (!negotiateResult) {
109 this.redirectTo(request, response, loginConfig.getErrorPage());
110 }
111 return negotiateResult;
112 }
113 this.log.debug("authorization required");
114 this.sendUnauthorized(response);
115 return false;
116 } else if (securityCheck) {
117 final boolean postResult = this.post(request, response);
118 if (!postResult) {
119 this.redirectTo(request, response, loginConfig.getErrorPage());
120 }
121 return postResult;
122 } else {
123 this.redirectTo(request, response, loginConfig.getLoginPage());
124 return false;
125 }
126 }
127
128
129
130
131
132
133
134
135
136
137
138
139
140 private boolean negotiate(final Request request, final HttpServletResponse response,
141 final AuthorizationHeader authorizationHeader) {
142
143 final String securityPackage = authorizationHeader.getSecurityPackage();
144
145 final String connectionId = NtlmServletRequest.getConnectionId(request);
146
147 this.log.debug("security package: {}, connection id: {}", securityPackage, connectionId);
148
149 final boolean ntlmPost = authorizationHeader.isNtlmType1PostAuthorizationHeader();
150
151 if (ntlmPost) {
152
153 this.auth.resetSecurityToken(connectionId);
154 }
155
156 final byte[] tokenBuffer = authorizationHeader.getTokenBytes();
157 this.log.debug("token buffer: {} byte(s)", Integer.valueOf(tokenBuffer.length));
158
159
160 IWindowsSecurityContext securityContext;
161 try {
162 securityContext = this.auth.acceptSecurityToken(connectionId, tokenBuffer, securityPackage);
163 } catch (final Win32Exception e) {
164 this.log.warn("error logging in user: {}", e.getMessage());
165 this.log.trace("", e);
166 this.sendUnauthorized(response);
167 return false;
168 }
169 this.log.debug("continue required: {}", Boolean.valueOf(securityContext.isContinue()));
170
171 final byte[] continueTokenBytes = securityContext.getToken();
172 if (continueTokenBytes != null && continueTokenBytes.length > 0) {
173 final String continueToken = Base64.getEncoder().encodeToString(continueTokenBytes);
174 this.log.debug("continue token: {}", continueToken);
175 response.addHeader("WWW-Authenticate", securityPackage + " " + continueToken);
176 }
177
178 try {
179 if (securityContext.isContinue() || ntlmPost) {
180 response.setHeader("Connection", "keep-alive");
181 response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
182 response.flushBuffer();
183 return false;
184 }
185 } catch (final IOException e) {
186 this.log.warn("error logging in user: {}", e.getMessage());
187 this.log.trace("", e);
188 this.sendUnauthorized(response);
189 return false;
190 }
191
192
193 final IWindowsIdentity windowsIdentity = securityContext.getIdentity();
194
195
196 if (!this.allowGuestLogin && windowsIdentity.isGuest()) {
197 this.log.warn("guest login disabled: {}", windowsIdentity.getFqn());
198 this.sendUnauthorized(response);
199 return false;
200 }
201
202 try {
203
204 this.log.debug("logged in user: {} ({})", windowsIdentity.getFqn(), windowsIdentity.getSidString());
205
206 final GenericPrincipal genericPrincipal = this.createPrincipal(windowsIdentity);
207
208 if (this.log.isDebugEnabled()) {
209 this.log.debug("roles: {}", String.join(", ", genericPrincipal.getRoles()));
210 }
211
212
213 final HttpSession session = request.getSession(true);
214 this.log.debug("session id: {}", session == null ? "null" : session.getId());
215
216 this.register(request, response, genericPrincipal, securityPackage, genericPrincipal.getName(), null);
217 this.log.info("successfully logged in user: {}", genericPrincipal.getName());
218
219 } finally {
220 windowsIdentity.dispose();
221 }
222
223 return true;
224 }
225
226
227
228
229
230
231
232
233
234
235
236 private boolean post(final Request request, final HttpServletResponse response) {
237
238 final String username = request.getParameter("j_username");
239 final String password = request.getParameter("j_password");
240
241 this.log.debug("logging in: {}", username);
242
243 IWindowsIdentity windowsIdentity;
244 try {
245 windowsIdentity = this.auth.logonUser(username, password);
246 } catch (final Exception e) {
247 this.log.error(e.getMessage());
248 this.log.trace("", e);
249 return false;
250 }
251
252
253 if (!this.allowGuestLogin && windowsIdentity.isGuest()) {
254 this.log.warn("guest login disabled: {}", windowsIdentity.getFqn());
255 return false;
256 }
257
258 try {
259 this.log.debug("successfully logged in {} ({})", username, windowsIdentity.getSidString());
260
261 final GenericPrincipal genericPrincipal = this.createPrincipal(windowsIdentity);
262
263 if (this.log.isDebugEnabled()) {
264 this.log.debug("roles: {}", String.join(", ", genericPrincipal.getRoles()));
265 }
266
267
268 final HttpSession session = request.getSession(true);
269 this.log.debug("session id: {}", session == null ? "null" : session.getId());
270
271 this.register(request, response, genericPrincipal, "FORM", genericPrincipal.getName(), null);
272 this.log.info("successfully logged in user: {}", genericPrincipal.getName());
273 } finally {
274 windowsIdentity.dispose();
275 }
276
277 return true;
278 }
279
280
281
282
283
284
285
286
287
288
289
290 private void redirectTo(final Request request, final HttpServletResponse response, final String url) {
291 try {
292 this.log.debug("redirecting to: {}", url);
293 final ServletContext servletContext = this.context.getServletContext();
294 final RequestDispatcher disp = servletContext.getRequestDispatcher(url);
295 disp.forward(request.getRequest(), response);
296 } catch (final IOException | ServletException e) {
297 throw new RuntimeException(e);
298 }
299 }
300
301
302
303
304
305 @Override
306 protected boolean doAuthenticate(final Request request, final HttpServletResponse response) throws IOException {
307 return this.authenticate(request, response);
308 }
309
310 }