]> git.argeo.org Git - lgpl/argeo-commons.git/blob - kernel/NodeHttp.java
Prepare next development cycle
[lgpl/argeo-commons.git] / kernel / NodeHttp.java
1 package org.argeo.cms.internal.kernel;
2
3 import java.io.IOException;
4 import java.security.PrivilegedActionException;
5 import java.security.PrivilegedExceptionAction;
6 import java.security.cert.X509Certificate;
7 import java.util.Enumeration;
8 import java.util.Properties;
9 import java.util.StringTokenizer;
10
11 import javax.jcr.Repository;
12 import javax.security.auth.Subject;
13 import javax.security.auth.callback.Callback;
14 import javax.security.auth.callback.CallbackHandler;
15 import javax.security.auth.callback.NameCallback;
16 import javax.security.auth.callback.PasswordCallback;
17 import javax.security.auth.login.LoginContext;
18 import javax.security.auth.login.LoginException;
19 import javax.servlet.FilterChain;
20 import javax.servlet.Servlet;
21 import javax.servlet.ServletException;
22 import javax.servlet.http.HttpServletRequest;
23 import javax.servlet.http.HttpServletResponse;
24 import javax.servlet.http.HttpSession;
25
26 import org.apache.commons.codec.binary.Base64;
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 import org.argeo.cms.CmsException;
30 import org.argeo.cms.KernelHeader;
31 import org.argeo.jackrabbit.servlet.OpenInViewSessionProvider;
32 import org.argeo.jackrabbit.servlet.RemotingServlet;
33 import org.argeo.jackrabbit.servlet.WebdavServlet;
34 import org.argeo.jcr.ArgeoJcrConstants;
35 import org.eclipse.equinox.http.servlet.ExtendedHttpService;
36 import org.osgi.service.http.NamespaceException;
37
38 /**
39 * Intercepts and enriches http access, mainly focusing on security and
40 * transactionality.
41 */
42 class NodeHttp implements KernelConstants, ArgeoJcrConstants {
43 private final static Log log = LogFactory.getLog(NodeHttp.class);
44
45 private final static String ATTR_AUTH = "auth";
46 private final static String HEADER_AUTHORIZATION = "Authorization";
47 private final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
48
49 // private final AuthenticationManager authenticationManager;
50 private final ExtendedHttpService httpService;
51
52 // FIXME Make it more unique
53 private String httpAuthRealm = "Argeo";
54
55 // Filters
56 private final RootFilter rootFilter;
57 // private final DoSFilter dosFilter;
58 // private final QoSFilter qosFilter;
59
60 // WebDav / JCR remoting
61 private OpenInViewSessionProvider sessionProvider;
62
63 NodeHttp(ExtendedHttpService httpService, JackrabbitNode node) {
64 // this.bundleContext = bundleContext;
65 // this.authenticationManager = authenticationManager;
66
67 this.httpService = httpService;
68
69 // Filters
70 rootFilter = new RootFilter();
71 // dosFilter = new CustomDosFilter();
72 // qosFilter = new QoSFilter();
73
74 // DAV
75 sessionProvider = new OpenInViewSessionProvider();
76
77 registerRepositoryServlets(ALIAS_NODE, node);
78 try {
79 httpService.registerFilter("/", rootFilter, null, null);
80 } catch (Exception e) {
81 throw new CmsException("Could not register root filter", e);
82 }
83 }
84
85 public void destroy() {
86 sessionProvider.destroy();
87 unregisterRepositoryServlets(ALIAS_NODE);
88 }
89
90 void registerRepositoryServlets(String alias, Repository repository) {
91 try {
92 registerWebdavServlet(alias, repository, true);
93 registerWebdavServlet(alias, repository, false);
94 registerRemotingServlet(alias, repository, true);
95 registerRemotingServlet(alias, repository, false);
96 } catch (Exception e) {
97 throw new CmsException(
98 "Could not register servlets for repository " + alias, e);
99 }
100 }
101
102 void unregisterRepositoryServlets(String alias) {
103 // FIXME unregister servlets
104 }
105
106 void registerWebdavServlet(String alias, Repository repository,
107 boolean anonymous) throws NamespaceException, ServletException {
108 WebdavServlet webdavServlet = new WebdavServlet(repository,
109 sessionProvider);
110 String pathPrefix = anonymous ? WEBDAV_PUBLIC : WEBDAV_PRIVATE;
111 String path = pathPrefix + "/" + alias;
112 Properties ip = new Properties();
113 ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, WEBDAV_CONFIG);
114 ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
115 httpService.registerFilter(path, anonymous ? new AnonymousFilter()
116 : new DavFilter(), null, null);
117 // Cast to servlet because of a weird behaviour in Eclipse
118 httpService.registerServlet(path, (Servlet) webdavServlet, ip, null);
119 }
120
121 void registerRemotingServlet(String alias, Repository repository,
122 boolean anonymous) throws NamespaceException, ServletException {
123 String pathPrefix = anonymous ? REMOTING_PUBLIC : REMOTING_PRIVATE;
124 RemotingServlet remotingServlet = new RemotingServlet(repository,
125 sessionProvider);
126 String path = pathPrefix + "/" + alias;
127 Properties ip = new Properties();
128 ip.setProperty(RemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
129
130 // Looks like a bug in Jackrabbit remoting init
131 ip.setProperty(RemotingServlet.INIT_PARAM_HOME,
132 KernelUtils.getOsgiInstanceDir() + "/tmp/jackrabbit");
133 ip.setProperty(RemotingServlet.INIT_PARAM_TMP_DIRECTORY, "remoting");
134 // Cast to servlet because of a weird behaviour in Eclipse
135 httpService.registerFilter(path, anonymous ? new AnonymousFilter()
136 : new DavFilter(), null, null);
137 httpService.registerServlet(path, (Servlet) remotingServlet, ip, null);
138 }
139
140 // private Boolean isSessionAuthenticated(HttpSession httpSession) {
141 // SecurityContext contextFromSession = (SecurityContext) httpSession
142 // .getAttribute(SPRING_SECURITY_CONTEXT_KEY);
143 // return contextFromSession != null;
144 // }
145
146 private void requestBasicAuth(HttpSession httpSession,
147 HttpServletResponse response) {
148 response.setStatus(401);
149 response.setHeader(HEADER_WWW_AUTHENTICATE, "basic realm=\""
150 + httpAuthRealm + "\"");
151 httpSession.setAttribute(ATTR_AUTH, Boolean.TRUE);
152 }
153
154 private CallbackHandler basicAuth(String authHeader) {
155 if (authHeader != null) {
156 StringTokenizer st = new StringTokenizer(authHeader);
157 if (st.hasMoreTokens()) {
158 String basic = st.nextToken();
159 if (basic.equalsIgnoreCase("Basic")) {
160 try {
161 // TODO manipulate char[]
162 String credentials = new String(Base64.decodeBase64(st
163 .nextToken()), "UTF-8");
164 // log.debug("Credentials: " + credentials);
165 int p = credentials.indexOf(":");
166 if (p != -1) {
167 final String login = credentials.substring(0, p)
168 .trim();
169 final char[] password = credentials
170 .substring(p + 1).trim().toCharArray();
171
172 return new CallbackHandler() {
173 public void handle(Callback[] callbacks) {
174 for (Callback cb : callbacks) {
175 if (cb instanceof NameCallback)
176 ((NameCallback) cb).setName(login);
177 else if (cb instanceof PasswordCallback)
178 ((PasswordCallback) cb)
179 .setPassword(password);
180 }
181 }
182 };
183 } else {
184 throw new CmsException(
185 "Invalid authentication token");
186 }
187 } catch (Exception e) {
188 throw new CmsException(
189 "Couldn't retrieve authentication", e);
190 }
191 }
192 }
193 }
194 throw new CmsException("Couldn't retrieve authentication");
195 }
196
197 /** Intercepts all requests. Authenticates. */
198 class RootFilter extends HttpFilter {
199
200 @Override
201 public void doFilter(HttpSession httpSession,
202 HttpServletRequest request, HttpServletResponse response,
203 FilterChain filterChain) throws IOException, ServletException {
204 if (log.isTraceEnabled()) {
205 log.trace(request.getRequestURL().append(
206 request.getQueryString() != null ? "?"
207 + request.getQueryString() : ""));
208 logRequest(request);
209 }
210
211 String servletPath = request.getServletPath();
212
213 // client certificate
214 X509Certificate clientCert = extractCertificate(request);
215 if (clientCert != null) {
216 // TODO authenticate
217 // if (log.isDebugEnabled())
218 // log.debug(clientCert.getSubjectX500Principal().getName());
219 }
220
221 // skip data
222 if (servletPath.startsWith(PATH_DATA)) {
223 filterChain.doFilter(request, response);
224 return;
225 }
226
227 // skip /ui (workbench) for the time being
228 if (servletPath.startsWith(PATH_WORKBENCH)) {
229 filterChain.doFilter(request, response);
230 return;
231 }
232
233 // redirect long RWT paths to anchor
234 String path = request.getRequestURI().substring(
235 servletPath.length());
236 int pathLength = path.length();
237 if (pathLength != 0 && (path.charAt(0) == '/')
238 && !servletPath.endsWith("rwt-resources")
239 && path.lastIndexOf('/') != 0) {
240 String newLocation = request.getServletPath() + "#" + path;
241 response.setHeader("Location", newLocation);
242 response.setStatus(HttpServletResponse.SC_FOUND);
243 return;
244 }
245
246 // process normally
247 filterChain.doFilter(request, response);
248 }
249 }
250
251 private void logRequest(HttpServletRequest request) {
252 log.debug("contextPath=" + request.getContextPath());
253 log.debug("servletPath=" + request.getServletPath());
254 log.debug("requestURI=" + request.getRequestURI());
255 log.debug("queryString=" + request.getQueryString());
256 StringBuilder buf = new StringBuilder();
257 // headers
258 Enumeration<String> en = request.getHeaderNames();
259 while (en.hasMoreElements()) {
260 String header = en.nextElement();
261 Enumeration<String> values = request.getHeaders(header);
262 while (values.hasMoreElements())
263 buf.append(" " + header + ": " + values.nextElement());
264 buf.append('\n');
265 }
266
267 // attributed
268 Enumeration<String> an = request.getAttributeNames();
269 while (an.hasMoreElements()) {
270 String attr = an.nextElement();
271 Object value = request.getAttribute(attr);
272 buf.append(" " + attr + ": " + value);
273 buf.append('\n');
274 }
275 log.debug("\n" + buf);
276 }
277
278 private X509Certificate extractCertificate(HttpServletRequest req) {
279 X509Certificate[] certs = (X509Certificate[]) req
280 .getAttribute("javax.servlet.request.X509Certificate");
281 if (null != certs && certs.length > 0) {
282 return certs[0];
283 }
284 return null;
285 }
286
287 /** Intercepts all requests. Authenticates. */
288 private class AnonymousFilter extends HttpFilter {
289 @Override
290 public void doFilter(HttpSession httpSession,
291 final HttpServletRequest request,
292 final HttpServletResponse response,
293 final FilterChain filterChain) throws IOException,
294 ServletException {
295
296 // Authenticate from session
297 // if (isSessionAuthenticated(httpSession)) {
298 // filterChain.doFilter(request, response);
299 // return;
300 // }
301
302 Subject subject = KernelUtils.anonymousLogin();
303 try {
304 Subject.doAs(subject, new PrivilegedExceptionAction<Void>() {
305 public Void run() throws IOException, ServletException {
306 filterChain.doFilter(request, response);
307 return null;
308 }
309 });
310 } catch (PrivilegedActionException e) {
311 if (e.getCause() instanceof ServletException)
312 throw (ServletException) e.getCause();
313 else if (e.getCause() instanceof IOException)
314 throw (IOException) e.getCause();
315 else
316 throw new CmsException("Unexpected exception", e.getCause());
317 }
318 }
319 }
320
321 /** Intercepts all requests. Authenticates. */
322 private class DavFilter extends HttpFilter {
323
324 @Override
325 public void doFilter(HttpSession httpSession,
326 final HttpServletRequest request,
327 final HttpServletResponse response,
328 final FilterChain filterChain) throws IOException,
329 ServletException {
330
331 // Process basic auth
332 String basicAuth = request.getHeader(HEADER_AUTHORIZATION);
333 if (basicAuth != null) {
334 CallbackHandler token = basicAuth(basicAuth);
335 // FIXME Login
336 // Authentication auth =
337 // authenticationManager.authenticate(token);
338 // SecurityContextHolder.getContext().setAuthentication(auth);
339 // filterChain.doFilter(request, response);
340 Subject subject;
341 try {
342 LoginContext lc = new LoginContext(
343 KernelHeader.LOGIN_CONTEXT_USER, token);
344 lc.login();
345 subject = lc.getSubject();
346 } catch (LoginException e) {
347 throw new CmsException("Could not login", e);
348 }
349 try {
350 Subject.doAs(subject,
351 new PrivilegedExceptionAction<Void>() {
352 public Void run() throws IOException,
353 ServletException {
354 filterChain.doFilter(request, response);
355 return null;
356 }
357 });
358 } catch (PrivilegedActionException e) {
359 if (e.getCause() instanceof ServletException)
360 throw (ServletException) e.getCause();
361 else if (e.getCause() instanceof IOException)
362 throw (IOException) e.getCause();
363 else
364 throw new CmsException("Unexpected exception",
365 e.getCause());
366 }
367 return;
368 }
369
370 requestBasicAuth(httpSession, response);
371 }
372 }
373
374 // class CustomDosFilter extends DoSFilter {
375 // @Override
376 // protected String extractUserId(ServletRequest request) {
377 // HttpSession httpSession = ((HttpServletRequest) request)
378 // .getSession();
379 // if (isSessionAuthenticated(httpSession)) {
380 // String userId = ((SecurityContext) httpSession
381 // .getAttribute(SPRING_SECURITY_CONTEXT_KEY))
382 // .getAuthentication().getName();
383 // return userId;
384 // }
385 // return super.extractUserId(request);
386 //
387 // }
388 // }
389 }