]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java
- Improve CMS login (HTTP session now supported)
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / 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(
80 "Could not register root filter", e);
81 }
82 }
83
84 public void destroy() {
85 sessionProvider.destroy();
86 unregisterRepositoryServlets(ALIAS_NODE);
87 }
88
89 void registerRepositoryServlets(String alias, Repository repository) {
90 try {
91 registerWebdavServlet(alias, repository, true);
92 registerWebdavServlet(alias, repository, false);
93 registerRemotingServlet(alias, repository, true);
94 registerRemotingServlet(alias, repository, false);
95 } catch (Exception e) {
96 throw new CmsException(
97 "Could not register servlets for repository " + alias, e);
98 }
99 }
100
101 void unregisterRepositoryServlets(String alias) {
102 // FIXME unregister servlets
103 }
104
105 void registerWebdavServlet(String alias, Repository repository,
106 boolean anonymous) throws NamespaceException, ServletException {
107 WebdavServlet webdavServlet = new WebdavServlet(repository,
108 sessionProvider);
109 String pathPrefix = anonymous ? WEBDAV_PUBLIC : WEBDAV_PRIVATE;
110 String path = pathPrefix + "/" + alias;
111 Properties ip = new Properties();
112 ip.setProperty(INIT_PARAM_RESOURCE_CONFIG, WEBDAV_CONFIG);
113 ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
114 httpService.registerFilter(path, anonymous ? new AnonymousFilter()
115 : new DavFilter(), null, null);
116 // Cast to servlet because of a weird behaviour in Eclipse
117 httpService.registerServlet(path, (Servlet) webdavServlet, ip, null);
118 }
119
120 void registerRemotingServlet(String alias, Repository repository,
121 boolean anonymous) throws NamespaceException, ServletException {
122 String pathPrefix = anonymous ? REMOTING_PUBLIC : REMOTING_PRIVATE;
123 RemotingServlet remotingServlet = new RemotingServlet(repository,
124 sessionProvider);
125 String path = pathPrefix + "/" + alias;
126 Properties ip = new Properties();
127 ip.setProperty(RemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
128
129 // Looks like a bug in Jackrabbit remoting init
130 ip.setProperty(RemotingServlet.INIT_PARAM_HOME,
131 KernelUtils.getOsgiInstanceDir() + "/tmp/jackrabbit");
132 ip.setProperty(RemotingServlet.INIT_PARAM_TMP_DIRECTORY, "remoting");
133 // Cast to servlet because of a weird behaviour in Eclipse
134 httpService.registerFilter(path, anonymous ? new AnonymousFilter()
135 : new DavFilter(), null, null);
136 httpService.registerServlet(path, (Servlet) remotingServlet, ip, null);
137 }
138
139 // private Boolean isSessionAuthenticated(HttpSession httpSession) {
140 // SecurityContext contextFromSession = (SecurityContext) httpSession
141 // .getAttribute(SPRING_SECURITY_CONTEXT_KEY);
142 // return contextFromSession != null;
143 // }
144
145 private void requestBasicAuth(HttpSession httpSession,
146 HttpServletResponse response) {
147 response.setStatus(401);
148 response.setHeader(HEADER_WWW_AUTHENTICATE, "basic realm=\""
149 + httpAuthRealm + "\"");
150 httpSession.setAttribute(ATTR_AUTH, Boolean.TRUE);
151 }
152
153 private NodeAuthenticationToken basicAuth(String authHeader) {
154 if (authHeader != null) {
155 StringTokenizer st = new StringTokenizer(authHeader);
156 if (st.hasMoreTokens()) {
157 String basic = st.nextToken();
158 if (basic.equalsIgnoreCase("Basic")) {
159 try {
160 String credentials = new String(Base64.decodeBase64(st
161 .nextToken()), "UTF-8");
162 // log.debug("Credentials: " + credentials);
163 int p = credentials.indexOf(":");
164 if (p != -1) {
165 String login = credentials.substring(0, p).trim();
166 String password = credentials.substring(p + 1)
167 .trim();
168
169 return new NodeAuthenticationToken(login,
170 password.toCharArray());
171 } else {
172 throw new CmsException(
173 "Invalid authentication token");
174 }
175 } catch (Exception e) {
176 throw new CmsException(
177 "Couldn't retrieve authentication", e);
178 }
179 }
180 }
181 }
182 throw new CmsException("Couldn't retrieve authentication");
183 }
184
185 /** Intercepts all requests. Authenticates. */
186 class RootFilter extends HttpFilter {
187
188 @Override
189 public void doFilter(HttpSession httpSession,
190 HttpServletRequest request, HttpServletResponse response,
191 FilterChain filterChain) throws IOException, ServletException {
192 if (log.isTraceEnabled())
193 logRequest(request);
194
195 String servletPath = request.getServletPath();
196
197 // client certificate
198 X509Certificate clientCert = extractCertificate(request);
199 if (clientCert != null) {
200 // TODO authenticate
201 // if (log.isDebugEnabled())
202 // log.debug(clientCert.getSubjectX500Principal().getName());
203 }
204
205 // skip data
206 if (servletPath.startsWith(PATH_DATA)) {
207 filterChain.doFilter(request, response);
208 return;
209 }
210
211 // skip /ui (workbench) for the time being
212 if (servletPath.startsWith(PATH_WORKBENCH)) {
213 filterChain.doFilter(request, response);
214 return;
215 }
216
217 // redirect long RWT paths to anchor
218 String path = request.getRequestURI().substring(
219 servletPath.length());
220 int pathLength = path.length();
221 if (pathLength != 0 && (path.charAt(0) == '/')
222 && !servletPath.endsWith("rwt-resources")
223 && path.lastIndexOf('/')!=0) {
224 String newLocation = request.getServletPath() + "#" + path;
225 response.setHeader("Location", newLocation);
226 response.setStatus(HttpServletResponse.SC_FOUND);
227 return;
228 }
229
230 // process normally
231 filterChain.doFilter(request, response);
232 }
233 }
234
235 private void logRequest(HttpServletRequest request) {
236 log.debug(request.getContextPath());
237 log.debug(request.getServletPath());
238 log.debug(request.getRequestURI());
239 log.debug(request.getQueryString());
240 StringBuilder buf = new StringBuilder();
241 // headers
242 Enumeration<String> en = request.getHeaderNames();
243 while (en.hasMoreElements()) {
244 String header = en.nextElement();
245 Enumeration<String> values = request.getHeaders(header);
246 while (values.hasMoreElements())
247 buf.append(" " + header + ": " + values.nextElement());
248 buf.append('\n');
249 }
250
251 // attributed
252 Enumeration<String> an = request.getAttributeNames();
253 while (an.hasMoreElements()) {
254 String attr = an.nextElement();
255 Object value = request.getAttribute(attr);
256 buf.append(" " + attr + ": " + value);
257 buf.append('\n');
258 }
259 log.debug("\n" + buf);
260 }
261
262 private X509Certificate extractCertificate(HttpServletRequest req) {
263 X509Certificate[] certs = (X509Certificate[]) req
264 .getAttribute("javax.servlet.request.X509Certificate");
265 if (null != certs && certs.length > 0) {
266 return certs[0];
267 }
268 return null;
269 }
270
271 /** Intercepts all requests. Authenticates. */
272 private class AnonymousFilter extends HttpFilter {
273 @Override
274 public void doFilter(HttpSession httpSession,
275 HttpServletRequest request, HttpServletResponse response,
276 FilterChain filterChain) throws IOException, ServletException {
277
278 // Authenticate from session
279 // if (isSessionAuthenticated(httpSession)) {
280 // filterChain.doFilter(request, response);
281 // return;
282 // }
283
284 KernelUtils.anonymousLogin(authenticationManager);
285 filterChain.doFilter(request, response);
286 }
287 }
288
289 /** Intercepts all requests. Authenticates. */
290 private class DavFilter extends HttpFilter {
291
292 @Override
293 public void doFilter(HttpSession httpSession,
294 HttpServletRequest request, HttpServletResponse response,
295 FilterChain filterChain) throws IOException, ServletException {
296
297 // Authenticate from session
298 // if (isSessionAuthenticated(httpSession)) {
299 // filterChain.doFilter(request, response);
300 // return;
301 // }
302
303 // Process basic auth
304 String basicAuth = request.getHeader(HEADER_AUTHORIZATION);
305 if (basicAuth != null) {
306 UsernamePasswordAuthenticationToken token = basicAuth(basicAuth);
307 Authentication auth = authenticationManager.authenticate(token);
308 SecurityContextHolder.getContext().setAuthentication(auth);
309 // httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY,
310 // SecurityContextHolder.getContext());
311 // httpSession.setAttribute(ATTR_AUTH, Boolean.FALSE);
312 filterChain.doFilter(request, response);
313 return;
314 }
315
316 requestBasicAuth(httpSession, response);
317 }
318 }
319
320 // class CustomDosFilter extends DoSFilter {
321 // @Override
322 // protected String extractUserId(ServletRequest request) {
323 // HttpSession httpSession = ((HttpServletRequest) request)
324 // .getSession();
325 // if (isSessionAuthenticated(httpSession)) {
326 // String userId = ((SecurityContext) httpSession
327 // .getAttribute(SPRING_SECURITY_CONTEXT_KEY))
328 // .getAuthentication().getName();
329 // return userId;
330 // }
331 // return super.extractUserId(request);
332 //
333 // }
334 // }
335 }