1 package org
.argeo
.cms
.internal
.kernel
;
3 import java
.io
.IOException
;
4 import java
.security
.PrivilegedActionException
;
5 import java
.security
.PrivilegedExceptionAction
;
6 import java
.security
.cert
.X509Certificate
;
7 import java
.util
.Enumeration
;
8 import java
.util
.Properties
;
9 import java
.util
.StringTokenizer
;
11 import javax
.jcr
.Repository
;
12 import javax
.security
.auth
.Subject
;
13 import javax
.security
.auth
.callback
.Callback
;
14 import javax
.security
.auth
.callback
.CallbackHandler
;
15 import javax
.security
.auth
.callback
.NameCallback
;
16 import javax
.security
.auth
.callback
.PasswordCallback
;
17 import javax
.security
.auth
.login
.LoginContext
;
18 import javax
.security
.auth
.login
.LoginException
;
19 import javax
.servlet
.FilterChain
;
20 import javax
.servlet
.Servlet
;
21 import javax
.servlet
.ServletException
;
22 import javax
.servlet
.http
.HttpServletRequest
;
23 import javax
.servlet
.http
.HttpServletResponse
;
24 import javax
.servlet
.http
.HttpSession
;
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
.argeo
.cms
.CmsException
;
30 import org
.argeo
.cms
.KernelHeader
;
31 import org
.argeo
.jackrabbit
.servlet
.OpenInViewSessionProvider
;
32 import org
.argeo
.jackrabbit
.servlet
.RemotingServlet
;
33 import org
.argeo
.jackrabbit
.servlet
.WebdavServlet
;
34 import org
.argeo
.jcr
.ArgeoJcrConstants
;
35 import org
.eclipse
.equinox
.http
.servlet
.ExtendedHttpService
;
36 import org
.osgi
.service
.http
.NamespaceException
;
39 * Intercepts and enriches http access, mainly focusing on security and
42 class NodeHttp
implements KernelConstants
, ArgeoJcrConstants
{
43 private final static Log log
= LogFactory
.getLog(NodeHttp
.class);
45 private final static String ATTR_AUTH
= "auth";
46 private final static String HEADER_AUTHORIZATION
= "Authorization";
47 private final static String HEADER_WWW_AUTHENTICATE
= "WWW-Authenticate";
49 // private final AuthenticationManager authenticationManager;
50 private final ExtendedHttpService httpService
;
52 // FIXME Make it more unique
53 private String httpAuthRealm
= "Argeo";
56 private final RootFilter rootFilter
;
57 // private final DoSFilter dosFilter;
58 // private final QoSFilter qosFilter;
60 // WebDav / JCR remoting
61 private OpenInViewSessionProvider sessionProvider
;
63 NodeHttp(ExtendedHttpService httpService
, JackrabbitNode node
) {
64 // this.bundleContext = bundleContext;
65 // this.authenticationManager = authenticationManager;
67 this.httpService
= httpService
;
70 rootFilter
= new RootFilter();
71 // dosFilter = new CustomDosFilter();
72 // qosFilter = new QoSFilter();
75 sessionProvider
= new OpenInViewSessionProvider();
77 registerRepositoryServlets(ALIAS_NODE
, node
);
79 httpService
.registerFilter("/", rootFilter
, null, null);
80 } catch (Exception e
) {
81 throw new CmsException("Could not register root filter", e
);
85 public void destroy() {
86 sessionProvider
.destroy();
87 unregisterRepositoryServlets(ALIAS_NODE
);
90 void registerRepositoryServlets(String alias
, Repository repository
) {
92 registerWebdavServlet(alias
, repository
, true);
93 registerWebdavServlet(alias
, repository
, false);
94 registerRemotingServlet(alias
, repository
, true);
95 registerRemotingServlet(alias
, repository
, false);
96 } catch (Exception e
) {
97 throw new CmsException(
98 "Could not register servlets for repository " + alias
, e
);
102 void unregisterRepositoryServlets(String alias
) {
103 // FIXME unregister servlets
106 void registerWebdavServlet(String alias
, Repository repository
,
107 boolean anonymous
) throws NamespaceException
, ServletException
{
108 WebdavServlet webdavServlet
= new WebdavServlet(repository
,
110 String pathPrefix
= anonymous ? WEBDAV_PUBLIC
: WEBDAV_PRIVATE
;
111 String path
= pathPrefix
+ "/" + alias
;
112 Properties ip
= new Properties();
113 ip
.setProperty(WebdavServlet
.INIT_PARAM_RESOURCE_CONFIG
, WEBDAV_CONFIG
);
114 ip
.setProperty(WebdavServlet
.INIT_PARAM_RESOURCE_PATH_PREFIX
, path
);
115 httpService
.registerFilter(path
, anonymous ?
new AnonymousFilter()
116 : new DavFilter(), null, null);
117 // Cast to servlet because of a weird behaviour in Eclipse
118 httpService
.registerServlet(path
, (Servlet
) webdavServlet
, ip
, null);
121 void registerRemotingServlet(String alias
, Repository repository
,
122 boolean anonymous
) throws NamespaceException
, ServletException
{
123 String pathPrefix
= anonymous ? REMOTING_PUBLIC
: REMOTING_PRIVATE
;
124 RemotingServlet remotingServlet
= new RemotingServlet(repository
,
126 String path
= pathPrefix
+ "/" + alias
;
127 Properties ip
= new Properties();
128 ip
.setProperty(RemotingServlet
.INIT_PARAM_RESOURCE_PATH_PREFIX
, path
);
130 // Looks like a bug in Jackrabbit remoting init
131 ip
.setProperty(RemotingServlet
.INIT_PARAM_HOME
,
132 KernelUtils
.getOsgiInstanceDir() + "/tmp/jackrabbit");
133 ip
.setProperty(RemotingServlet
.INIT_PARAM_TMP_DIRECTORY
, "remoting");
134 // Cast to servlet because of a weird behaviour in Eclipse
135 httpService
.registerFilter(path
, anonymous ?
new AnonymousFilter()
136 : new DavFilter(), null, null);
137 httpService
.registerServlet(path
, (Servlet
) remotingServlet
, ip
, null);
140 // private Boolean isSessionAuthenticated(HttpSession httpSession) {
141 // SecurityContext contextFromSession = (SecurityContext) httpSession
142 // .getAttribute(SPRING_SECURITY_CONTEXT_KEY);
143 // return contextFromSession != null;
146 private void requestBasicAuth(HttpSession httpSession
,
147 HttpServletResponse response
) {
148 response
.setStatus(401);
149 response
.setHeader(HEADER_WWW_AUTHENTICATE
, "basic realm=\""
150 + httpAuthRealm
+ "\"");
151 httpSession
.setAttribute(ATTR_AUTH
, Boolean
.TRUE
);
154 private CallbackHandler
basicAuth(String authHeader
) {
155 if (authHeader
!= null) {
156 StringTokenizer st
= new StringTokenizer(authHeader
);
157 if (st
.hasMoreTokens()) {
158 String basic
= st
.nextToken();
159 if (basic
.equalsIgnoreCase("Basic")) {
161 // TODO manipulate char[]
162 String credentials
= new String(Base64
.decodeBase64(st
163 .nextToken()), "UTF-8");
164 // log.debug("Credentials: " + credentials);
165 int p
= credentials
.indexOf(":");
167 final String login
= credentials
.substring(0, p
)
169 final char[] password
= credentials
170 .substring(p
+ 1).trim().toCharArray();
172 return new CallbackHandler() {
173 public void handle(Callback
[] callbacks
) {
174 for (Callback cb
: callbacks
) {
175 if (cb
instanceof NameCallback
)
176 ((NameCallback
) cb
).setName(login
);
177 else if (cb
instanceof PasswordCallback
)
178 ((PasswordCallback
) cb
)
179 .setPassword(password
);
184 throw new CmsException(
185 "Invalid authentication token");
187 } catch (Exception e
) {
188 throw new CmsException(
189 "Couldn't retrieve authentication", e
);
194 throw new CmsException("Couldn't retrieve authentication");
197 /** Intercepts all requests. Authenticates. */
198 class RootFilter
extends HttpFilter
{
201 public void doFilter(HttpSession httpSession
,
202 HttpServletRequest request
, HttpServletResponse response
,
203 FilterChain filterChain
) throws IOException
, ServletException
{
204 if (log
.isTraceEnabled()) {
205 log
.trace(request
.getRequestURL().append(
206 request
.getQueryString() != null ?
"?"
207 + request
.getQueryString() : ""));
211 String servletPath
= request
.getServletPath();
213 // client certificate
214 X509Certificate clientCert
= extractCertificate(request
);
215 if (clientCert
!= null) {
217 // if (log.isDebugEnabled())
218 // log.debug(clientCert.getSubjectX500Principal().getName());
222 if (servletPath
.startsWith(PATH_DATA
)) {
223 filterChain
.doFilter(request
, response
);
227 // skip /ui (workbench) for the time being
228 if (servletPath
.startsWith(PATH_WORKBENCH
)) {
229 filterChain
.doFilter(request
, response
);
233 // redirect long RWT paths to anchor
234 String path
= request
.getRequestURI().substring(
235 servletPath
.length());
236 int pathLength
= path
.length();
237 if (pathLength
!= 0 && (path
.charAt(0) == '/')
238 && !servletPath
.endsWith("rwt-resources")
239 && path
.lastIndexOf('/') != 0) {
240 String newLocation
= request
.getServletPath() + "#" + path
;
241 response
.setHeader("Location", newLocation
);
242 response
.setStatus(HttpServletResponse
.SC_FOUND
);
247 filterChain
.doFilter(request
, response
);
251 private void logRequest(HttpServletRequest request
) {
252 log
.debug("contextPath=" + request
.getContextPath());
253 log
.debug("servletPath=" + request
.getServletPath());
254 log
.debug("requestURI=" + request
.getRequestURI());
255 log
.debug("queryString=" + request
.getQueryString());
256 StringBuilder buf
= new StringBuilder();
258 Enumeration
<String
> en
= request
.getHeaderNames();
259 while (en
.hasMoreElements()) {
260 String header
= en
.nextElement();
261 Enumeration
<String
> values
= request
.getHeaders(header
);
262 while (values
.hasMoreElements())
263 buf
.append(" " + header
+ ": " + values
.nextElement());
268 Enumeration
<String
> an
= request
.getAttributeNames();
269 while (an
.hasMoreElements()) {
270 String attr
= an
.nextElement();
271 Object value
= request
.getAttribute(attr
);
272 buf
.append(" " + attr
+ ": " + value
);
275 log
.debug("\n" + buf
);
278 private X509Certificate
extractCertificate(HttpServletRequest req
) {
279 X509Certificate
[] certs
= (X509Certificate
[]) req
280 .getAttribute("javax.servlet.request.X509Certificate");
281 if (null != certs
&& certs
.length
> 0) {
287 /** Intercepts all requests. Authenticates. */
288 private class AnonymousFilter
extends HttpFilter
{
290 public void doFilter(HttpSession httpSession
,
291 final HttpServletRequest request
,
292 final HttpServletResponse response
,
293 final FilterChain filterChain
) throws IOException
,
296 // Authenticate from session
297 // if (isSessionAuthenticated(httpSession)) {
298 // filterChain.doFilter(request, response);
302 Subject subject
= KernelUtils
.anonymousLogin();
304 Subject
.doAs(subject
, new PrivilegedExceptionAction
<Void
>() {
305 public Void
run() throws IOException
, ServletException
{
306 filterChain
.doFilter(request
, response
);
310 } catch (PrivilegedActionException e
) {
311 if (e
.getCause() instanceof ServletException
)
312 throw (ServletException
) e
.getCause();
313 else if (e
.getCause() instanceof IOException
)
314 throw (IOException
) e
.getCause();
316 throw new CmsException("Unexpected exception", e
.getCause());
321 /** Intercepts all requests. Authenticates. */
322 private class DavFilter
extends HttpFilter
{
325 public void doFilter(HttpSession httpSession
,
326 final HttpServletRequest request
,
327 final HttpServletResponse response
,
328 final FilterChain filterChain
) throws IOException
,
331 // Process basic auth
332 String basicAuth
= request
.getHeader(HEADER_AUTHORIZATION
);
333 if (basicAuth
!= null) {
334 CallbackHandler token
= basicAuth(basicAuth
);
336 // Authentication auth =
337 // authenticationManager.authenticate(token);
338 // SecurityContextHolder.getContext().setAuthentication(auth);
339 // filterChain.doFilter(request, response);
342 LoginContext lc
= new LoginContext(
343 KernelHeader
.LOGIN_CONTEXT_USER
, token
);
345 subject
= lc
.getSubject();
346 } catch (LoginException e
) {
347 throw new CmsException("Could not login", e
);
350 Subject
.doAs(subject
,
351 new PrivilegedExceptionAction
<Void
>() {
352 public Void
run() throws IOException
,
354 filterChain
.doFilter(request
, response
);
358 } catch (PrivilegedActionException e
) {
359 if (e
.getCause() instanceof ServletException
)
360 throw (ServletException
) e
.getCause();
361 else if (e
.getCause() instanceof IOException
)
362 throw (IOException
) e
.getCause();
364 throw new CmsException("Unexpected exception",
370 requestBasicAuth(httpSession
, response
);
374 // class CustomDosFilter extends DoSFilter {
376 // protected String extractUserId(ServletRequest request) {
377 // HttpSession httpSession = ((HttpServletRequest) request)
379 // if (isSessionAuthenticated(httpSession)) {
380 // String userId = ((SecurityContext) httpSession
381 // .getAttribute(SPRING_SECURITY_CONTEXT_KEY))
382 // .getAuthentication().getName();
385 // return super.extractUserId(request);