1 package org
.argeo
.cms
.internal
.kernel
;
3 import static org
.argeo
.cms
.auth
.AuthConstants
.LOGIN_CONTEXT_USER
;
5 import java
.io
.IOException
;
6 import java
.io
.Serializable
;
8 import java
.security
.PrivilegedActionException
;
9 import java
.security
.PrivilegedExceptionAction
;
10 import java
.util
.Properties
;
11 import java
.util
.StringTokenizer
;
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
;
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
;
47 * Intercepts and enriches http access, mainly focusing on security and
50 class DataHttp
implements KernelConstants
, ArgeoJcrConstants
{
51 private final static Log log
= LogFactory
.getLog(DataHttp
.class);
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";
57 private final HttpService httpService
;
59 // FIXME Make it more unique
60 private String httpAuthRealm
= "Argeo";
62 // WebDav / JCR remoting
63 private OpenInViewSessionProvider sessionProvider
;
65 DataHttp(HttpService httpService
, NodeRepository node
) {
66 this.httpService
= httpService
;
67 sessionProvider
= new OpenInViewSessionProvider();
68 registerRepositoryServlets(ALIAS_NODE
, node
);
71 public void destroy() {
72 unregisterRepositoryServlets(ALIAS_NODE
);
75 void registerRepositoryServlets(String alias
, Repository repository
) {
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
);
87 void unregisterRepositoryServlets(String alias
) {
88 // FIXME unregister servlets
91 void registerWebdavServlet(String alias
, Repository repository
,
92 boolean anonymous
) throws NamespaceException
, ServletException
{
93 WebdavServlet webdavServlet
= new WebdavServlet(repository
,
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
));
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
,
112 String path
= pathPrefix
+ "/" + alias
;
113 Properties ip
= new Properties();
114 ip
.setProperty(JcrRemotingServlet
.INIT_PARAM_RESOURCE_PATH_PREFIX
, path
);
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
));
129 // private X509Certificate extractCertificate(HttpServletRequest req) {
130 // X509Certificate[] certs = (X509Certificate[]) req
131 // .getAttribute("javax.servlet.request.X509Certificate");
132 // if (null != certs && certs.length > 0) {
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");
144 LoginContext lc
= new LoginContext(
145 AuthConstants
.LOGIN_CONTEXT_USER
,
146 new HttpRequestCallbackHandler(request
));
148 return lc
.getSubject();
149 } catch (LoginException e
) {
150 throw new CmsException("Cannot login", e
);
154 private class DataHttpContext
implements HttpContext
{
155 private final boolean anonymous
;
157 DataHttpContext(boolean anonymous
) {
158 this.anonymous
= anonymous
;
162 public boolean handleSecurity(final HttpServletRequest request
,
163 HttpServletResponse response
) throws IOException
{
166 Subject subject
= KernelUtils
.anonymousLogin();
167 Authorization authorization
= subject
168 .getPrivateCredentials(Authorization
.class).iterator()
170 request
.setAttribute(REMOTE_USER
, AuthConstants
.ROLE_ANONYMOUS
);
171 request
.setAttribute(AUTHORIZATION
, authorization
);
175 KernelUtils
.logRequestHeaders(log
, request
);
177 new LoginContext(LOGIN_CONTEXT_USER
,
178 new HttpRequestCallbackHandler(request
)).login();
180 } catch (CredentialNotFoundException e
) {
181 CallbackHandler token
= basicAuth(request
);
184 LoginContext lc
= new LoginContext(LOGIN_CONTEXT_USER
,
187 // Note: this is impossible to reliably clear the
188 // authorization header when access from a browser.
190 } catch (LoginException e1
) {
191 throw new CmsException("Could not login", e1
);
194 requestBasicAuth(request
, response
);
197 } catch (LoginException e
) {
198 throw new CmsException("Could not login", e
);
203 public URL
getResource(String name
) {
204 return Activator
.getBundleContext().getBundle().getResource(name
);
208 public String
getMimeType(String name
) {
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);
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")) {
228 // TODO manipulate char[]
229 String credentials
= new String(
230 Base64
.decodeBase64(st
.nextToken()),
232 // log.debug("Credentials: " + credentials);
233 int p
= credentials
.indexOf(":");
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
)
245 else if (cb
instanceof PasswordCallback
)
246 ((PasswordCallback
) cb
)
247 .setPassword(password
);
248 else if (cb
instanceof HttpRequestCallback
)
249 ((HttpRequestCallback
) cb
)
250 .setRequest(httpRequest
);
255 throw new CmsException(
256 "Invalid authentication token");
258 } catch (Exception e
) {
259 throw new CmsException(
260 "Couldn't retrieve authentication", e
);
271 * Implements an open session in view patter: a new JCR session is created
274 private class OpenInViewSessionProvider
implements SessionProvider
,
276 private static final long serialVersionUID
= 2270957712453841368L;
278 public Session
getSession(HttpServletRequest request
, Repository rep
,
279 String workspace
) throws javax
.jcr
.LoginException
,
280 ServletException
, RepositoryException
{
281 return login(request
, rep
, workspace
);
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
);
294 public void releaseSession(Session session
) {
295 JcrUtils
.logoutQuietly(session
);
296 if (log
.isTraceEnabled())
297 log
.trace("Logged out remote JCR session " + session
);
301 private class WebdavServlet
extends SimpleWebdavServlet
{
302 private static final long serialVersionUID
= -4687354117811443881L;
303 private final Repository repository
;
305 public WebdavServlet(Repository repository
,
306 SessionProvider sessionProvider
) {
307 this.repository
= repository
;
308 setSessionProvider(sessionProvider
);
311 public Repository
getRepository() {
316 protected void service(final HttpServletRequest request
,
317 final HttpServletResponse response
) throws ServletException
,
320 Subject subject
= subjectFromRequest(request
);
321 Subject
.doAs(subject
, new PrivilegedExceptionAction
<Void
>() {
323 public Void
run() throws Exception
{
324 WebdavServlet
.super.service(request
, response
);
328 } catch (PrivilegedActionException e
) {
329 throw new CmsException("Cannot process webdav request",
335 private class RemotingServlet
extends JcrRemotingServlet
{
336 private static final long serialVersionUID
= 4605238259548058883L;
337 private final Repository repository
;
338 private final SessionProvider sessionProvider
;
340 public RemotingServlet(Repository repository
,
341 SessionProvider sessionProvider
) {
342 this.repository
= repository
;
343 this.sessionProvider
= sessionProvider
;
347 protected Repository
getRepository() {
352 protected SessionProvider
getSessionProvider() {
353 return sessionProvider
;
357 protected void service(final HttpServletRequest request
,
358 final HttpServletResponse response
) throws ServletException
,
361 Subject subject
= subjectFromRequest(request
);
362 Subject
.doAs(subject
, new PrivilegedExceptionAction
<Void
>() {
364 public Void
run() throws Exception
{
365 RemotingServlet
.super.service(request
, response
);
369 } catch (PrivilegedActionException e
) {
370 throw new CmsException("Cannot process JCR remoting request",