]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java
Fix typo in string validation
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / DataHttp.java
1 package org.argeo.cms.internal.kernel;
2
3 import static org.argeo.cms.auth.AuthConstants.LOGIN_CONTEXT_USER;
4
5 import java.io.IOException;
6 import java.io.Serializable;
7 import java.net.URL;
8 import java.security.PrivilegedActionException;
9 import java.security.PrivilegedExceptionAction;
10 import java.util.Properties;
11 import java.util.StringTokenizer;
12
13 import javax.jcr.Repository;
14 import javax.jcr.RepositoryException;
15 import javax.jcr.Session;
16 import javax.security.auth.Subject;
17 import javax.security.auth.callback.Callback;
18 import javax.security.auth.callback.CallbackHandler;
19 import javax.security.auth.callback.NameCallback;
20 import javax.security.auth.callback.PasswordCallback;
21 import javax.security.auth.login.CredentialNotFoundException;
22 import javax.security.auth.login.LoginContext;
23 import javax.security.auth.login.LoginException;
24 import javax.servlet.ServletException;
25 import javax.servlet.http.HttpServletRequest;
26 import javax.servlet.http.HttpServletResponse;
27
28 import org.apache.commons.codec.binary.Base64;
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 import org.apache.jackrabbit.server.SessionProvider;
32 import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet;
33 import org.apache.jackrabbit.webdav.simple.SimpleWebdavServlet;
34 import org.argeo.cms.CmsException;
35 import org.argeo.cms.auth.AuthConstants;
36 import org.argeo.cms.auth.CurrentUser;
37 import org.argeo.cms.auth.HttpRequestCallback;
38 import org.argeo.cms.auth.HttpRequestCallbackHandler;
39 import org.argeo.jcr.ArgeoJcrConstants;
40 import org.argeo.jcr.JcrUtils;
41 import org.osgi.service.http.HttpContext;
42 import org.osgi.service.http.HttpService;
43 import org.osgi.service.http.NamespaceException;
44 import org.osgi.service.useradmin.Authorization;
45
46 /**
47 * Intercepts and enriches http access, mainly focusing on security and
48 * transactionality.
49 */
50 class DataHttp implements KernelConstants, ArgeoJcrConstants {
51 private final static Log log = LogFactory.getLog(DataHttp.class);
52
53 // private final static String ATTR_AUTH = "auth";
54 private final static String HEADER_AUTHORIZATION = "Authorization";
55 // private final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
56
57 private final HttpService httpService;
58
59 // FIXME Make it more unique
60 // private String httpAuthRealm = "Argeo";
61
62 // WebDav / JCR remoting
63 private OpenInViewSessionProvider sessionProvider;
64
65 DataHttp(HttpService httpService) {
66 this.httpService = httpService;
67 sessionProvider = new OpenInViewSessionProvider();
68 // registerRepositoryServlets(ALIAS_NODE, node);
69 }
70
71 public void destroy() {
72 // unregisterRepositoryServlets(ALIAS_NODE);
73 }
74
75 void registerRepositoryServlets(String alias, Repository repository) {
76 try {
77 registerWebdavServlet(alias, repository, true);
78 registerWebdavServlet(alias, repository, false);
79 registerRemotingServlet(alias, repository, true);
80 registerRemotingServlet(alias, repository, false);
81 } catch (Exception e) {
82 throw new CmsException(
83 "Could not register servlets for repository " + alias, e);
84 }
85 }
86
87 void unregisterRepositoryServlets(String alias) {
88 // FIXME unregister servlets
89 }
90
91 void registerWebdavServlet(String alias, Repository repository,
92 boolean anonymous) throws NamespaceException, ServletException {
93 WebdavServlet webdavServlet = new WebdavServlet(repository,
94 sessionProvider);
95 String pathPrefix = anonymous ? WEBDAV_PUBLIC : WEBDAV_PRIVATE;
96 String path = pathPrefix + "/" + alias;
97 Properties ip = new Properties();
98 ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, WEBDAV_CONFIG);
99 ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
100 httpService.registerServlet(path, webdavServlet, ip,
101 new DataHttpContext(anonymous));
102 }
103
104 void registerRemotingServlet(String alias, Repository repository,
105 boolean anonymous) throws NamespaceException, ServletException {
106 String pathPrefix = anonymous ? REMOTING_PUBLIC : REMOTING_PRIVATE;
107 RemotingServlet remotingServlet = new RemotingServlet(repository,
108 sessionProvider);
109 String path = pathPrefix + "/" + alias;
110 Properties ip = new Properties();
111 ip.setProperty(JcrRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
112
113 // Looks like a bug in Jackrabbit remoting init
114 ip.setProperty(RemotingServlet.INIT_PARAM_HOME,
115 KernelUtils.getOsgiInstanceDir() + "/tmp/jackrabbit");
116 ip.setProperty(RemotingServlet.INIT_PARAM_TMP_DIRECTORY, "remoting");
117 // in order to avoid annoying warning.
118 ip.setProperty(RemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG, "");
119 httpService.registerServlet(path, remotingServlet, ip,
120 new DataHttpContext(anonymous));
121 }
122
123 private Subject subjectFromRequest(HttpServletRequest request) {
124 Authorization authorization = (Authorization) request
125 .getAttribute(HttpContext.AUTHORIZATION);
126 if (authorization == null)
127 throw new CmsException("Not authenticated");
128 try {
129 LoginContext lc = new LoginContext(
130 AuthConstants.LOGIN_CONTEXT_USER,
131 new HttpRequestCallbackHandler(request));
132 lc.login();
133 return lc.getSubject();
134 } catch (LoginException e) {
135 throw new CmsException("Cannot login", e);
136 }
137 }
138
139 private class DataHttpContext implements HttpContext {
140 private final boolean anonymous;
141
142 DataHttpContext(boolean anonymous) {
143 this.anonymous = anonymous;
144 }
145
146 @Override
147 public boolean handleSecurity(final HttpServletRequest request,
148 HttpServletResponse response) throws IOException {
149
150 if (anonymous) {
151 Subject subject = KernelUtils.anonymousLogin();
152 Authorization authorization = subject
153 .getPrivateCredentials(Authorization.class).iterator()
154 .next();
155 request.setAttribute(REMOTE_USER, AuthConstants.ROLE_ANONYMOUS);
156 request.setAttribute(AUTHORIZATION, authorization);
157 return true;
158 }
159
160 if (log.isTraceEnabled())
161 KernelUtils.logRequestHeaders(log, request);
162 try {
163 new LoginContext(LOGIN_CONTEXT_USER,
164 new HttpRequestCallbackHandler(request)).login();
165 return true;
166 } catch (CredentialNotFoundException e) {
167 CallbackHandler token = basicAuth(request);
168 if (token != null) {
169 try {
170 LoginContext lc = new LoginContext(LOGIN_CONTEXT_USER,
171 token);
172 lc.login();
173 // Note: this is impossible to reliably clear the
174 // authorization header when access from a browser.
175 return true;
176 } catch (LoginException e1) {
177 throw new CmsException("Could not login", e1);
178 }
179 } else {
180 // requestBasicAuth(request, response);
181 return false;
182 }
183 } catch (LoginException e) {
184 throw new CmsException("Could not login", e);
185 }
186 }
187
188 @Override
189 public URL getResource(String name) {
190 return Activator.getBundleContext().getBundle().getResource(name);
191 }
192
193 @Override
194 public String getMimeType(String name) {
195 return null;
196 }
197
198 // private void requestBasicAuth(HttpServletRequest request,
199 // HttpServletResponse response) {
200 // response.setStatus(401);
201 // response.setHeader(HEADER_WWW_AUTHENTICATE, "basic realm=\""
202 // + httpAuthRealm + "\"");
203 // // request.getSession().setAttribute(ATTR_AUTH, Boolean.TRUE);
204 // }
205
206 private CallbackHandler basicAuth(final HttpServletRequest httpRequest) {
207 String authHeader = httpRequest.getHeader(HEADER_AUTHORIZATION);
208 if (authHeader != null) {
209 StringTokenizer st = new StringTokenizer(authHeader);
210 if (st.hasMoreTokens()) {
211 String basic = st.nextToken();
212 if (basic.equalsIgnoreCase("Basic")) {
213 try {
214 // TODO manipulate char[]
215 String credentials = new String(
216 Base64.decodeBase64(st.nextToken()),
217 "UTF-8");
218 // log.debug("Credentials: " + credentials);
219 int p = credentials.indexOf(":");
220 if (p != -1) {
221 final String login = credentials
222 .substring(0, p).trim();
223 final char[] password = credentials
224 .substring(p + 1).trim().toCharArray();
225 return new CallbackHandler() {
226 public void handle(Callback[] callbacks) {
227 for (Callback cb : callbacks) {
228 if (cb instanceof NameCallback)
229 ((NameCallback) cb)
230 .setName(login);
231 else if (cb instanceof PasswordCallback)
232 ((PasswordCallback) cb)
233 .setPassword(password);
234 else if (cb instanceof HttpRequestCallback)
235 ((HttpRequestCallback) cb)
236 .setRequest(httpRequest);
237 }
238 }
239 };
240 } else {
241 throw new CmsException(
242 "Invalid authentication token");
243 }
244 } catch (Exception e) {
245 throw new CmsException(
246 "Couldn't retrieve authentication", e);
247 }
248 }
249 }
250 }
251 return null;
252 }
253
254 }
255
256 /**
257 * Implements an open session in view patter: a new JCR session is created
258 * for each request
259 */
260 private class OpenInViewSessionProvider implements SessionProvider,
261 Serializable {
262 private static final long serialVersionUID = 2270957712453841368L;
263
264 public Session getSession(HttpServletRequest request, Repository rep,
265 String workspace) throws javax.jcr.LoginException,
266 ServletException, RepositoryException {
267 return login(request, rep, workspace);
268 }
269
270 protected Session login(HttpServletRequest request,
271 Repository repository, String workspace)
272 throws RepositoryException {
273 if (log.isTraceEnabled())
274 log.trace("Login to workspace "
275 + (workspace == null ? "<default>" : workspace)
276 + " in web session " + request.getSession().getId());
277 return repository.login(workspace);
278 }
279
280 public void releaseSession(Session session) {
281 JcrUtils.logoutQuietly(session);
282 if (log.isTraceEnabled())
283 log.trace("Logged out remote JCR session " + session);
284 }
285 }
286
287 private class WebdavServlet extends SimpleWebdavServlet {
288 private static final long serialVersionUID = -4687354117811443881L;
289 private final Repository repository;
290
291 public WebdavServlet(Repository repository,
292 SessionProvider sessionProvider) {
293 this.repository = repository;
294 setSessionProvider(sessionProvider);
295 }
296
297 public Repository getRepository() {
298 return repository;
299 }
300
301 @Override
302 protected void service(final HttpServletRequest request,
303 final HttpServletResponse response) throws ServletException,
304 IOException {
305 try {
306 Subject subject = subjectFromRequest(request);
307 if (CurrentUser.isAnonymous(subject)
308 && request.getMethod().equals("GET")) {
309 response.setHeader("Cache-Control",
310 "no-transform, public, max-age=300, s-maxage=900");
311 }
312
313 Subject.doAs(subject, new PrivilegedExceptionAction<Void>() {
314 @Override
315 public Void run() throws Exception {
316 WebdavServlet.super.service(request, response);
317 return null;
318 }
319 });
320 } catch (PrivilegedActionException e) {
321 throw new CmsException("Cannot process webdav request",
322 e.getException());
323 }
324 }
325 }
326
327 private class RemotingServlet extends JcrRemotingServlet {
328 private static final long serialVersionUID = 4605238259548058883L;
329 private final Repository repository;
330 private final SessionProvider sessionProvider;
331
332 public RemotingServlet(Repository repository,
333 SessionProvider sessionProvider) {
334 this.repository = repository;
335 this.sessionProvider = sessionProvider;
336 }
337
338 @Override
339 protected Repository getRepository() {
340 return repository;
341 }
342
343 @Override
344 protected SessionProvider getSessionProvider() {
345 return sessionProvider;
346 }
347
348 @Override
349 protected void service(final HttpServletRequest request,
350 final HttpServletResponse response) throws ServletException,
351 IOException {
352 try {
353 Subject subject = subjectFromRequest(request);
354 Subject.doAs(subject, new PrivilegedExceptionAction<Void>() {
355 @Override
356 public Void run() throws Exception {
357 RemotingServlet.super.service(request, response);
358 return null;
359 }
360 });
361 } catch (PrivilegedActionException e) {
362 throw new CmsException("Cannot process JCR remoting request",
363 e.getException());
364 }
365 }
366 }
367 }