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