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