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