]> git.argeo.org Git - lgpl/argeo-commons.git/blob - DataHttp.java
ab9211a5439fc0ab02368107628ffe53d8a18fb8
[lgpl/argeo-commons.git] / DataHttp.java
1 package org.argeo.cms.internal.kernel;
2
3 import static org.argeo.cms.auth.AuthConstants.LOGIN_CONTEXT_USER;
4
5 import java.io.IOException;
6 import java.io.Serializable;
7 import java.net.URL;
8 import java.security.PrivilegedActionException;
9 import java.security.PrivilegedExceptionAction;
10 import java.util.Properties;
11 import java.util.StringTokenizer;
12
13 import javax.jcr.Repository;
14 import javax.jcr.RepositoryException;
15 import javax.jcr.Session;
16 import javax.security.auth.Subject;
17 import javax.security.auth.callback.Callback;
18 import javax.security.auth.callback.CallbackHandler;
19 import javax.security.auth.callback.NameCallback;
20 import javax.security.auth.callback.PasswordCallback;
21 import javax.security.auth.login.CredentialNotFoundException;
22 import javax.security.auth.login.LoginContext;
23 import javax.security.auth.login.LoginException;
24 import javax.servlet.Servlet;
25 import javax.servlet.ServletException;
26 import javax.servlet.http.HttpServletRequest;
27 import javax.servlet.http.HttpServletResponse;
28
29 import org.apache.commons.codec.binary.Base64;
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.apache.jackrabbit.server.SessionProvider;
33 import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet;
34 import org.apache.jackrabbit.webdav.simple.SimpleWebdavServlet;
35 import org.argeo.cms.CmsException;
36 import org.argeo.cms.auth.AuthConstants;
37 import org.argeo.cms.auth.HttpRequestCallback;
38 import org.argeo.cms.auth.HttpRequestCallbackHandler;
39 import org.argeo.jcr.ArgeoJcrConstants;
40 import org.argeo.jcr.JcrUtils;
41 import org.osgi.service.http.HttpContext;
42 import org.osgi.service.http.HttpService;
43 import org.osgi.service.http.NamespaceException;
44 import org.osgi.service.useradmin.Authorization;
45
46 /**
47 * Intercepts and enriches http access, mainly focusing on security and
48 * transactionality.
49 */
50 class DataHttp implements KernelConstants, ArgeoJcrConstants {
51 private final static Log log = LogFactory.getLog(DataHttp.class);
52
53 // private final static String ATTR_AUTH = "auth";
54 private final static String HEADER_AUTHORIZATION = "Authorization";
55 private final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
56
57 private final HttpService httpService;
58
59 // FIXME Make it more unique
60 private String httpAuthRealm = "Argeo";
61
62 // WebDav / JCR remoting
63 private OpenInViewSessionProvider sessionProvider;
64
65 DataHttp(HttpService httpService, NodeRepository node) {
66 this.httpService = httpService;
67 sessionProvider = new OpenInViewSessionProvider();
68 registerRepositoryServlets(ALIAS_NODE, node);
69 }
70
71 public void destroy() {
72 unregisterRepositoryServlets(ALIAS_NODE);
73 }
74
75 void registerRepositoryServlets(String alias, Repository repository) {
76 try {
77 registerWebdavServlet(alias, repository, true);
78 registerWebdavServlet(alias, repository, false);
79 registerRemotingServlet(alias, repository, true);
80 registerRemotingServlet(alias, repository, false);
81 } catch (Exception e) {
82 throw new CmsException(
83 "Could not register servlets for repository " + alias, e);
84 }
85 }
86
87 void unregisterRepositoryServlets(String alias) {
88 // FIXME unregister servlets
89 }
90
91 void registerWebdavServlet(String alias, Repository repository,
92 boolean anonymous) throws NamespaceException, ServletException {
93 WebdavServlet webdavServlet = new WebdavServlet(repository,
94 sessionProvider);
95 String pathPrefix = anonymous ? WEBDAV_PUBLIC : WEBDAV_PRIVATE;
96 String path = pathPrefix + "/" + alias;
97 Properties ip = new Properties();
98 ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, WEBDAV_CONFIG);
99 ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
100 // httpService.registerFilter(path, anonymous ? new AnonymousFilter()
101 // : new DavFilter(), null, null);
102 // Cast to servlet because of a weird behaviour in Eclipse
103 httpService.registerServlet(path, (Servlet) webdavServlet, ip,
104 new DataHttpContext(anonymous));
105 }
106
107 void registerRemotingServlet(String alias, Repository repository,
108 boolean anonymous) throws NamespaceException, ServletException {
109 String pathPrefix = anonymous ? REMOTING_PUBLIC : REMOTING_PRIVATE;
110 RemotingServlet remotingServlet = new RemotingServlet(repository,
111 sessionProvider);
112 String path = pathPrefix + "/" + alias;
113 Properties ip = new Properties();
114 ip.setProperty(JcrRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
115
116 // Looks like a bug in Jackrabbit remoting init
117 ip.setProperty(RemotingServlet.INIT_PARAM_HOME,
118 KernelUtils.getOsgiInstanceDir() + "/tmp/jackrabbit");
119 ip.setProperty(RemotingServlet.INIT_PARAM_TMP_DIRECTORY, "remoting");
120 // in order to avoid annoying warning.
121 ip.setProperty(RemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG, "");
122 // Cast to servlet because of a weird behaviour in Eclipse
123 // httpService.registerFilter(path, anonymous ? new AnonymousFilter()
124 // : new DavFilter(), null, null);
125 httpService.registerServlet(path, (Servlet) remotingServlet, ip,
126 new DataHttpContext(anonymous));
127 }
128
129 // private X509Certificate extractCertificate(HttpServletRequest req) {
130 // X509Certificate[] certs = (X509Certificate[]) req
131 // .getAttribute("javax.servlet.request.X509Certificate");
132 // if (null != certs && certs.length > 0) {
133 // return certs[0];
134 // }
135 // return null;
136 // }
137
138 private Subject subjectFromRequest(HttpServletRequest request) {
139 Authorization authorization = (Authorization) request
140 .getAttribute(HttpContext.AUTHORIZATION);
141 if (authorization == null)
142 throw new CmsException("Not authenticated");
143 try {
144 LoginContext lc = new LoginContext(
145 AuthConstants.LOGIN_CONTEXT_USER,
146 new HttpRequestCallbackHandler(request));
147 lc.login();
148 return lc.getSubject();
149 } catch (LoginException e) {
150 throw new CmsException("Cannot login", e);
151 }
152 }
153
154 private class DataHttpContext implements HttpContext {
155 private final boolean anonymous;
156
157 DataHttpContext(boolean anonymous) {
158 this.anonymous = anonymous;
159 }
160
161 @Override
162 public boolean handleSecurity(final HttpServletRequest request,
163 HttpServletResponse response) throws IOException {
164
165 if (anonymous) {
166 Subject subject = KernelUtils.anonymousLogin();
167 Authorization authorization = subject
168 .getPrivateCredentials(Authorization.class).iterator()
169 .next();
170 request.setAttribute(REMOTE_USER, AuthConstants.ROLE_ANONYMOUS);
171 request.setAttribute(AUTHORIZATION, authorization);
172 return true;
173 }
174
175 KernelUtils.logRequestHeaders(log, request);
176 try {
177 new LoginContext(LOGIN_CONTEXT_USER,
178 new HttpRequestCallbackHandler(request)).login();
179 return true;
180 } catch (CredentialNotFoundException e) {
181 CallbackHandler token = basicAuth(request);
182 if (token != null) {
183 try {
184 LoginContext lc = new LoginContext(LOGIN_CONTEXT_USER,
185 token);
186 lc.login();
187 // Note: this is impossible to reliably clear the
188 // authorization header when access from a browser.
189 return true;
190 } catch (LoginException e1) {
191 throw new CmsException("Could not login", e1);
192 }
193 } else {
194 requestBasicAuth(request, response);
195 return false;
196 }
197 } catch (LoginException e) {
198 throw new CmsException("Could not login", e);
199 }
200 }
201
202 @Override
203 public URL getResource(String name) {
204 return Activator.getBundleContext().getBundle().getResource(name);
205 }
206
207 @Override
208 public String getMimeType(String name) {
209 return null;
210 }
211
212 private void requestBasicAuth(HttpServletRequest request,
213 HttpServletResponse response) {
214 response.setStatus(401);
215 response.setHeader(HEADER_WWW_AUTHENTICATE, "basic realm=\""
216 + httpAuthRealm + "\"");
217 // request.getSession().setAttribute(ATTR_AUTH, Boolean.TRUE);
218 }
219
220 private CallbackHandler basicAuth(final HttpServletRequest httpRequest) {
221 String authHeader = httpRequest.getHeader(HEADER_AUTHORIZATION);
222 if (authHeader != null) {
223 StringTokenizer st = new StringTokenizer(authHeader);
224 if (st.hasMoreTokens()) {
225 String basic = st.nextToken();
226 if (basic.equalsIgnoreCase("Basic")) {
227 try {
228 // TODO manipulate char[]
229 String credentials = new String(
230 Base64.decodeBase64(st.nextToken()),
231 "UTF-8");
232 // log.debug("Credentials: " + credentials);
233 int p = credentials.indexOf(":");
234 if (p != -1) {
235 final String login = credentials
236 .substring(0, p).trim();
237 final char[] password = credentials
238 .substring(p + 1).trim().toCharArray();
239 return new CallbackHandler() {
240 public void handle(Callback[] callbacks) {
241 for (Callback cb : callbacks) {
242 if (cb instanceof NameCallback)
243 ((NameCallback) cb)
244 .setName(login);
245 else if (cb instanceof PasswordCallback)
246 ((PasswordCallback) cb)
247 .setPassword(password);
248 else if (cb instanceof HttpRequestCallback)
249 ((HttpRequestCallback) cb)
250 .setRequest(httpRequest);
251 }
252 }
253 };
254 } else {
255 throw new CmsException(
256 "Invalid authentication token");
257 }
258 } catch (Exception e) {
259 throw new CmsException(
260 "Couldn't retrieve authentication", e);
261 }
262 }
263 }
264 }
265 return null;
266 }
267
268 }
269
270 /**
271 * Implements an open session in view patter: a new JCR session is created
272 * for each request
273 */
274 private class OpenInViewSessionProvider implements SessionProvider,
275 Serializable {
276 private static final long serialVersionUID = 2270957712453841368L;
277
278 public Session getSession(HttpServletRequest request, Repository rep,
279 String workspace) throws javax.jcr.LoginException,
280 ServletException, RepositoryException {
281 return login(request, rep, workspace);
282 }
283
284 protected Session login(HttpServletRequest request,
285 Repository repository, String workspace)
286 throws RepositoryException {
287 if (log.isTraceEnabled())
288 log.trace("Login to workspace "
289 + (workspace == null ? "<default>" : workspace)
290 + " in web session " + request.getSession().getId());
291 return repository.login(workspace);
292 }
293
294 public void releaseSession(Session session) {
295 JcrUtils.logoutQuietly(session);
296 if (log.isTraceEnabled())
297 log.trace("Logged out remote JCR session " + session);
298 }
299 }
300
301 private class WebdavServlet extends SimpleWebdavServlet {
302 private static final long serialVersionUID = -4687354117811443881L;
303 private final Repository repository;
304
305 public WebdavServlet(Repository repository,
306 SessionProvider sessionProvider) {
307 this.repository = repository;
308 setSessionProvider(sessionProvider);
309 }
310
311 public Repository getRepository() {
312 return repository;
313 }
314
315 @Override
316 protected void service(final HttpServletRequest request,
317 final HttpServletResponse response) throws ServletException,
318 IOException {
319 try {
320 Subject subject = subjectFromRequest(request);
321 Subject.doAs(subject, new PrivilegedExceptionAction<Void>() {
322 @Override
323 public Void run() throws Exception {
324 WebdavServlet.super.service(request, response);
325 return null;
326 }
327 });
328 } catch (PrivilegedActionException e) {
329 throw new CmsException("Cannot process webdav request",
330 e.getException());
331 }
332 }
333 }
334
335 private class RemotingServlet extends JcrRemotingServlet {
336 private static final long serialVersionUID = 4605238259548058883L;
337 private final Repository repository;
338 private final SessionProvider sessionProvider;
339
340 public RemotingServlet(Repository repository,
341 SessionProvider sessionProvider) {
342 this.repository = repository;
343 this.sessionProvider = sessionProvider;
344 }
345
346 @Override
347 protected Repository getRepository() {
348 return repository;
349 }
350
351 @Override
352 protected SessionProvider getSessionProvider() {
353 return sessionProvider;
354 }
355
356 @Override
357 protected void service(final HttpServletRequest request,
358 final HttpServletResponse response) throws ServletException,
359 IOException {
360 try {
361 Subject subject = subjectFromRequest(request);
362 Subject.doAs(subject, new PrivilegedExceptionAction<Void>() {
363 @Override
364 public Void run() throws Exception {
365 RemotingServlet.super.service(request, response);
366 return null;
367 }
368 });
369 } catch (PrivilegedActionException e) {
370 throw new CmsException("Cannot process JCR remoting request",
371 e.getException());
372 }
373 }
374 }
375 }