1 package org
.argeo
.cms
.internal
.kernel
;
3 import static org
.argeo
.cms
.auth
.AuthConstants
.ACCESS_CONTROL_CONTEXT
;
5 import java
.io
.IOException
;
6 import java
.io
.Serializable
;
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
;
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
;
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
;
49 * Intercepts and enriches http access, mainly focusing on security and
52 class DataHttp
implements KernelConstants
, ArgeoJcrConstants
{
53 private final static Log log
= LogFactory
.getLog(DataHttp
.class);
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";
59 // private final AuthenticationManager authenticationManager;
60 private final HttpService httpService
;
62 // FIXME Make it more unique
63 private String httpAuthRealm
= "Argeo";
65 // WebDav / JCR remoting
66 private OpenInViewSessionProvider sessionProvider
;
68 DataHttp(HttpService httpService
, JackrabbitNode node
) {
69 this.httpService
= httpService
;
70 sessionProvider
= new OpenInViewSessionProvider();
71 registerRepositoryServlets(ALIAS_NODE
, node
);
74 public void destroy() {
75 unregisterRepositoryServlets(ALIAS_NODE
);
78 void registerRepositoryServlets(String alias
, Repository repository
) {
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
);
90 void unregisterRepositoryServlets(String alias
) {
91 // FIXME unregister servlets
94 void registerWebdavServlet(String alias
, Repository repository
,
95 boolean anonymous
) throws NamespaceException
, ServletException
{
96 WebdavServlet webdavServlet
= new WebdavServlet(repository
,
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
));
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
,
115 String path
= pathPrefix
+ "/" + alias
;
116 Properties ip
= new Properties();
117 ip
.setProperty(JcrRemotingServlet
.INIT_PARAM_RESOURCE_PATH_PREFIX
, path
);
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
));
132 // private Boolean isSessionAuthenticated(HttpSession httpSession) {
133 // SecurityContext contextFromSession = (SecurityContext) httpSession
134 // .getAttribute(SPRING_SECURITY_CONTEXT_KEY);
135 // return contextFromSession != null;
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
);
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")) {
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(":");
159 final String login
= credentials
.substring(0, p
)
161 final char[] password
= credentials
162 .substring(p
+ 1).trim().toCharArray();
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
);
176 throw new CmsException(
177 "Invalid authentication token");
179 } catch (Exception e
) {
180 throw new CmsException(
181 "Couldn't retrieve authentication", e
);
186 throw new CmsException("Couldn't retrieve authentication");
189 private X509Certificate
extractCertificate(HttpServletRequest req
) {
190 X509Certificate
[] certs
= (X509Certificate
[]) req
191 .getAttribute("javax.servlet.request.X509Certificate");
192 if (null != certs
&& certs
.length
> 0) {
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
);
210 private class DataHttpContext
implements HttpContext
{
211 private final boolean anonymous
;
213 DataHttpContext(boolean anonymous
) {
214 this.anonymous
= anonymous
;
218 public boolean handleSecurity(HttpServletRequest request
,
219 HttpServletResponse response
) throws IOException
{
220 final Subject subject
;
223 subject
= KernelUtils
.anonymousLogin();
224 Authorization authorization
= subject
225 .getPrivateCredentials(Authorization
.class).iterator()
227 request
.setAttribute(AUTHORIZATION
, authorization
);
231 final HttpSession httpSession
= request
.getSession();
232 AccessControlContext acc
= (AccessControlContext
) httpSession
233 .getAttribute(AuthConstants
.ACCESS_CONTROL_CONTEXT
);
235 subject
= Subject
.getSubject(acc
);
237 // Process basic auth
238 String basicAuth
= request
.getHeader(HEADER_AUTHORIZATION
);
239 if (basicAuth
!= null) {
240 CallbackHandler token
= basicAuth(basicAuth
);
242 LoginContext lc
= new LoginContext(
243 AuthConstants
.LOGIN_CONTEXT_USER
, token
);
245 subject
= lc
.getSubject();
246 } catch (LoginException e
) {
247 throw new CmsException("Could not login", e
);
249 Subject
.doAs(subject
, new PrivilegedAction
<Void
>() {
251 // add security context to session
252 httpSession
.setAttribute(ACCESS_CONTROL_CONTEXT
,
253 AccessController
.getContext());
258 requestBasicAuth(httpSession
, response
);
262 // authenticate request
263 Authorization authorization
= subject
264 .getPrivateCredentials(Authorization
.class).iterator()
266 request
.setAttribute(AUTHORIZATION
, authorization
);
271 public URL
getResource(String name
) {
272 return Activator
.getBundleContext().getBundle().getResource(name
);
276 public String
getMimeType(String name
) {
283 * Implements an open session in view patter: a new JCR session is created
286 private class OpenInViewSessionProvider
implements SessionProvider
,
288 private static final long serialVersionUID
= 2270957712453841368L;
290 public Session
getSession(HttpServletRequest request
, Repository rep
,
291 String workspace
) throws javax
.jcr
.LoginException
,
292 ServletException
, RepositoryException
{
293 return login(request
, rep
, workspace
);
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
);
306 public void releaseSession(Session session
) {
307 JcrUtils
.logoutQuietly(session
);
308 if (log
.isTraceEnabled())
309 log
.trace("Logged out remote JCR session " + session
);
313 private class WebdavServlet
extends SimpleWebdavServlet
{
314 private static final long serialVersionUID
= -4687354117811443881L;
315 private final Repository repository
;
317 public WebdavServlet(Repository repository
,
318 SessionProvider sessionProvider
) {
319 this.repository
= repository
;
320 setSessionProvider(sessionProvider
);
323 public Repository
getRepository() {
328 protected void service(final HttpServletRequest request
,
329 final HttpServletResponse response
) throws ServletException
,
332 Subject subject
= subjectFromRequest(request
);
333 Subject
.doAs(subject
, new PrivilegedExceptionAction
<Void
>() {
335 public Void
run() throws Exception
{
336 WebdavServlet
.super.service(request
, response
);
340 } catch (PrivilegedActionException e
) {
341 throw new CmsException("Cannot process webdav request",
347 private class RemotingServlet
extends JcrRemotingServlet
{
348 private static final long serialVersionUID
= 4605238259548058883L;
349 private final Repository repository
;
350 private final SessionProvider sessionProvider
;
352 public RemotingServlet(Repository repository
,
353 SessionProvider sessionProvider
) {
354 this.repository
= repository
;
355 this.sessionProvider
= sessionProvider
;
359 protected Repository
getRepository() {
364 protected SessionProvider
getSessionProvider() {
365 return sessionProvider
;
369 protected void service(final HttpServletRequest request
,
370 final HttpServletResponse response
) throws ServletException
,
373 Subject subject
= subjectFromRequest(request
);
374 Subject
.doAs(subject
, new PrivilegedExceptionAction
<Void
>() {
376 public Void
run() throws Exception
{
377 RemotingServlet
.super.service(request
, response
);
381 } catch (PrivilegedActionException e
) {
382 throw new CmsException("Cannot process JCR remoting request",