1 package org
.argeo
.cms
.internal
.kernel
;
3 import static org
.argeo
.cms
.KernelHeader
.ACCESS_CONTROL_CONTEXT
;
5 import java
.io
.IOException
;
6 import java
.security
.AccessControlContext
;
7 import java
.security
.AccessController
;
8 import java
.security
.PrivilegedActionException
;
9 import java
.security
.PrivilegedExceptionAction
;
10 import java
.security
.cert
.X509Certificate
;
11 import java
.util
.Enumeration
;
12 import java
.util
.Properties
;
13 import java
.util
.StringTokenizer
;
15 import javax
.jcr
.Repository
;
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
.LoginContext
;
22 import javax
.security
.auth
.login
.LoginException
;
23 import javax
.servlet
.FilterChain
;
24 import javax
.servlet
.Servlet
;
25 import javax
.servlet
.ServletException
;
26 import javax
.servlet
.http
.HttpServletRequest
;
27 import javax
.servlet
.http
.HttpServletResponse
;
28 import javax
.servlet
.http
.HttpSession
;
30 import org
.apache
.commons
.codec
.binary
.Base64
;
31 import org
.apache
.commons
.logging
.Log
;
32 import org
.apache
.commons
.logging
.LogFactory
;
33 import org
.argeo
.cms
.CmsException
;
34 import org
.argeo
.cms
.KernelHeader
;
35 import org
.argeo
.jackrabbit
.servlet
.OpenInViewSessionProvider
;
36 import org
.argeo
.jackrabbit
.servlet
.RemotingServlet
;
37 import org
.argeo
.jackrabbit
.servlet
.WebdavServlet
;
38 import org
.argeo
.jcr
.ArgeoJcrConstants
;
39 import org
.eclipse
.equinox
.http
.servlet
.ExtendedHttpService
;
40 import org
.osgi
.service
.http
.NamespaceException
;
43 * Intercepts and enriches http access, mainly focusing on security and
46 class NodeHttp
implements KernelConstants
, ArgeoJcrConstants
{
47 private final static Log log
= LogFactory
.getLog(NodeHttp
.class);
49 private final static String ATTR_AUTH
= "auth";
50 private final static String HEADER_AUTHORIZATION
= "Authorization";
51 private final static String HEADER_WWW_AUTHENTICATE
= "WWW-Authenticate";
53 // private final AuthenticationManager authenticationManager;
54 private final ExtendedHttpService httpService
;
56 // FIXME Make it more unique
57 private String httpAuthRealm
= "Argeo";
60 private final RootFilter rootFilter
;
61 // private final DoSFilter dosFilter;
62 // private final QoSFilter qosFilter;
64 // WebDav / JCR remoting
65 private OpenInViewSessionProvider sessionProvider
;
67 NodeHttp(ExtendedHttpService httpService
, JackrabbitNode node
) {
68 // this.bundleContext = bundleContext;
69 // this.authenticationManager = authenticationManager;
71 this.httpService
= httpService
;
74 rootFilter
= new RootFilter();
75 // dosFilter = new CustomDosFilter();
76 // qosFilter = new QoSFilter();
79 sessionProvider
= new OpenInViewSessionProvider();
81 registerRepositoryServlets(ALIAS_NODE
, node
);
83 httpService
.registerFilter("/", rootFilter
, null, null);
84 } catch (Exception e
) {
85 throw new CmsException("Could not register root filter", e
);
89 public void destroy() {
90 sessionProvider
.destroy();
91 unregisterRepositoryServlets(ALIAS_NODE
);
94 void registerRepositoryServlets(String alias
, Repository repository
) {
96 registerWebdavServlet(alias
, repository
, true);
97 registerWebdavServlet(alias
, repository
, false);
98 registerRemotingServlet(alias
, repository
, true);
99 registerRemotingServlet(alias
, repository
, false);
100 } catch (Exception e
) {
101 throw new CmsException(
102 "Could not register servlets for repository " + alias
, e
);
106 void unregisterRepositoryServlets(String alias
) {
107 // FIXME unregister servlets
110 void registerWebdavServlet(String alias
, Repository repository
,
111 boolean anonymous
) throws NamespaceException
, ServletException
{
112 WebdavServlet webdavServlet
= new WebdavServlet(repository
,
114 String pathPrefix
= anonymous ? WEBDAV_PUBLIC
: WEBDAV_PRIVATE
;
115 String path
= pathPrefix
+ "/" + alias
;
116 Properties ip
= new Properties();
117 ip
.setProperty(WebdavServlet
.INIT_PARAM_RESOURCE_CONFIG
, WEBDAV_CONFIG
);
118 ip
.setProperty(WebdavServlet
.INIT_PARAM_RESOURCE_PATH_PREFIX
, path
);
119 httpService
.registerFilter(path
, anonymous ?
new AnonymousFilter()
120 : new DavFilter(), null, null);
121 // Cast to servlet because of a weird behaviour in Eclipse
122 httpService
.registerServlet(path
, (Servlet
) webdavServlet
, ip
, null);
125 void registerRemotingServlet(String alias
, Repository repository
,
126 boolean anonymous
) throws NamespaceException
, ServletException
{
127 String pathPrefix
= anonymous ? REMOTING_PUBLIC
: REMOTING_PRIVATE
;
128 RemotingServlet remotingServlet
= new RemotingServlet(repository
,
130 String path
= pathPrefix
+ "/" + alias
;
131 Properties ip
= new Properties();
132 ip
.setProperty(RemotingServlet
.INIT_PARAM_RESOURCE_PATH_PREFIX
, path
);
134 // Looks like a bug in Jackrabbit remoting init
135 ip
.setProperty(RemotingServlet
.INIT_PARAM_HOME
,
136 KernelUtils
.getOsgiInstanceDir() + "/tmp/jackrabbit");
137 ip
.setProperty(RemotingServlet
.INIT_PARAM_TMP_DIRECTORY
, "remoting");
138 // in order to avoid annoying warning.
139 ip
.setProperty(RemotingServlet
.INIT_PARAM_PROTECTED_HANDLERS_CONFIG
,
141 // Cast to servlet because of a weird behaviour in Eclipse
142 httpService
.registerFilter(path
, anonymous ?
new AnonymousFilter()
143 : new DavFilter(), null, null);
144 httpService
.registerServlet(path
, (Servlet
) remotingServlet
, ip
, null);
147 // private Boolean isSessionAuthenticated(HttpSession httpSession) {
148 // SecurityContext contextFromSession = (SecurityContext) httpSession
149 // .getAttribute(SPRING_SECURITY_CONTEXT_KEY);
150 // return contextFromSession != null;
153 private void requestBasicAuth(HttpSession httpSession
,
154 HttpServletResponse response
) {
155 response
.setStatus(401);
156 response
.setHeader(HEADER_WWW_AUTHENTICATE
, "basic realm=\""
157 + httpAuthRealm
+ "\"");
158 httpSession
.setAttribute(ATTR_AUTH
, Boolean
.TRUE
);
161 private CallbackHandler
basicAuth(String authHeader
) {
162 if (authHeader
!= null) {
163 StringTokenizer st
= new StringTokenizer(authHeader
);
164 if (st
.hasMoreTokens()) {
165 String basic
= st
.nextToken();
166 if (basic
.equalsIgnoreCase("Basic")) {
168 // TODO manipulate char[]
169 String credentials
= new String(Base64
.decodeBase64(st
170 .nextToken()), "UTF-8");
171 // log.debug("Credentials: " + credentials);
172 int p
= credentials
.indexOf(":");
174 final String login
= credentials
.substring(0, p
)
176 final char[] password
= credentials
177 .substring(p
+ 1).trim().toCharArray();
179 return new CallbackHandler() {
180 public void handle(Callback
[] callbacks
) {
181 for (Callback cb
: callbacks
) {
182 if (cb
instanceof NameCallback
)
183 ((NameCallback
) cb
).setName(login
);
184 else if (cb
instanceof PasswordCallback
)
185 ((PasswordCallback
) cb
)
186 .setPassword(password
);
191 throw new CmsException(
192 "Invalid authentication token");
194 } catch (Exception e
) {
195 throw new CmsException(
196 "Couldn't retrieve authentication", e
);
201 throw new CmsException("Couldn't retrieve authentication");
204 /** Intercepts all requests. Authenticates. */
205 class RootFilter
extends HttpFilter
{
208 public void doFilter(HttpSession httpSession
,
209 HttpServletRequest request
, HttpServletResponse response
,
210 FilterChain filterChain
) throws IOException
, ServletException
{
211 if (log
.isTraceEnabled()) {
212 log
.trace(request
.getRequestURL().append(
213 request
.getQueryString() != null ?
"?"
214 + request
.getQueryString() : ""));
218 String servletPath
= request
.getServletPath();
220 // client certificate
221 X509Certificate clientCert
= extractCertificate(request
);
222 if (clientCert
!= null) {
224 // if (log.isDebugEnabled())
225 // log.debug(clientCert.getSubjectX500Principal().getName());
229 if (servletPath
.startsWith(PATH_DATA
)) {
230 filterChain
.doFilter(request
, response
);
234 // skip /ui (workbench) for the time being
235 if (servletPath
.startsWith(PATH_WORKBENCH
)) {
236 filterChain
.doFilter(request
, response
);
240 // redirect long RWT paths to anchor
241 String path
= request
.getRequestURI().substring(
242 servletPath
.length());
243 int pathLength
= path
.length();
244 if (pathLength
!= 0 && (path
.charAt(0) == '/')
245 && !servletPath
.endsWith("rwt-resources")
246 && !path
.startsWith(KernelConstants
.PATH_WORKBENCH
)
247 && path
.lastIndexOf('/') != 0) {
248 String newLocation
= request
.getServletPath() + "#" + path
;
249 response
.setHeader("Location", newLocation
);
250 response
.setStatus(HttpServletResponse
.SC_FOUND
);
255 filterChain
.doFilter(request
, response
);
259 private void logRequest(HttpServletRequest request
) {
260 log
.debug("contextPath=" + request
.getContextPath());
261 log
.debug("servletPath=" + request
.getServletPath());
262 log
.debug("requestURI=" + request
.getRequestURI());
263 log
.debug("queryString=" + request
.getQueryString());
264 StringBuilder buf
= new StringBuilder();
266 Enumeration
<String
> en
= request
.getHeaderNames();
267 while (en
.hasMoreElements()) {
268 String header
= en
.nextElement();
269 Enumeration
<String
> values
= request
.getHeaders(header
);
270 while (values
.hasMoreElements())
271 buf
.append(" " + header
+ ": " + values
.nextElement());
276 Enumeration
<String
> an
= request
.getAttributeNames();
277 while (an
.hasMoreElements()) {
278 String attr
= an
.nextElement();
279 Object value
= request
.getAttribute(attr
);
280 buf
.append(" " + attr
+ ": " + value
);
283 log
.debug("\n" + buf
);
286 private X509Certificate
extractCertificate(HttpServletRequest req
) {
287 X509Certificate
[] certs
= (X509Certificate
[]) req
288 .getAttribute("javax.servlet.request.X509Certificate");
289 if (null != certs
&& certs
.length
> 0) {
295 /** Intercepts all requests. Authenticates. */
296 private class AnonymousFilter
extends HttpFilter
{
298 public void doFilter(HttpSession httpSession
,
299 final HttpServletRequest request
,
300 final HttpServletResponse response
,
301 final FilterChain filterChain
) throws IOException
,
304 // Authenticate from session
305 // if (isSessionAuthenticated(httpSession)) {
306 // filterChain.doFilter(request, response);
310 Subject subject
= KernelUtils
.anonymousLogin();
312 Subject
.doAs(subject
, new PrivilegedExceptionAction
<Void
>() {
313 public Void
run() throws IOException
, ServletException
{
314 filterChain
.doFilter(request
, response
);
318 } catch (PrivilegedActionException e
) {
319 if (e
.getCause() instanceof ServletException
)
320 throw (ServletException
) e
.getCause();
321 else if (e
.getCause() instanceof IOException
)
322 throw (IOException
) e
.getCause();
324 throw new CmsException("Unexpected exception", e
.getCause());
329 /** Intercepts all requests. Authenticates. */
330 private class DavFilter
extends HttpFilter
{
333 public void doFilter(final HttpSession httpSession
,
334 final HttpServletRequest request
,
335 final HttpServletResponse response
,
336 final FilterChain filterChain
) throws IOException
,
339 AccessControlContext acc
= (AccessControlContext
) httpSession
340 .getAttribute(KernelHeader
.ACCESS_CONTROL_CONTEXT
);
341 final Subject subject
;
343 subject
= Subject
.getSubject(acc
);
345 // Process basic auth
346 String basicAuth
= request
.getHeader(HEADER_AUTHORIZATION
);
347 if (basicAuth
!= null) {
348 CallbackHandler token
= basicAuth(basicAuth
);
350 LoginContext lc
= new LoginContext(
351 KernelHeader
.LOGIN_CONTEXT_USER
, token
);
353 subject
= lc
.getSubject();
354 } catch (LoginException e
) {
355 throw new CmsException("Could not login", e
);
358 requestBasicAuth(httpSession
, response
);
362 // do filter as subject
364 Subject
.doAs(subject
, new PrivilegedExceptionAction
<Void
>() {
365 public Void
run() throws IOException
, ServletException
{
366 // add security context to session
367 httpSession
.setAttribute(ACCESS_CONTROL_CONTEXT
,
368 AccessController
.getContext());
369 filterChain
.doFilter(request
, response
);
373 } catch (PrivilegedActionException e
) {
374 if (e
.getCause() instanceof ServletException
)
375 throw (ServletException
) e
.getCause();
376 else if (e
.getCause() instanceof IOException
)
377 throw (IOException
) e
.getCause();
379 throw new CmsException("Unexpected exception", e
.getCause());
385 // class CustomDosFilter extends DoSFilter {
387 // protected String extractUserId(ServletRequest request) {
388 // HttpSession httpSession = ((HttpServletRequest) request)
390 // if (isSessionAuthenticated(httpSession)) {
391 // String userId = ((SecurityContext) httpSession
392 // .getAttribute(SPRING_SECURITY_CONTEXT_KEY))
393 // .getAuthentication().getName();
396 // return super.extractUserId(request);