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