KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sslexplorer > vfs > webdav > DAVTransaction


1 /* ========================================================================== *
2  * Copyright (C) 2004-2005 Pier Fumagalli <http://www.betaversion.org/~pier/> *
3  * All rights reserved. *
4  * ========================================================================== *
5  * *
6  * Licensed under the Apache License, Version 2.0 (the "License"). You may *
7  * not use this file except in compliance with the License. You may obtain a *
8  * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
9  * *
10  * Unless required by applicable law or agreed to in writing, software *
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
13  * License for the specific language governing permissions and limitations *
14  * under the License. *
15  * *
16  * ========================================================================== */

17 package com.sslexplorer.vfs.webdav;
18
19 import java.io.IOException JavaDoc;
20 import java.io.InputStream JavaDoc;
21 import java.io.OutputStream JavaDoc;
22 import java.io.OutputStreamWriter JavaDoc;
23 import java.io.PrintWriter JavaDoc;
24 import java.net.URI JavaDoc;
25 import java.net.URISyntaxException JavaDoc;
26 import java.util.Date JavaDoc;
27 import java.util.HashMap JavaDoc;
28 import java.util.Map JavaDoc;
29
30 import javax.servlet.ServletException JavaDoc;
31 import javax.servlet.ServletRequest JavaDoc;
32 import javax.servlet.ServletResponse JavaDoc;
33 import javax.servlet.http.HttpServletRequest JavaDoc;
34 import javax.servlet.http.HttpServletResponse JavaDoc;
35
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38
39 import com.maverick.crypto.encoders.Base64;
40 import com.sslexplorer.boot.Util;
41 import com.sslexplorer.core.ServletRequestAdapter;
42 import com.sslexplorer.core.ServletResponseAdapter;
43 import com.sslexplorer.core.UserDatabaseManager;
44 import com.sslexplorer.policyframework.LaunchSession;
45 import com.sslexplorer.policyframework.LaunchSessionFactory;
46 import com.sslexplorer.security.AccountLockedException;
47 import com.sslexplorer.security.AuthenticationModuleManager;
48 import com.sslexplorer.security.AuthenticationScheme;
49 import com.sslexplorer.security.Constants;
50 import com.sslexplorer.security.DefaultAuthenticationScheme;
51 import com.sslexplorer.security.InvalidLoginCredentialsException;
52 import com.sslexplorer.security.LogonController;
53 import com.sslexplorer.security.LogonControllerFactory;
54 import com.sslexplorer.security.PasswordCredentials;
55 import com.sslexplorer.security.SessionInfo;
56 import com.sslexplorer.security.SystemDatabaseFactory;
57 import com.sslexplorer.security.User;
58 import com.sslexplorer.security.WebDAVAuthenticationModule;
59 import com.sslexplorer.security.actions.LogonAction;
60 import com.sslexplorer.vfs.VFSResource;
61
62 /**
63  * <p>
64  * A simple wrapper isolating the Java Servlet API from this <a
65  * HREF="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a> implementation.
66  * </p>
67  *
68  * @author <a HREF="http://www.betaversion.org/~pier/">Pier Fumagalli</a>
69  */

