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