1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.sso.spnego;
17
18 import java.io.File;
19 import java.util.Arrays;
20 import java.util.Enumeration;
21
22 import org.apache.logging.log4j.LogManager;
23 import org.apache.logging.log4j.Logger;
24 import org.codelibs.core.io.ResourceUtil;
25 import org.codelibs.core.lang.StringUtil;
26 import org.codelibs.fess.app.web.base.login.ActionResponseCredential;
27 import org.codelibs.fess.app.web.base.login.FessLoginAssist.LoginCredentialResolver;
28 import org.codelibs.fess.app.web.base.login.SpnegoCredential;
29 import org.codelibs.fess.exception.SsoLoginException;
30 import org.codelibs.fess.mylasta.action.FessUserBean;
31 import org.codelibs.fess.mylasta.direction.FessConfig;
32 import org.codelibs.fess.sso.SsoAuthenticator;
33 import org.codelibs.fess.sso.SsoResponseType;
34 import org.codelibs.fess.util.ComponentUtil;
35 import org.codelibs.spnego.SpnegoFilterConfig;
36 import org.codelibs.spnego.SpnegoHttpFilter;
37 import org.codelibs.spnego.SpnegoHttpFilter.Constants;
38 import org.codelibs.spnego.SpnegoHttpServletResponse;
39 import org.codelibs.spnego.SpnegoPrincipal;
40 import org.dbflute.optional.OptionalEntity;
41 import org.lastaflute.web.login.credential.LoginCredential;
42 import org.lastaflute.web.response.ActionResponse;
43 import org.lastaflute.web.servlet.filter.RequestLoggingFilter;
44 import org.lastaflute.web.util.LaRequestUtil;
45 import org.lastaflute.web.util.LaResponseUtil;
46
47 import jakarta.annotation.PostConstruct;
48 import jakarta.servlet.FilterConfig;
49 import jakarta.servlet.ServletContext;
50 import jakarta.servlet.http.HttpServletResponse;
51
52
53
54
55
56
57
58
59
60
61
62
63 public class SpnegoAuthenticator implements SsoAuthenticator {
64
65
66 private static final Logger logger = LogManager.getLogger(SpnegoAuthenticator.class);
67
68
69 protected static final String SPNEGO_INITIALIZED = "spnego.initialized";
70
71
72 protected static final String SPNEGO_EXCLUDE_DIRS = "spnego.exclude.dirs";
73
74
75 protected static final String SPNEGO_ALLOW_DELEGATION = "spnego.allow.delegation";
76
77
78 protected static final String SPNEGO_ALLOW_LOCALHOST = "spnego.allow.localhost";
79
80
81 protected static final String SPNEGO_PROMPT_NTLM = "spnego.prompt.ntlm";
82
83
84 protected static final String SPNEGO_ALLOW_UNSECURE_BASIC = "spnego.allow.unsecure.basic";
85
86
87 protected static final String SPNEGO_ALLOW_BASIC = "spnego.allow.basic";
88
89
90 protected static final String SPNEGO_PREAUTH_PASSWORD = "spnego.preauth.password";
91
92
93 protected static final String SPNEGO_PREAUTH_USERNAME = "spnego.preauth.username";
94
95
96 protected static final String SPNEGO_LOGIN_SERVER_MODULE = "spnego.login.server.module";
97
98
99 protected static final String SPNEGO_LOGIN_CLIENT_MODULE = "spnego.login.client.module";
100
101
102 protected static final String SPNEGO_KRB5_CONF = "spnego.krb5.conf";
103
104
105 protected static final String SPNEGO_LOGIN_CONF = "spnego.login.conf";
106
107
108 protected static final String SPNEGO_LOGGER_LEVEL = "spnego.logger.level";
109
110
111 protected org.codelibs.spnego.SpnegoAuthenticator authenticator = null;
112
113
114
115
116 public SpnegoAuthenticator() {
117
118 }
119
120
121
122
123
124 @PostConstruct
125 public void init() {
126 if (logger.isDebugEnabled()) {
127 logger.debug("Initializing {}", this.getClass().getSimpleName());
128 }
129 ComponentUtil.getSsoManager().register(this);
130 }
131
132
133
134
135
136
137
138
139
140
141
142 protected synchronized org.codelibs.spnego.SpnegoAuthenticator getAuthenticator() {
143 final FessConfig fessConfig = ComponentUtil.getFessConfig();
144 if (authenticator != null && fessConfig.getSystemPropertyAsBoolean(SPNEGO_INITIALIZED, false)) {
145 return authenticator;
146 }
147 try {
148
149 final SpnegoFilterConfig config = SpnegoFilterConfig.getInstance(new SpnegoConfig());
150
151
152 authenticator = new org.codelibs.spnego.SpnegoAuthenticator(config);
153
154 fessConfig.setSystemPropertyAsBoolean(SPNEGO_INITIALIZED, true);
155 fessConfig.storeSystemProperties();
156 return authenticator;
157 } catch (final Exception e) {
158 throw new SsoLoginException("Failed to initialize SPNEGO.", e);
159 }
160 }
161
162
163
164
165
166
167
168
169
170
171
172
173
174 @Override
175 public LoginCredential getLoginCredential() {
176 return LaRequestUtil.getOptionalRequest().map(request -> {
177 if (logger.isDebugEnabled()) {
178 logger.debug("Logging in with SPNEGO Authenticator");
179 }
180 final HttpServletResponse response = LaResponseUtil.getResponse();
181 final SpnegoHttpServletResponse spnegoResponse = new SpnegoHttpServletResponse(response);
182
183
184 final SpnegoPrincipal principal;
185 try {
186 principal = getAuthenticator().authenticate(request, spnegoResponse);
187 if (logger.isDebugEnabled()) {
188 logger.debug("principal={}", principal);
189 }
190 } catch (final Exception e) {
191 final String authzHeader = request.getHeader(Constants.AUTHZ_HEADER);
192 final String maskedHeader;
193 if (authzHeader == null) {
194 maskedHeader = "null";
195 } else if (authzHeader.length() <= 10) {
196 maskedHeader = "***";
197 } else {
198 maskedHeader = authzHeader.substring(0, 10) + "***";
199 }
200 final String msg = "Failed to process Authorization Header: " + maskedHeader;
201 if (logger.isDebugEnabled()) {
202 logger.debug(msg);
203 }
204 throw new SsoLoginException(e.getMessage() + " " + msg, e);
205 }
206
207
208 final boolean status = spnegoResponse.isStatusSet();
209 if (logger.isDebugEnabled()) {
210 logger.debug("isStatusSet={}", status);
211 }
212 if (status) {
213 return new ActionResponseCredential(() -> {
214 throw new RequestLoggingFilter.RequestClientErrorException("Your request is not authorized.", "401 Unauthorized",
215 HttpServletResponse.SC_UNAUTHORIZED);
216 });
217 }
218
219
220 if (null == principal) {
221 final String msg = "Principal was null.";
222 if (logger.isDebugEnabled()) {
223 logger.debug(msg);
224 }
225 throw new SsoLoginException(msg);
226 }
227
228 if (logger.isDebugEnabled()) {
229 logger.debug("principal={}", principal);
230 }
231
232 final String[] username = principal.getName().split("@", 2);
233 if (logger.isDebugEnabled()) {
234 logger.debug("username={}", Arrays.toString(username));
235 }
236 return new SpnegoCredential(username[0]);
237 }).orElse(null);
238
239 }
240
241
242
243
244
245
246
247
248
249 protected static class SpnegoConfig implements FilterConfig {
250
251
252
253
254 public SpnegoConfig() {
255
256 }
257
258
259
260
261
262
263 @Override
264 public String getFilterName() {
265 return SpnegoAuthenticator.class.getName();
266 }
267
268
269
270
271
272
273
274 @Override
275 public ServletContext getServletContext() {
276 throw new UnsupportedOperationException("getServletContext() is not supported in SpnegoFilterConfig");
277 }
278
279
280
281
282
283
284
285
286
287
288
289 @Override
290 public String getInitParameter(final String name) {
291 if (SpnegoHttpFilter.Constants.LOGGER_LEVEL.equals(name)) {
292 final String logLevel = getProperty(SPNEGO_LOGGER_LEVEL, StringUtil.EMPTY);
293 if (StringUtil.isNotBlank(logLevel)) {
294 return logLevel;
295 }
296 if (logger.isDebugEnabled()) {
297 return "3";
298 }
299 if (logger.isInfoEnabled()) {
300 return "5";
301 }
302 if (logger.isWarnEnabled()) {
303 return "6";
304 }
305 if (logger.isErrorEnabled()) {
306 return "7";
307 }
308 return "0";
309 }
310 if (SpnegoHttpFilter.Constants.LOGIN_CONF.equals(name)) {
311 return getResourcePath(getProperty(SPNEGO_LOGIN_CONF, "auth_login.conf"));
312 }
313 if (SpnegoHttpFilter.Constants.KRB5_CONF.equals(name)) {
314 return getResourcePath(getProperty(SPNEGO_KRB5_CONF, "krb5.conf"));
315 }
316 if (SpnegoHttpFilter.Constants.CLIENT_MODULE.equals(name)) {
317 return getProperty(SPNEGO_LOGIN_CLIENT_MODULE, "spnego-client");
318 }
319 if (SpnegoHttpFilter.Constants.SERVER_MODULE.equals(name)) {
320 return getProperty(SPNEGO_LOGIN_SERVER_MODULE, "spnego-server");
321 }
322 if (SpnegoHttpFilter.Constants.PREAUTH_USERNAME.equals(name)) {
323 return getProperty(SPNEGO_PREAUTH_USERNAME, "username");
324 }
325 if (SpnegoHttpFilter.Constants.PREAUTH_PASSWORD.equals(name)) {
326 return getProperty(SPNEGO_PREAUTH_PASSWORD, "password");
327 }
328 if (SpnegoHttpFilter.Constants.ALLOW_BASIC.equals(name)) {
329
330
331 return getProperty(SPNEGO_ALLOW_BASIC, "true");
332 }
333 if (SpnegoHttpFilter.Constants.ALLOW_UNSEC_BASIC.equals(name)) {
334
335
336
337
338 return getProperty(SPNEGO_ALLOW_UNSECURE_BASIC, "true");
339 }
340 if (SpnegoHttpFilter.Constants.PROMPT_NTLM.equals(name)) {
341 return getProperty(SPNEGO_PROMPT_NTLM, "true");
342 }
343 if (SpnegoHttpFilter.Constants.ALLOW_LOCALHOST.equals(name)) {
344 return getProperty(SPNEGO_ALLOW_LOCALHOST, "true");
345 }
346 if (SpnegoHttpFilter.Constants.ALLOW_DELEGATION.equals(name)) {
347 return getProperty(SPNEGO_ALLOW_DELEGATION, "false");
348 }
349 if (SpnegoHttpFilter.Constants.EXCLUDE_DIRS.equals(name)) {
350 return getProperty(SPNEGO_EXCLUDE_DIRS, StringUtil.EMPTY);
351 }
352 return null;
353 }
354
355
356
357
358
359
360
361
362 protected String getProperty(final String key, final String defaultValue) {
363 return ComponentUtil.getSystemProperties().getProperty(key, defaultValue);
364 }
365
366
367
368
369
370
371
372 protected String getResourcePath(final String path) {
373 final File file = ResourceUtil.getResourceAsFileNoException(path);
374 if (file != null) {
375 return file.getAbsolutePath();
376 }
377 return null;
378 }
379
380
381
382
383
384
385
386 @Override
387 public Enumeration<String> getInitParameterNames() {
388 throw new UnsupportedOperationException("getInitParameterNames() is not supported in SpnegoFilterConfig");
389 }
390
391 }
392
393
394
395
396
397
398
399
400
401 @Override
402 public void resolveCredential(final LoginCredentialResolver resolver) {
403 resolver.resolve(SpnegoCredential.class, credential -> {
404 final String username = credential.getUserId();
405 if (!ComponentUtil.getFessConfig().isAdminUser(username)) {
406 return ComponentUtil.getLdapManager().login(username);
407 }
408 return OptionalEntity.empty();
409 });
410 }
411
412
413
414
415
416
417
418
419
420
421 @Override
422 public ActionResponse getResponse(final SsoResponseType responseType) {
423 return null;
424 }
425
426
427
428
429
430
431
432
433
434
435 @Override
436 public String logout(final FessUserBean user) {
437 return null;
438 }
439
440 }