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