1 package org
.argeo
.cms
.internal
.kernel
;
3 import java
.io
.IOException
;
4 import java
.io
.Serializable
;
6 import java
.security
.PrivilegedActionException
;
7 import java
.security
.PrivilegedExceptionAction
;
8 import java
.util
.Properties
;
9 import java
.util
.StringTokenizer
;
11 import javax
.jcr
.Repository
;
12 import javax
.jcr
.RepositoryException
;
13 import javax
.jcr
.Session
;
14 import javax
.security
.auth
.Subject
;
15 import javax
.security
.auth
.callback
.Callback
;
16 import javax
.security
.auth
.callback
.CallbackHandler
;
17 import javax
.security
.auth
.callback
.NameCallback
;
18 import javax
.security
.auth
.callback
.PasswordCallback
;
19 import javax
.security
.auth
.login
.CredentialNotFoundException
;
20 import javax
.security
.auth
.login
.LoginContext
;
21 import javax
.security
.auth
.login
.LoginException
;
22 import javax
.servlet
.ServletException
;
23 import javax
.servlet
.http
.HttpServletRequest
;
24 import javax
.servlet
.http
.HttpServletResponse
;
26 import org
.apache
.commons
.codec
.binary
.Base64
;
27 import org
.apache
.commons
.logging
.Log
;
28 import org
.apache
.commons
.logging
.LogFactory
;
29 import org
.apache
.jackrabbit
.server
.SessionProvider
;
30 import org
.apache
.jackrabbit
.server
.remoting
.davex
.JcrRemotingServlet
;
31 import org
.apache
.jackrabbit
.webdav
.simple
.SimpleWebdavServlet
;
32 import org
.argeo
.cms
.CmsException
;
33 import org
.argeo
.cms
.auth
.HttpRequestCallback
;
34 import org
.argeo
.cms
.auth
.HttpRequestCallbackHandler
;
35 import org
.argeo
.jcr
.JcrUtils
;
36 import org
.argeo
.node
.NodeConstants
;
37 import org
.osgi
.framework
.BundleContext
;
38 import org
.osgi
.framework
.FrameworkUtil
;
39 import org
.osgi
.framework
.ServiceReference
;
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 import org
.osgi
.util
.tracker
.ServiceTracker
;
45 import org
.osgi
.util
.tracker
.ServiceTrackerCustomizer
;
48 * Intercepts and enriches http access, mainly focusing on security and
51 class DataHttp
implements KernelConstants
{
52 private final static Log log
= LogFactory
.getLog(DataHttp
.class);
54 // private final static String ATTR_AUTH = "auth";
55 private final static String HEADER_AUTHORIZATION
= "Authorization";
56 private final static String HEADER_WWW_AUTHENTICATE
= "WWW-Authenticate";
58 private final static String DEFAULT_PROTECTED_HANDLERS
= "/org/argeo/cms/internal/kernel/protectedHandlers.xml";
60 private final BundleContext bc
;
61 private final HttpService httpService
;
62 private final ServiceTracker
<Repository
, Repository
> repositories
;
64 // FIXME Make it more unique
65 private String httpAuthRealm
= "Argeo";
67 DataHttp(HttpService httpService
) {
68 this.bc
= FrameworkUtil
.getBundle(getClass()).getBundleContext();
69 this.httpService
= httpService
;
70 repositories
= new ServiceTracker
<>(bc
, Repository
.class, new RepositoriesStc());
74 public void destroy() {
78 void registerRepositoryServlets(String alias
, Repository repository
) {
80 registerWebdavServlet(alias
, repository
);
81 registerRemotingServlet(alias
, repository
);
82 if (log
.isDebugEnabled())
83 log
.debug("Registered servlets for repository '" + alias
+ "'");
84 } catch (Exception e
) {
85 throw new CmsException("Could not register servlets for repository '" + alias
+ "'", e
);
89 void unregisterRepositoryServlets(String alias
) {
91 httpService
.unregister(webdavPath(alias
));
92 httpService
.unregister(remotingPath(alias
));
93 if (log
.isDebugEnabled())
94 log
.debug("Unregistered servlets for repository '" + alias
+ "'");
95 } catch (Exception e
) {
96 log
.error("Could not unregister servlets for repository '" + alias
+ "'", e
);
100 void registerWebdavServlet(String alias
, Repository repository
) throws NamespaceException
, ServletException
{
101 WebdavServlet webdavServlet
= new WebdavServlet(repository
, new OpenInViewSessionProvider(alias
));
102 String path
= webdavPath(alias
);
103 Properties ip
= new Properties();
104 ip
.setProperty(WebdavServlet
.INIT_PARAM_RESOURCE_CONFIG
, WEBDAV_CONFIG
);
105 ip
.setProperty(WebdavServlet
.INIT_PARAM_RESOURCE_PATH_PREFIX
, path
);
106 httpService
.registerServlet(path
, webdavServlet
, ip
, new DataHttpContext());
109 void registerRemotingServlet(String alias
, Repository repository
) throws NamespaceException
, ServletException
{
110 RemotingServlet remotingServlet
= new RemotingServlet(repository
, new OpenInViewSessionProvider(alias
));
111 String path
= remotingPath(alias
);
112 Properties ip
= new Properties();
113 ip
.setProperty(JcrRemotingServlet
.INIT_PARAM_RESOURCE_PATH_PREFIX
, path
);
115 // Looks like a bug in Jackrabbit remoting init
116 ip
.setProperty(RemotingServlet
.INIT_PARAM_HOME
, KernelUtils
.getOsgiInstanceDir() + "/tmp/remoting_" + alias
);
117 ip
.setProperty(RemotingServlet
.INIT_PARAM_TMP_DIRECTORY
, "remoting_" + alias
);
118 ip
.setProperty(RemotingServlet
.INIT_PARAM_PROTECTED_HANDLERS_CONFIG
, DEFAULT_PROTECTED_HANDLERS
);
119 ip
.setProperty(RemotingServlet
.INIT_PARAM_CREATE_ABSOLUTE_URI
, "false");
120 httpService
.registerServlet(path
, remotingServlet
, ip
, new RemotingHttpContext());
123 private String
webdavPath(String alias
) {
124 return NodeConstants
.PATH_DATA
+ "/" + alias
;
127 private String
remotingPath(String alias
) {
128 return NodeConstants
.PATH_JCR
+ "/" + alias
;
131 private Subject
subjectFromRequest(HttpServletRequest request
) {
132 Authorization authorization
= (Authorization
) request
.getAttribute(HttpContext
.AUTHORIZATION
);
133 if (authorization
== null)
134 throw new CmsException("Not authenticated");
136 LoginContext lc
= new LoginContext(NodeConstants
.LOGIN_CONTEXT_USER
,
137 new HttpRequestCallbackHandler(request
));
139 return lc
.getSubject();
140 } catch (LoginException e
) {
141 throw new CmsException("Cannot login", e
);
145 private void requestBasicAuth(HttpServletRequest request
, HttpServletResponse response
) {
146 response
.setStatus(401);
147 response
.setHeader(HEADER_WWW_AUTHENTICATE
, "basic realm=\"" + httpAuthRealm
+ "\"");
150 private CallbackHandler
basicAuth(final HttpServletRequest httpRequest
) {
151 String authHeader
= httpRequest
.getHeader(HEADER_AUTHORIZATION
);
152 if (authHeader
!= null) {
153 StringTokenizer st
= new StringTokenizer(authHeader
);
154 if (st
.hasMoreTokens()) {
155 String basic
= st
.nextToken();
156 if (basic
.equalsIgnoreCase("Basic")) {
158 // TODO manipulate char[]
159 String credentials
= new String(Base64
.decodeBase64(st
.nextToken()), "UTF-8");
160 // log.debug("Credentials: " + credentials);
161 int p
= credentials
.indexOf(":");
163 final String login
= credentials
.substring(0, p
).trim();
164 final char[] password
= credentials
.substring(p
+ 1).trim().toCharArray();
165 return new CallbackHandler() {
166 public void handle(Callback
[] callbacks
) {
167 for (Callback cb
: callbacks
) {
168 if (cb
instanceof NameCallback
)
169 ((NameCallback
) cb
).setName(login
);
170 else if (cb
instanceof PasswordCallback
)
171 ((PasswordCallback
) cb
).setPassword(password
);
172 else if (cb
instanceof HttpRequestCallback
)
173 ((HttpRequestCallback
) cb
).setRequest(httpRequest
);
178 throw new CmsException("Invalid authentication token");
180 } catch (Exception e
) {
181 throw new CmsException("Couldn't retrieve authentication", e
);
189 private class RepositoriesStc
implements ServiceTrackerCustomizer
<Repository
, Repository
> {
192 public Repository
addingService(ServiceReference
<Repository
> reference
) {
193 Repository repository
= bc
.getService(reference
);
194 Object jcrRepoAlias
= reference
.getProperty(NodeConstants
.CN
);
195 if (jcrRepoAlias
!= null) {
196 String alias
= jcrRepoAlias
.toString();
197 registerRepositoryServlets(alias
, repository
);
203 public void modifiedService(ServiceReference
<Repository
> reference
, Repository service
) {
207 public void removedService(ServiceReference
<Repository
> reference
, Repository service
) {
208 Object jcrRepoAlias
= reference
.getProperty(NodeConstants
.CN
);
209 if (jcrRepoAlias
!= null) {
210 String alias
= jcrRepoAlias
.toString();
211 unregisterRepositoryServlets(alias
);
216 private class DataHttpContext
implements HttpContext
{
218 public boolean handleSecurity(final HttpServletRequest request
, HttpServletResponse response
)
220 if (log
.isTraceEnabled())
221 KernelUtils
.logRequestHeaders(log
, request
);
224 lc
= new LoginContext(NodeConstants
.LOGIN_CONTEXT_USER
, new HttpRequestCallbackHandler(request
));
227 } catch (LoginException e
) {
228 CallbackHandler token
= basicAuth(request
);
231 lc
= new LoginContext(NodeConstants
.LOGIN_CONTEXT_USER
, token
);
233 // Note: this is impossible to reliably clear the
234 // authorization header when access from a browser.
236 } catch (LoginException e1
) {
237 throw new CmsException("Could not login", e1
);
242 lc
= new LoginContext(NodeConstants
.LOGIN_CONTEXT_USER
);
244 } catch (LoginException e1
) {
245 if (log
.isDebugEnabled())
246 log
.error("Cannot log in anonynous", e1
);
251 request
.setAttribute(NodeConstants
.LOGIN_CONTEXT_USER
, lc
);
256 public URL
getResource(String name
) {
257 return KernelUtils
.getBundleContext(DataHttp
.class).getBundle().getResource(name
);
261 public String
getMimeType(String name
) {
267 private class RemotingHttpContext
implements HttpContext
{
268 // private final boolean anonymous;
270 RemotingHttpContext() {
271 // this.anonymous = anonymous;
275 public boolean handleSecurity(final HttpServletRequest request
, HttpServletResponse response
)
279 // Subject subject = KernelUtils.anonymousLogin();
280 // Authorization authorization =
281 // subject.getPrivateCredentials(Authorization.class).iterator().next();
282 // request.setAttribute(REMOTE_USER, NodeConstants.ROLE_ANONYMOUS);
283 // request.setAttribute(AUTHORIZATION, authorization);
287 if (log
.isTraceEnabled())
288 KernelUtils
.logRequestHeaders(log
, request
);
291 lc
= new LoginContext(NodeConstants
.LOGIN_CONTEXT_USER
, new HttpRequestCallbackHandler(request
));
293 } catch (CredentialNotFoundException e
) {
294 CallbackHandler token
= basicAuth(request
);
297 lc
= new LoginContext(NodeConstants
.LOGIN_CONTEXT_USER
, token
);
299 // Note: this is impossible to reliably clear the
300 // authorization header when access from a browser.
301 } catch (LoginException e1
) {
302 throw new CmsException("Could not login", e1
);
305 requestBasicAuth(request
, response
);
308 } catch (LoginException e
) {
309 throw new CmsException("Could not login", e
);
313 request
.setAttribute(NodeConstants
.LOGIN_CONTEXT_USER
, lc
);
321 public URL
getResource(String name
) {
322 return KernelUtils
.getBundleContext(DataHttp
.class).getBundle().getResource(name
);
326 public String
getMimeType(String name
) {
333 * Implements an open session in view patter: a new JCR session is created
336 private class OpenInViewSessionProvider
implements SessionProvider
, Serializable
{
337 private static final long serialVersionUID
= 2270957712453841368L;
338 private final String alias
;
340 public OpenInViewSessionProvider(String alias
) {
344 public Session
getSession(HttpServletRequest request
, Repository rep
, String workspace
)
345 throws javax
.jcr
.LoginException
, ServletException
, RepositoryException
{
346 return login(request
, rep
, workspace
);
349 protected Session
login(HttpServletRequest request
, Repository repository
, String workspace
)
350 throws RepositoryException
{
351 if (log
.isTraceEnabled())
352 log
.trace("Repo " + alias
+ ", login to workspace " + (workspace
== null ?
"<default>" : workspace
)
353 + " in web session " + request
.getSession().getId());
354 LoginContext lc
= (LoginContext
) request
.getAttribute(NodeConstants
.LOGIN_CONTEXT_USER
);
356 throw new CmsException("No login context available");
358 // LoginContext lc = new
359 // LoginContext(NodeConstants.LOGIN_CONTEXT_USER,
360 // new HttpRequestCallbackHandler(request));
362 return Subject
.doAs(lc
.getSubject(), new PrivilegedExceptionAction
<Session
>() {
364 public Session
run() throws Exception
{
365 return repository
.login(workspace
);
368 } catch (Exception e
) {
369 throw new CmsException("Cannot log in to JCR", e
);
371 // return repository.login(workspace);
374 public void releaseSession(Session session
) {
375 JcrUtils
.logoutQuietly(session
);
376 if (log
.isTraceEnabled())
377 log
.trace("Logged out remote JCR session " + session
);
381 private class WebdavServlet
extends SimpleWebdavServlet
{
382 private static final long serialVersionUID
= -4687354117811443881L;
383 private final Repository repository
;
385 public WebdavServlet(Repository repository
, SessionProvider sessionProvider
) {
386 this.repository
= repository
;
387 setSessionProvider(sessionProvider
);
390 public Repository
getRepository() {
395 protected void service(final HttpServletRequest request
, final HttpServletResponse response
)
396 throws ServletException
, IOException
{
397 WebdavServlet
.super.service(request
, response
);
399 // Subject subject = subjectFromRequest(request);
400 // // TODO make it stronger, with eTags.
401 // // if (CurrentUser.isAnonymous(subject) &&
402 // // request.getMethod().equals("GET")) {
403 // // response.setHeader("Cache-Control", "no-transform, public,
404 // // max-age=300, s-maxage=900");
407 // Subject.doAs(subject, new PrivilegedExceptionAction<Void>() {
409 // public Void run() throws Exception {
410 // WebdavServlet.super.service(request, response);
414 // } catch (PrivilegedActionException e) {
415 // throw new CmsException("Cannot process webdav request",
416 // e.getException());
421 private class RemotingServlet
extends JcrRemotingServlet
{
422 private static final long serialVersionUID
= 4605238259548058883L;
423 private final Repository repository
;
424 private final SessionProvider sessionProvider
;
426 public RemotingServlet(Repository repository
, SessionProvider sessionProvider
) {
427 this.repository
= repository
;
428 this.sessionProvider
= sessionProvider
;
432 protected Repository
getRepository() {
437 protected SessionProvider
getSessionProvider() {
438 return sessionProvider
;
442 protected void service(final HttpServletRequest request
, final HttpServletResponse response
)
443 throws ServletException
, IOException
{
445 Subject subject
= subjectFromRequest(request
);
446 Subject
.doAs(subject
, new PrivilegedExceptionAction
<Void
>() {
448 public Void
run() throws Exception
{
449 RemotingServlet
.super.service(request
, response
);
453 } catch (PrivilegedActionException e
) {
454 throw new CmsException("Cannot process JCR remoting request", e
.getException());