70 public class DAVTransaction {
71
72     private static Log log = LogFactory.getLog(DAVTransaction.class);
73
74     public final static String JavaDoc ATTR_EXPECTING_REALM_AUTHENTICATION = "expectingRealmAuth";
75     public final static String JavaDoc ATTR_DEREGISTER_SUB_AUTHS = "deregisterSubAuths";
76     public final static String JavaDoc ATTR_AUTH_ATTEMPTS = "authAttempts";
77
78     /**
79      * <p>
80      * The identifyication of the <code>infinity</code> value in the
81      * <code>Depth</code> header.
82      * </p>
83      */

84     public static final int INFINITY = Integer.MAX_VALUE;
85
86     /**
87      * <p>
88      * The nested {@link HttpServletRequest}.
89      * </p>
90      */

91     private HttpServletRequest JavaDoc req = null;
92     /**
93      * <p>
94      * The nested {@link HttpServletResponse}.
95      * </p>
96      */

97     private HttpServletResponse JavaDoc res = null;
98     /**
99      * <p>
100      * The {@link URI} associated with the base of the repository.
101      * </p>
102      */

103     private URI JavaDoc base = null;
104     /**
105      * <p>
106      * The path for this transaction. contains user etc.
107      * </p>
108      */

109     private String JavaDoc path;
110     /**
111      * <p>
112      * A cache of resources for the life of this transaction
113      */

114     private Map JavaDoc resourceCache;
115     
116     /**
117      * The session
118      */

119     private SessionInfo sessionInfo;
120
121     /**
122      * <p>
123      * The current credentials object being used for authentication to the
124      * resources
125      */

126     // private DAVCredentials currentCredentials;
127
/* ====================================================================== */
128     /* Constructors */
129     /* ====================================================================== */
130
131     /**
132      * <p>
133      * Create a new {@link DAVTransaction} instance.
134      * </p>
135      */

136     public DAVTransaction(ServletRequest JavaDoc request, ServletResponse JavaDoc response)
137     // throws ServletException, DAVAuthenticationRequiredException {
138
throws ServletException JavaDoc {
139         if (request == null)
140             throw new NullPointerException JavaDoc("Null request");
141         if (response == null)
142             throw new NullPointerException JavaDoc("Null response");
143         this.req = (HttpServletRequest JavaDoc) request;
144         this.res = (HttpServletResponse JavaDoc) response;
145         this.resourceCache = new HashMap JavaDoc();
146
147         /* First see if the launch ID has been provided as a parameter. If it
148          * has we can just get the resource session directly. This should happen
149          * for web folders that are first launched from an active user session or
150          * from a file download from the network place HTML file browser.
151          */

152
153         String JavaDoc launchId = request.getParameter(LaunchSession.LAUNCH_ID);
154
155         if (launchId != null) {
156             LaunchSession launchSession = LaunchSessionFactory.getInstance().getLaunchSession(launchId);
157             if (launchSession != null) {
158                 sessionInfo = launchSession.getSession();
159                 LogonControllerFactory.getInstance().addCookies(new ServletRequestAdapter((HttpServletRequest JavaDoc) request),
160                     new ServletResponseAdapter((HttpServletResponse JavaDoc) response),
161                     launchSession.getSession().getLogonTicket(),
162                     launchSession.getSession());
163                 sessionInfo.access();
164             } else if (log.isDebugEnabled())
165                 log.debug("Could not locate session using ticket");
166         }
167     }
168
169     void configureFromRequest() throws URISyntaxException JavaDoc {
170         String JavaDoc scheme = this.req.getScheme();
171         String JavaDoc host = this.req.getServerName();
172         String JavaDoc basePath = DAVUtilities.concatenatePaths(this.req.getServletPath(),
173             this.req.getPathInfo());
174         int port = this.req.getServerPort();
175         this.base = new URI JavaDoc(scheme, null, host, port, basePath, null, null);
176         this.base = this.base.normalize();
177         path = DAVUtilities.stripTrailingSlash(DAVUtilities.stripLeadingSlash(DAVUtilities.stripFirstPath(base.getPath())));
178
179     }
180
181     public void putCachedResource(VFSResource resource) {
182         String JavaDoc key = DAVUtilities.concatenatePaths(resource.getMount().getMountString(), resource.getRelativePath());
183         resourceCache.put(key, resource);
184     }
185
186     public VFSResource getCachedResource(String JavaDoc fullPath) {
187         return (VFSResource) resourceCache.get(fullPath);
188     }
189
190     public boolean verifyAuthorization() throws IOException JavaDoc, DAVAuthenticationRequiredException {
191         
192         String JavaDoc expectingRealm = (String JavaDoc) req.getSession().getAttribute(ATTR_EXPECTING_REALM_AUTHENTICATION);
193
194         try {
195             if (!SystemDatabaseFactory.getInstance().verifyIPAddress(req.getRemoteAddr())) {
196                 if (log.isDebugEnabled())
197                     log.debug(req.getRemoteHost() + " is not authorized");
198                 res.setStatus(HttpServletResponse.SC_FORBIDDEN);
199                 return false;
200             } else {
201
202                 /**
203                  * LDP - Do not use LogonController.hasClientLoggedOn because this will take over the SessionInfo
204                  * of the main browser if a web folder connects using the same logon ticket. This method ensures
205                  * that the SessionInfo object is still attached to the browsers HTTP session so that authentication
206                  * information is still available.
207                  */

208                 if (sessionInfo != null) {
209                     configureFromRequest();
210                     return true;
211                 }
212                 
213                 /**
214                  * No resource session, so try get all resource sessions active for this session
215                  * and look for one that matches the path requested
216                  */

217                 sessionInfo = LogonControllerFactory.getInstance().getSessionInfo(req);
218                 if (sessionInfo!=null) {
219                     configureFromRequest();
220                     return true;
221                 }
222             }
223
224             if (log.isDebugEnabled())
225                 log.debug("Requesting HTTP authentication for SSL-Explorer logon.");
226
227
228             req.getSession().setAttribute(Constants.ORIGINAL_REQUEST, Util.getOriginalRequest(req));
229
230             if (log.isDebugEnabled())
231                 log.debug("Expecting authentication " + expectingRealm);
232
233             // Now test for the authorization
234
boolean hasAuthorization = req.getHeader("Authorization") != null && Boolean.TRUE.equals(req.getSession()
235                             .getAttribute(Constants.AUTH_SENT));
236
237             if (!hasAuthorization) {
238                 if (log.isDebugEnabled())
239                     log.debug("Creating new HTTP authentication authentication session");
240
241                 AuthenticationScheme seq = AuthenticationModuleManager.getInstance()
242                                 .getSchemeForAuthenticationModuleInUse(WebDAVAuthenticationModule.MODULE_NAME);
243                 if (seq == null || !seq.getEnabled()) {
244                     log.error("User cannot authenticate via WebDAV using only HTTP BASIC authentication as the current policy does not allow this.");
245                     res.sendError(DAVStatus.SC_FORBIDDEN,
246                         "You cannot authenticate via WebDAV using only HTTP BASIC authentication as the current policy does not allow this.");
247                     return false;
248                 }
249                 seq.addModule(WebDAVAuthenticationModule.MODULE_NAME);
250                 seq.init(req.getSession());
251                 seq.nextAuthenticationModule();
252                 req.getSession().setAttribute(Constants.AUTH_SENT, Boolean.TRUE);
253                 req.getSession().setAttribute(Constants.AUTH_SESSION, seq);
254                 req.getSession().setAttribute(ATTR_EXPECTING_REALM_AUTHENTICATION, WebDAVAuthenticationModule.DEFAULT_REALM);
255                 WebDAVAuthenticationModule.sendAuthorizationError(req, res, WebDAVAuthenticationModule.DEFAULT_REALM);
256                 return false;
257             } else {
258                 String JavaDoc authorization = req.getHeader("Authorization");
259                 int idx = authorization.indexOf(' ');
260                 if (idx == -1 || idx == authorization.length() - 1) {
261                     WebDAVAuthenticationModule.sendAuthorizationError(req, res, expectingRealm);
262                     return false;
263                 }
264                 // Authenticate the user
265
String JavaDoc method = authorization.substring(0, idx);
266                 if (!method.equalsIgnoreCase("basic")) {
267                     WebDAVAuthenticationModule.sendAuthorizationError(req, res, expectingRealm);
268                     return false;
269                 }
270
271                 // Extract the credentials - should be ticket:tunnel
272
String JavaDoc encoded = authorization.substring(idx + 1);
273                 String JavaDoc credentials = new String JavaDoc(Base64.decode(encoded));
274
275                 idx = credentials.indexOf(':');
276                 if (idx == 0 || idx == -1) {
277                     WebDAVAuthenticationModule.sendAuthorizationError(req, res, expectingRealm);
278                     return false;
279                 }
280
281                 // Get the user credentials
282
String JavaDoc username = credentials.substring(0, idx);
283
284                 if (expectingRealm.equals(WebDAVAuthenticationModule.DEFAULT_REALM)) {
285                     AuthenticationScheme authScheme = (DefaultAuthenticationScheme) req.getSession()
286                                     .getAttribute(Constants.AUTH_SESSION);
287                     if (authScheme == null) {
288                         throw new Exception JavaDoc("No authentication scheme initialised.");
289                     }
290
291                     // Find user
292
User user = UserDatabaseManager.getInstance().getDefaultUserDatabase().getAccount(username);
293                     authScheme.setUser(user);
294                     LogonAction.authenticate(authScheme, req);
295                     LogonAction.finishAuthentication(authScheme, req, res);
296                 } else {
297
298                     if (log.isDebugEnabled())
299                         log.debug("Logging " + username
300                             + " ["
301                             + req.getRemoteHost()
302                             + "] onto realm "
303                             + expectingRealm
304                             + " using Basic authentication for session "
305                             + req.getSession().getId());
306                     // subAuths.put(expectingRealm, new AuthPair(username,
307
// password.toCharArray()));
308
req.getSession().removeAttribute(ATTR_EXPECTING_REALM_AUTHENTICATION);
309                 }
310                 req.getSession().removeAttribute(Constants.AUTH_SENT);
311
312                 /* Logging method */
313                 if (log.isDebugEnabled())
314                     log.debug(req.getMethod() + ' ' + req.getRequestURI() + ' ' + req.getProtocol());
315
316                 // We now can get the sessionInfo object for this session
317
sessionInfo = LogonControllerFactory.getInstance().getSessionInfo(req);
318                                 
319                 configureFromRequest();
320                 return true;
321
322             }
323         } catch (InvalidLoginCredentialsException ex) {
324             log.error(ex);
325             WebDAVAuthenticationModule.sendAuthorizationError(req, res, expectingRealm);
326             return false;
327         } catch (AccountLockedException ex) {
328             log.error(ex);
329             res.sendError(DAVStatus.SC_FORBIDDEN, ex.getMessage());
330             return false;
331         } catch (Exception JavaDoc ex) {
332             log.error("Unexpected error", ex);
333             res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
334             return false;
335         }
336     }
337
338     public HttpServletResponse JavaDoc getResponse() {
339         return (HttpServletResponse JavaDoc) res;
340     }
341
342     /* ====================================================================== */
343     /* Request methods */
344     /* ====================================================================== */
345
346     /**
347      * <p>
348      * Get the request object.
349      * </p>
350      */

351     public HttpServletRequest JavaDoc getRequest() {
352         return req;
353     }
354
355     /**
356      * <p>
357      * Return the path originally requested by the client.
358      * </p>
359      */

360     public String JavaDoc getMethod() {
361         return this.req.getMethod();
362     }
363
364     /**
365      * <p>
366      * Return the path for this transaction. This will be the path as the client
367      * sees it less the first element
368      *
369      * @return path
370      */

371     public String JavaDoc getPath() {
372         return path;
373         // String path = this.req.getPathInfo();
374
// if (path == null) return "";
375
// if ((path.length() > 0) && (path.charAt(0) == '/')) {
376
// return path.substring(1);
377
// } else {
378
// return path;
379
// }
380
}
381
382     public boolean isRequiredRootRedirect() {
383         return false;
384     }
385
386     /**
387      * <p>
388      * Return the path originally requested by the client encoded.
389      * </p>
390      */

391     public String JavaDoc getPathEncoded() {
392         return DAVUtilities.encodePath(getPath());
393     }
394
395     /**
396      * <p>
397      * Return the depth requested by the client for this transaction.
398      * </p>
399      */

400     public int getDepth() {
401         String JavaDoc depth = req.getHeader("Depth");
402         if (depth == null)
403             return INFINITY;
404         if ("infinity".equals(depth))
405             return INFINITY;
406         try {
407             return Integer.parseInt(depth);
408         } catch (NumberFormatException JavaDoc exception) {
409             throw new DAVException(412, "Unable to parse depth", exception);
410         }
411     }
412
413     /**
414      * <p>
415      * Return a {@link URI}
416      */

417     public URI JavaDoc getDestination() {
418         String JavaDoc destination = this.req.getHeader("Destination");
419         if (destination != null)
420             try {
421                 return this.base.relativize(new URI JavaDoc(destination.replaceAll(" ", "%20")));
422             } catch (URISyntaxException JavaDoc exception) {
423                 throw new DAVException(412, "Can't parse destination", exception);
424             }
425         return null;
426     }
427
428     /**
429      * <p>
430      * Return the overwrite flag requested by the client for this transaction.
431      * </p>
432      */

433     public boolean getOverwrite() {
434         String JavaDoc overwrite = req.getHeader("Overwrite");
435         if (overwrite == null)
436             return true;
437         if ("T".equals(overwrite))
438             return true;
439         if ("F".equals(overwrite))
440             return false;
441         throw new DAVException(412, "Unable to parse overwrite flag");
442     }
443
444     /**
445      * <p>
446      * Check if the client requested a date-based conditional operation.
447      * </p>
448      */

449     public Date JavaDoc getIfModifiedSince() {
450         String JavaDoc name = "If-Modified-Since";
451         if (this.req.getHeader(name) == null)
452             return null;
453         return new Date JavaDoc(this.req.getDateHeader(name));
454     }
455
456     /* ====================================================================== */
457     /* Response methods */
458     /* ====================================================================== */
459
460     /**
461      * <p>
462      * Set the HTTP status code of the response.
463      * </p>
464      */

465     public void setStatus(int status) {
466         this.res.setStatus(status);
467     }
468
469     /**
470      * <p>
471      * Set the HTTP <code>Content-Type</code> header.
472      * </p>
473      */

474     public void setContentType(String JavaDoc type) {
475         this.res.setContentType(type);
476     }
477
478     /**
479      * <p>
480      * Set an HTTP header in the response.
481      * </p>
482      */

483     public void setHeader(String JavaDoc name, String JavaDoc value) {
484         this.res.setHeader(name, value);
485     }
486
487     /**
488      * <p>
489      * Set an HTTP header in the response.
490      * </p>
491      *
492      * @param name name
493      * @param value value
494      */

495     public void setDateHeader(String JavaDoc name, int value) {
496         this.res.setDateHeader(name, value);
497     }
498
499     /* ====================================================================== */
500     /* I/O methods */
501     /* ====================================================================== */
502
503     /**
504      * <p>
505      * Read from the body of the original request.
506      * </p>
507      */

508     public InputStream JavaDoc getInputStream() throws IOException JavaDoc {
509         /* We don't support ranges */
510         if (req.getHeader("Content-Range") != null)
511             throw new DAVException(501, "Content-Range not supported");
512
513         if (this.req.getContentLength() >= 0)
514             this.req.getInputStream();
515         String JavaDoc len = this.req.getHeader("Content-Length");
516         if (len != null)
517             try {
518                 if (Long.parseLong(len) > 0)
519                     return this.req.getInputStream();
520             } catch (NumberFormatException JavaDoc exception) {
521                 // Unparseable content length header...
522
}
523
524         // Do not throw an exception, this could be null without an error
525
// condition
526
return null;
527     }
528
529     /**
530      * <p>
531      * Write the body of the response.
532      * </p>
533      */

534     public OutputStream JavaDoc getOutputStream() throws IOException JavaDoc {
535         if (System.getProperty("sslexplorer.webdav.debug", "false").equals("true"))
536             return new TempOutputStream(this.res.getOutputStream());
537         else
538             return this.res.getOutputStream();
539     }
540
541     class TempOutputStream extends OutputStream JavaDoc {
542
543         OutputStream JavaDoc out;
544         StringBuffer JavaDoc wbBuf;
545
546         TempOutputStream(OutputStream JavaDoc out) {
547             this.out = out;
548             wbBuf = new StringBuffer JavaDoc();
549         }
550
551         public void write(byte[] buf, int off, int len) throws IOException JavaDoc {
552             wbBuf.append(new String JavaDoc(buf, off, len));
553             out.write(buf, off, len);
554         }
555
556         public void write(int b) throws IOException JavaDoc {
557             wbBuf.append((byte) b);
558             out.write((byte) b);
559         }
560
561         public void flush() throws IOException JavaDoc {
562             log.info(wbBuf.toString());
563             wbBuf.setLength(0);
564             out.flush();
565         }
566
567     }
568
569     /**
570      * <p>
571      * Write the body of the response.
572      * </p>
573      */

574     public PrintWriter JavaDoc write(String JavaDoc encoding) throws IOException JavaDoc {
575         return new PrintWriter JavaDoc(new OutputStreamWriter JavaDoc(this.getOutputStream(), encoding));
576     }
577
578     /* ====================================================================== */
579     /* Lookup methods */
580     /* ====================================================================== */
581
582     /**
583      * <p>
584      * Look up the final URI of a {@link VFSResource} as visible from the HTTP
585      * client requesting this transaction.
586      * </p>
587      */

588     public URI JavaDoc lookup(VFSResource resource) {
589         URI JavaDoc uri = resource.getRelativeURI();
590         URI JavaDoc resolved = null;
591         if (uri == null || uri.toString().equals("")) {
592             resolved = this.base;
593         } else {
594             resolved = this.base.resolve(uri).normalize();
595             ;
596         }
597         return resolved;
598     }
599
600     public PasswordCredentials getCredentials() {
601         String JavaDoc authorization = req.getHeader("Authorization");
602
603         if (authorization == null) {
604             return null;
605         }
606
607         int idx = authorization.indexOf(' ');
608
609         if (idx == -1 || idx == authorization.length() - 1) {
610             return null;
611         }
612
613         // Authenticate the user
614
String JavaDoc method = authorization.substring(0, idx);
615
616         if (!method.equalsIgnoreCase("basic")) {
617             return null;
618         }
619
620         // Extract the credentials - should be ticket:tunnel
621
String JavaDoc encoded = authorization.substring(idx + 1);
622
623         String JavaDoc credentials = new String JavaDoc(Base64.decode(encoded));
624         idx = credentials.indexOf(':');
625
626         if (idx == 0 || idx == -1) {
627             return null;
628         }
629
630         // Get the user credentials
631
String JavaDoc username = credentials.substring(0, idx);
632         String JavaDoc password = credentials.substring(idx + 1);
633
634         return new PasswordCredentials(username, password.toCharArray());
635     }
636
637     /**
638      * Get the session info for this transaction. The user and other session
639      * related objects may be found here.
640      *
641      * @return session info
642      */

643     public SessionInfo getSessionInfo() {
644         return sessionInfo;
645     }
646
647     /**
648      * Check if the supplied resource path is valid for this transaction path.
649      * This will be used to force a redirect to the required path if not.
650      *
651      * @param fullResourcePath
652      * @return is resource path
653      */

654     public boolean isResourcePath(String JavaDoc fullResourcePath) {
655         String JavaDoc fullUri = DAVUtilities.stripTrailingSlash(DAVUtilities.stripLeadingSlash(fullResourcePath));
656         return fullUri.equals(getPath());
657     }
658 }
Popular Tags