]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java
490a1134f4f19ffac9d9ca4ac0ee730389c6bcd5
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / NodeHttp.java
1 package org.argeo.cms.internal.kernel;
2
3 import java.io.IOException;
4 import java.util.Enumeration;
5 import java.util.Properties;
6 import java.util.StringTokenizer;
7
8 import javax.servlet.FilterChain;
9 import javax.servlet.Servlet;
10 import javax.servlet.ServletException;
11 import javax.servlet.ServletRequest;
12 import javax.servlet.http.HttpServletRequest;
13 import javax.servlet.http.HttpServletResponse;
14 import javax.servlet.http.HttpSession;
15
16 import org.apache.commons.codec.binary.Base64;
17 import org.apache.commons.logging.Log;
18 import org.apache.commons.logging.LogFactory;
19 import org.argeo.cms.CmsException;
20 import org.argeo.jackrabbit.servlet.OpenInViewSessionProvider;
21 import org.argeo.jackrabbit.servlet.RemotingServlet;
22 import org.argeo.jackrabbit.servlet.WebdavServlet;
23 import org.argeo.jcr.ArgeoJcrConstants;
24 import org.eclipse.equinox.http.servlet.ExtendedHttpService;
25 import org.eclipse.jetty.servlets.DoSFilter;
26 import org.eclipse.jetty.servlets.QoSFilter;
27 import org.osgi.framework.BundleContext;
28 import org.osgi.service.http.NamespaceException;
29 import org.osgi.util.tracker.ServiceTracker;
30 import org.springframework.security.authentication.AuthenticationManager;
31 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
32 import org.springframework.security.core.Authentication;
33 import org.springframework.security.core.context.SecurityContext;
34 import org.springframework.security.core.context.SecurityContextHolder;
35
36 /**
37 * Intercepts and enriches http access, mainly focusing on security and
38 * transactionality.
39 */
40 class NodeHttp implements KernelConstants, ArgeoJcrConstants {
41 private final static Log log = LogFactory.getLog(NodeHttp.class);
42
43 private final static String ATTR_AUTH = "auth";
44 private final static String HEADER_AUTHORIZATION = "Authorization";
45 private final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
46
47 private final AuthenticationManager authenticationManager;
48 private final BundleContext bundleContext;
49 private ExtendedHttpService httpService;
50
51 // FIXME Make it more unique
52 private String httpAuthRealm = "Argeo";
53
54 // Filters
55 private final RootFilter rootFilter;
56 private final DoSFilter dosFilter;
57 private final QoSFilter qosFilter;
58
59 // remoting
60 private OpenInViewSessionProvider sessionProvider;
61 private WebdavServlet publicWebdavServlet;
62 private WebdavServlet privateWebdavServlet;
63 private RemotingServlet publicRemotingServlet;
64 private RemotingServlet privateRemotingServlet;
65
66 NodeHttp(BundleContext bundleContext, JackrabbitNode node,
67 NodeSecurity authenticationManager) {
68 this.bundleContext = bundleContext;
69 this.authenticationManager = authenticationManager;
70
71 // Equinox dependency
72 ServiceTracker<ExtendedHttpService, ExtendedHttpService> st = new ServiceTracker<ExtendedHttpService, ExtendedHttpService>(
73 bundleContext, ExtendedHttpService.class, null);
74 st.open();
75 try {
76 httpService = st.waitForService(1000);
77 } catch (InterruptedException e) {
78 httpService = null;
79 }
80
81 if (httpService == null)
82 throw new CmsException("Could not find "
83 + ExtendedHttpService.class + " service.");
84
85 // Filters
86 rootFilter = new RootFilter();
87 dosFilter = new CustomDosFilter();
88 qosFilter = new QoSFilter();
89
90 // DAV
91 sessionProvider = new OpenInViewSessionProvider();
92 publicWebdavServlet = new WebdavServlet(node, sessionProvider);
93 privateWebdavServlet = new WebdavServlet(node, sessionProvider);
94 publicRemotingServlet = new RemotingServlet(node, sessionProvider);
95 privateRemotingServlet = new RemotingServlet(node, sessionProvider);
96 }
97
98 void publish() {
99 try {
100 registerWebdavServlet(PATH_WEBDAV_PUBLIC, ALIAS_NODE, true,
101 publicWebdavServlet);
102 registerWebdavServlet(PATH_WEBDAV_PRIVATE, ALIAS_NODE, false,
103 privateWebdavServlet);
104 registerRemotingServlet(PATH_REMOTING_PUBLIC, ALIAS_NODE, true,
105 publicRemotingServlet);
106 registerRemotingServlet(PATH_REMOTING_PRIVATE, ALIAS_NODE, false,
107 privateRemotingServlet);
108
109 httpService.registerFilter("/", dosFilter, null, null);
110 httpService.registerFilter("/", rootFilter, null, null);
111 httpService.registerFilter("/", qosFilter, null, null);
112 } catch (Exception e) {
113 throw new CmsException("Cannot publish HTTP services to OSGi", e);
114 }
115 }
116
117 private void registerWebdavServlet(String pathPrefix, String alias,
118 Boolean anonymous, WebdavServlet webdavServlet)
119 throws NamespaceException, ServletException {
120 String path = pathPrefix + "/" + alias;
121 Properties initParameters = new Properties();
122 initParameters.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG,
123 KernelConstants.WEBDAV_CONFIG);
124 initParameters.setProperty(
125 WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
126 httpService.registerFilter(path, anonymous ? new AnonymousFilter()
127 : new DavFilter(), null, null);
128 // Cast to servlet because of a weird behaviour in Eclipse
129 httpService.registerServlet(path, (Servlet) webdavServlet,
130 initParameters, null);
131 }
132
133 private void registerRemotingServlet(String pathPrefix, String alias,
134 Boolean anonymous, RemotingServlet remotingServlet)
135 throws NamespaceException, ServletException {
136 String path = pathPrefix + "/" + alias;
137 Properties initParameters = new Properties();
138 initParameters.setProperty(
139 RemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
140
141 // Looks like a bug in Jackrabbit remoting init
142 initParameters.setProperty(RemotingServlet.INIT_PARAM_HOME,
143 KernelUtils.getOsgiInstanceDir(bundleContext)
144 + "/tmp/jackrabbit");
145 initParameters.setProperty(RemotingServlet.INIT_PARAM_TMP_DIRECTORY,
146 "remoting");
147 // Cast to servlet because of a weird behaviour in Eclipse
148 httpService.registerFilter(path, anonymous ? new AnonymousFilter()
149 : new DavFilter(), null, null);
150 httpService.registerServlet(path, (Servlet) remotingServlet,
151 initParameters, null);
152 }
153
154 private Boolean isSessionAuthenticated(HttpSession httpSession) {
155 SecurityContext contextFromSession = (SecurityContext) httpSession
156 .getAttribute(SPRING_SECURITY_CONTEXT_KEY);
157 return contextFromSession != null;
158 }
159
160 private void requestBasicAuth(HttpSession httpSession,
161 HttpServletResponse response) {
162 response.setStatus(401);
163 response.setHeader(HEADER_WWW_AUTHENTICATE, "basic realm=\""
164 + httpAuthRealm + "\"");
165 httpSession.setAttribute(ATTR_AUTH, Boolean.TRUE);
166 }
167
168 private UsernamePasswordAuthenticationToken basicAuth(String authHeader) {
169 if (authHeader != null) {
170 StringTokenizer st = new StringTokenizer(authHeader);
171 if (st.hasMoreTokens()) {
172 String basic = st.nextToken();
173 if (basic.equalsIgnoreCase("Basic")) {
174 try {
175 String credentials = new String(Base64.decodeBase64(st
176 .nextToken()), "UTF-8");
177 // log.debug("Credentials: " + credentials);
178 int p = credentials.indexOf(":");
179 if (p != -1) {
180 String login = credentials.substring(0, p).trim();
181 String password = credentials.substring(p + 1)
182 .trim();
183
184 return new UsernamePasswordAuthenticationToken(
185 login, password.toCharArray());
186 } else {
187 throw new CmsException(
188 "Invalid authentication token");
189 }
190 } catch (Exception e) {
191 throw new CmsException(
192 "Couldn't retrieve authentication", e);
193 }
194 }
195 }
196 }
197 throw new CmsException("Couldn't retrieve authentication");
198 }
199
200 /** Intercepts all requests. Authenticates. */
201 class RootFilter extends HttpFilter {
202
203 @Override
204 public void doFilter(HttpSession httpSession,
205 HttpServletRequest request, HttpServletResponse response,
206 FilterChain filterChain) throws IOException, ServletException {
207 if (log.isTraceEnabled()) {
208 log.debug(request.getContextPath());
209 log.debug(request.getServletPath());
210 log.debug(request.getRequestURI());
211 log.debug(request.getQueryString());
212 StringBuilder buf = new StringBuilder();
213 Enumeration<String> en = request.getHeaderNames();
214 while (en.hasMoreElements()) {
215 String header = en.nextElement();
216 Enumeration<String> values = request.getHeaders(header);
217 while (values.hasMoreElements())
218 buf.append(" " + header + ": " + values.nextElement());
219 buf.append('\n');
220 }
221 log.debug("\n" + buf);
222 }
223
224 String servletPath = request.getServletPath();
225
226 // skip data
227 if (servletPath.startsWith(PATH_DATA)) {
228 filterChain.doFilter(request, response);
229 return;
230 }
231
232 // redirect long RWT paths to anchor
233 String path = request.getRequestURI()
234 .substring(servletPath.length()).trim();
235 if (!servletPath.endsWith("rwt-resources") && !path.equals("")
236 && !path.equals("/")) {
237 String newLocation = request.getServletPath() + "#" + path;
238 response.setHeader("Location", newLocation);
239 response.setStatus(HttpServletResponse.SC_FOUND);
240 return;
241 }
242
243 // process normally
244 filterChain.doFilter(request, response);
245 }
246 }
247
248 /** Intercepts all requests. Authenticates. */
249 class AnonymousFilter extends HttpFilter {
250 @Override
251 public void doFilter(HttpSession httpSession,
252 HttpServletRequest request, HttpServletResponse response,
253 FilterChain filterChain) throws IOException, ServletException {
254
255 // Authenticate from session
256 if (isSessionAuthenticated(httpSession)) {
257 filterChain.doFilter(request, response);
258 return;
259 }
260
261 KernelUtils.anonymousLogin(authenticationManager);
262 filterChain.doFilter(request, response);
263 }
264 }
265
266 /** Intercepts all requests. Authenticates. */
267 class DavFilter extends HttpFilter {
268
269 @Override
270 public void doFilter(HttpSession httpSession,
271 HttpServletRequest request, HttpServletResponse response,
272 FilterChain filterChain) throws IOException, ServletException {
273
274 // Authenticate from session
275 // if (isSessionAuthenticated(httpSession)) {
276 // filterChain.doFilter(request, response);
277 // return;
278 // }
279
280 // Process basic auth
281 String basicAuth = request.getHeader(HEADER_AUTHORIZATION);
282 if (basicAuth != null) {
283 UsernamePasswordAuthenticationToken token = basicAuth(basicAuth);
284 Authentication auth = authenticationManager.authenticate(token);
285 SecurityContextHolder.getContext().setAuthentication(auth);
286 httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY,
287 SecurityContextHolder.getContext());
288 httpSession.setAttribute(ATTR_AUTH, Boolean.FALSE);
289 filterChain.doFilter(request, response);
290 return;
291 }
292
293 requestBasicAuth(httpSession, response);
294 }
295 }
296
297 class CustomDosFilter extends DoSFilter {
298 @Override
299 protected String extractUserId(ServletRequest request) {
300 HttpSession httpSession = ((HttpServletRequest) request)
301 .getSession();
302 if (isSessionAuthenticated(httpSession)) {
303 String userId = ((SecurityContext) httpSession
304 .getAttribute(SPRING_SECURITY_CONTEXT_KEY))
305 .getAuthentication().getName();
306 return userId;
307 }
308 return super.extractUserId(request);
309
310 }
311 }
312 }