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 javax.annotation.PostConstruct;
23 import javax.servlet.FilterConfig;
24 import javax.servlet.ServletContext;
25 import javax.servlet.http.HttpServletResponse;
26
27 import org.apache.logging.log4j.LogManager;
28 import org.apache.logging.log4j.Logger;
29 import org.codelibs.core.io.ResourceUtil;
30 import org.codelibs.core.lang.StringUtil;
31 import org.codelibs.fess.app.web.base.login.ActionResponseCredential;
32 import org.codelibs.fess.app.web.base.login.FessLoginAssist.LoginCredentialResolver;
33 import org.codelibs.fess.app.web.base.login.SpnegoCredential;
34 import org.codelibs.fess.exception.SsoLoginException;
35 import org.codelibs.fess.mylasta.action.FessUserBean;
36 import org.codelibs.fess.mylasta.direction.FessConfig;
37 import org.codelibs.fess.sso.SsoAuthenticator;
38 import org.codelibs.fess.sso.SsoResponseType;
39 import org.codelibs.fess.util.ComponentUtil;
40 import org.codelibs.spnego.SpnegoFilterConfig;
41 import org.codelibs.spnego.SpnegoHttpFilter;
42 import org.codelibs.spnego.SpnegoHttpFilter.Constants;
43 import org.codelibs.spnego.SpnegoHttpServletResponse;
44 import org.codelibs.spnego.SpnegoPrincipal;
45 import org.dbflute.optional.OptionalEntity;
46 import org.lastaflute.web.login.credential.LoginCredential;
47 import org.lastaflute.web.response.ActionResponse;
48 import org.lastaflute.web.servlet.filter.RequestLoggingFilter;
49 import org.lastaflute.web.util.LaRequestUtil;
50 import org.lastaflute.web.util.LaResponseUtil;
51
52 public class SpnegoAuthenticator implements SsoAuthenticator {
53
54 private static final Logger logger = LogManager.getLogger(SpnegoAuthenticator.class);
55
56 protected static final String SPNEGO_INITIALIZED = "spnego.initialized";
57 protected static final String SPNEGO_EXCLUDE_DIRS = "spnego.exclude.dirs";
58 protected static final String SPNEGO_ALLOW_DELEGATION = "spnego.allow.delegation";
59 protected static final String SPNEGO_ALLOW_LOCALHOST = "spnego.allow.localhost";
60 protected static final String SPNEGO_PROMPT_NTLM = "spnego.prompt.ntlm";
61 protected static final String SPNEGO_ALLOW_UNSECURE_BASIC = "spnego.allow.unsecure.basic";
62 protected static final String SPNEGO_ALLOW_BASIC = "spnego.allow.basic";
63 protected static final String SPNEGO_PREAUTH_PASSWORD = "spnego.preauth.password";
64 protected static final String SPNEGO_PREAUTH_USERNAME = "spnego.preauth.username";
65 protected static final String SPNEGO_LOGIN_SERVER_MODULE = "spnego.login.server.module";
66 protected static final String SPNEGO_LOGIN_CLIENT_MODULE = "spnego.login.client.module";
67 protected static final String SPNEGO_KRB5_CONF = "spnego.krb5.conf";
68 protected static final String SPNEGO_LOGIN_CONF = "spnego.login.conf";
69 protected static final String SPNEGO_LOGGER_LEVEL = "spnego.logger.level";
70
71 protected org.codelibs.spnego.SpnegoAuthenticator authenticator = null;
72
73 @PostConstruct
74 public void init() {
75 if (logger.isDebugEnabled()) {
76 logger.debug("Initialize {}", this.getClass().getSimpleName());
77 }
78 ComponentUtil.getSsoManager().register(this);
79 }
80
81 protected synchronized org.codelibs.spnego.SpnegoAuthenticator getAuthenticator() {
82 final FessConfig fessConfig = ComponentUtil.getFessConfig();
83 if (authenticator != null && fessConfig.getSystemPropertyAsBoolean(SPNEGO_INITIALIZED, false)) {
84 return authenticator;
85 }
86 try {
87
88 final SpnegoFilterConfig config = SpnegoFilterConfig.getInstance(new SpengoConfig());
89
90
91 authenticator = new org.codelibs.spnego.SpnegoAuthenticator(config);
92
93 fessConfig.setSystemPropertyAsBoolean(SPNEGO_INITIALIZED, true);
94 fessConfig.storeSystemProperties();
95 return authenticator;
96 } catch (final Exception e) {
97 throw new SsoLoginException("Failed to initialize SPNEGO.", e);
98 }
99 }
100
101
102
103
104 @Override
105 public LoginCredential getLoginCredential() {
106 return LaRequestUtil.getOptionalRequest().map(request -> {
107 if (logger.isDebugEnabled()) {
108 logger.debug("Logging in with SPNEGO Authenticator");
109 }
110 final HttpServletResponse response = LaResponseUtil.getResponse();
111 final SpnegoHttpServletResponse spnegoResponse = new SpnegoHttpServletResponse(response);
112
113
114 final SpnegoPrincipal principal;
115 try {
116 principal = getAuthenticator().authenticate(request, spnegoResponse);
117 if (logger.isDebugEnabled()) {
118 logger.debug("principal: {}", principal);
119 }
120 } catch (final Exception e) {
121 final String msg = "HTTP Authorization Header=" + request.getHeader(Constants.AUTHZ_HEADER);
122 if (logger.isDebugEnabled()) {
123 logger.debug(msg);
124 }
125 throw new SsoLoginException(msg, e);
126 }
127
128
129 final boolean status = spnegoResponse.isStatusSet();
130 if (logger.isDebugEnabled()) {
131 logger.debug("isStatusSet: {}", status);
132 }
133 if (status) {
134 return new ActionResponseCredential(() -> {
135 throw new RequestLoggingFilter.RequestClientErrorException("Your request is not authorized.", "401 Unauthorized",
136 HttpServletResponse.SC_UNAUTHORIZED);
137 });
138 }
139
140
141 if (null == principal) {
142 final String msg = "Principal was null.";
143 if (logger.isDebugEnabled()) {
144 logger.debug(msg);
145 }
146 throw new SsoLoginException(msg);
147 }
148
149 if (logger.isDebugEnabled()) {
150 logger.debug("principal={}", principal);
151 }
152
153 final String[] username = principal.getName().split("@", 2);
154 if (logger.isDebugEnabled()) {
155 logger.debug("username: {}", Arrays.toString(username));
156 }
157 return new SpnegoCredential(username[0]);
158 }).orElseGet(() -> null);
159
160 }
161
162 protected class SpengoConfig implements FilterConfig {
163
164 @Override
165 public String getFilterName() {
166 return SpnegoAuthenticator.class.getName();
167 }
168
169 @Override
170 public ServletContext getServletContext() {
171 throw new UnsupportedOperationException();
172 }
173
174 @Override
175 public String getInitParameter(final String name) {
176 if (SpnegoHttpFilter.Constants.LOGGER_LEVEL.equals(name)) {
177 final String logLevel = getProperty(SPNEGO_LOGGER_LEVEL, StringUtil.EMPTY);
178 if (StringUtil.isNotBlank(logLevel)) {
179 return logLevel;
180 }
181 if (logger.isDebugEnabled()) {
182 return "3";
183 } else if (logger.isInfoEnabled()) {
184 return "5";
185 } else if (logger.isWarnEnabled()) {
186 return "6";
187 } else if (logger.isErrorEnabled()) {
188 return "7";
189 } else {
190 return "0";
191 }
192 }
193 if (SpnegoHttpFilter.Constants.LOGIN_CONF.equals(name)) {
194 return getResourcePath(getProperty(SPNEGO_LOGIN_CONF, "auth_login.conf"));
195 }
196 if (SpnegoHttpFilter.Constants.KRB5_CONF.equals(name)) {
197 return getResourcePath(getProperty(SPNEGO_KRB5_CONF, "krb5.conf"));
198 } else if (SpnegoHttpFilter.Constants.CLIENT_MODULE.equals(name)) {
199 return getProperty(SPNEGO_LOGIN_CLIENT_MODULE, "spnego-client");
200 } else if (SpnegoHttpFilter.Constants.SERVER_MODULE.equals(name)) {
201 return getProperty(SPNEGO_LOGIN_SERVER_MODULE, "spnego-server");
202 } else if (SpnegoHttpFilter.Constants.PREAUTH_USERNAME.equals(name)) {
203 return getProperty(SPNEGO_PREAUTH_USERNAME, "username");
204 } else if (SpnegoHttpFilter.Constants.PREAUTH_PASSWORD.equals(name)) {
205 return getProperty(SPNEGO_PREAUTH_PASSWORD, "password");
206 } else if (SpnegoHttpFilter.Constants.ALLOW_BASIC.equals(name)) {
207 return getProperty(SPNEGO_ALLOW_BASIC, "true");
208 } else if (SpnegoHttpFilter.Constants.ALLOW_UNSEC_BASIC.equals(name)) {
209 return getProperty(SPNEGO_ALLOW_UNSECURE_BASIC, "true");
210 } else if (SpnegoHttpFilter.Constants.PROMPT_NTLM.equals(name)) {
211 return getProperty(SPNEGO_PROMPT_NTLM, "true");
212 } else if (SpnegoHttpFilter.Constants.ALLOW_LOCALHOST.equals(name)) {
213 return getProperty(SPNEGO_ALLOW_LOCALHOST, "true");
214 } else if (SpnegoHttpFilter.Constants.ALLOW_DELEGATION.equals(name)) {
215 return getProperty(SPNEGO_ALLOW_DELEGATION, "false");
216 } else if (SpnegoHttpFilter.Constants.EXCLUDE_DIRS.equals(name)) {
217 return getProperty(SPNEGO_EXCLUDE_DIRS, StringUtil.EMPTY);
218 }
219 return null;
220 }
221
222 protected String getProperty(final String key, final String defaultValue) {
223 return ComponentUtil.getSystemProperties().getProperty(key, defaultValue);
224 }
225
226 protected String getResourcePath(final String path) {
227 final File file = ResourceUtil.getResourceAsFileNoException(path);
228 if (file != null) {
229 return file.getAbsolutePath();
230 }
231 return null;
232 }
233
234 @Override
235 public Enumeration<String> getInitParameterNames() {
236 throw new UnsupportedOperationException();
237 }
238
239 }
240
241 @Override
242 public void resolveCredential(final LoginCredentialResolver resolver) {
243 resolver.resolve(SpnegoCredential.class, credential -> {
244 final String username = credential.getUserId();
245 if (!ComponentUtil.getFessConfig().isAdminUser(username)) {
246 return ComponentUtil.getLdapManager().login(username);
247 }
248 return OptionalEntity.empty();
249 });
250 }
251
252 @Override
253 public ActionResponse getResponse(final SsoResponseType responseType) {
254 return null;
255 }
256
257 @Override
258 public String logout(final FessUserBean user) {
259 return null;
260 }
261
262 }