org.jivesoftware.openfire.webdav.WebDAVLiteServlet Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2004-2009 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.openfire.webdav;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthFactory;
import org.jivesoftware.util.Base64;
import org.jivesoftware.util.JiveGlobals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
/*
* This is a work in progress and is checked in for future use.
*/
/**
* Implements a very light WebDAV-ish servlet for specific purposes. It does not support WebDAV extensions
* to the HTTP protocol. Instead, it supports the set of commands: GET, PUT, and DELETE. This serves
* as a WebDAV like storage interface for MUC shared files. It handles part of XEP-0129: WebDAV File Transfers,
* but not all of it. We don't handle the PROPPATCH command, for example, as the user has no rights to set
* the permissions on MUC shared files.
*
* TODO: How to handle a remote account? As posed in the WebDAV XEP, we should send a message and wait for response.
* TODO: How to handle SparkWeb? Reasking for a username and password would suck. Need some form of SSO.
* Maybe the SSO could be some special token provided during sign-on that the client could store and use
* for auth.
*
* @author Daniel Henninger
*/
public class WebDAVLiteServlet extends HttpServlet {
private static final Logger Log = LoggerFactory.getLogger(WebDAVLiteServlet.class);
// Storage directory under the Openfire install root
private static String WEBDAV_SUBDIR = "mucFiles";
/**
* Retrieves a File object referring to a file in a service and room. Leaving file as null
* will get you the directory that would contain all of the files for a particular service and room.
*
* @param service Subdomain of the conference service we are forming a file path for.
* @param room Conference room we are forming a file path for.
* @param file Optional (can be null) filename for a path to a specific file (otherwise, directory).
* @return The File reference constructed for the service, room, and file combination.
*/
private File getFileReference(String service, String room, String file) {
return new File(JiveGlobals.getHomeDirectory(), WEBDAV_SUBDIR+File.separator+service+File.separator+room+(file != null ? File.separator+file : ""));
}
/**
* Verifies that the user is authenticated via some mechanism such as Basic Auth. If the
* authentication fails, this method will alter the HTTP response to include a request for
* auth and send the unauthorized response back to the client.
*
* TODO: Handle some form of special token auth, perhaps provided a room connection?
* TODO: If it's not a local account, we should try message auth access? XEP-0070?
* TODO: Should we support digest auth as well?
*
* @param request Object representing the HTTP request.
* @param response Object representing the HTTP response.
* @return True or false if the user is authenticated.
* @throws ServletException If there was a servlet related exception.
* @throws IOException If there was an IO error while setting the error.
*/
private Boolean isAuthenticated(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String auth = request.getHeader("Authorization");
JID jid;
try {
if (auth == null || !request.getAuthType().equals(HttpServletRequest.BASIC_AUTH)) {
throw new Exception("No authorization or improper authorization provided.");
}
auth = auth.substring(auth.indexOf(" "));
String decoded = new String(Base64.decode(auth));
int i = decoded.indexOf(":");
String username = decoded.substring(0,i);
if (!username.contains("@")) {
throw new Exception("Not a valid JID.");
}
jid = new JID(username);
if (XMPPServer.getInstance().isLocal(jid)) {
String password = decoded.substring(i+1, decoded.length());
if (AuthFactory.authenticate(username, password) == null) {
throw new Exception("Authentication failed.");
}
}
else {
// TODO: Authenticate a remote user, probably via message auth.
throw new Exception("Not a local account.");
}
return true;
}
catch (Exception e) {
/*
* This covers all possible authentication issues. Eg:
* - not enough of auth info passed in
* - failed auth
*/
response.setHeader("WWW-Authenticate", "Basic realm=\"Openfire WebDAV\"");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
}
/**
* Verifies that the authenticated user is a member of a conference service and room, or else
* they are not entitled to view any of the files in the room.
*
* @param request Object representing the HTTP request.
* @param response Object representing the HTTP response.
* @param service Subdomain of the conference service they are trying to access files for.
* @param room Room in the conference service they are trying to access files for.
* @return True or false if the user is authenticated.
* @throws ServletException If there was a servlet related exception.
* @throws IOException If there was an IO error while setting the error.
*/
private Boolean isAuthorized(HttpServletRequest request, HttpServletResponse response,
String service, String room) throws ServletException, IOException {
String auth = request.getHeader("Authorization");
try {
if (auth == null || !request.getAuthType().equals(HttpServletRequest.BASIC_AUTH)) {
throw new Exception("No authorization or improper authorization provided.");
}
auth = auth.substring(auth.indexOf(" "));
String decoded = new String(Base64.decode(auth));
int i = decoded.indexOf(":");
String username = decoded.substring(0,i);
if (!username.contains("@")) {
throw new Exception("Not a valid JID.");
}
final JID bareJID = new JID(username).asBareJID();
XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(service).getChatRoom(room).getOccupantsByBareJID(bareJID);
return true;
}
catch (Exception e) {
/*
* This covers all possible authorization issues. Eg:
* - accessing a room that doesn't exist
* - accessing a room that user isn't a member of
*/
response.setHeader("WWW-Authenticate", "Basic realm=\"Openfire WebDAV\"");
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return false;
}
}
/**
* Initialize the WebDAV servlet, auto-creating it's file root if it doesn't exist.
*
* @param servletConfig Configuration settings of the servlet from web.xml.
* @throws ServletException If there was an exception setting up the servlet.
*/
@Override
public void init(ServletConfig servletConfig) throws ServletException {
super.init(servletConfig);
File webdavDir = new File(JiveGlobals.getHomeDirectory(), WEBDAV_SUBDIR);
if (!webdavDir.exists()) {
webdavDir.mkdirs();
}
}
/**
* Handles a GET request for files or for a file listing.
*
* @param request Object representing the HTTP request.
* @param response Object representing the HTTP response.
* @throws ServletException If there was a servlet related exception.
* @throws IOException If there was an IO error while setting the error.
*/
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws
ServletException, IOException {
// Verify authentication
if (!isAuthenticated(request, response)) return;
String path = request.getPathInfo();
Log.debug("WebDAVLiteServlet: GET with path = "+path);
if (path == null || !path.startsWith("/rooms/")) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
String[] pathPcs = path.split("/");
if (pathPcs.length < 4 || pathPcs.length > 5) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
String service = pathPcs[2];
String room = pathPcs[3];
// Verify authorization
if (!isAuthorized(request, response, service, room)) return;
if (pathPcs.length == 5) {
// File retrieval
String filename = pathPcs[4];
File file = getFileReference(service, room, filename);
Log.debug("WebDAVListServlet: File path = "+file.getAbsolutePath());
Log.debug("WebDAVListServlet: Service = "+service+", room = "+room+", file = "+filename);
if (file.exists()) {
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("application/octet-stream");
response.setContentLength((int)file.length());
FileInputStream fileStream = new FileInputStream(file);
byte[] byteArray = new byte[(int)file.length()];
new DataInputStream(fileStream).readFully(byteArray);
fileStream.close();
response.getOutputStream().write(byteArray);
}
else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
else {
// File listing
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
String content = "Files available for "+room+"@"+service+":\n";
File fileDir = getFileReference(service, room, null);
Log.debug("WebDAVListServlet: File path = "+fileDir.getAbsolutePath());
if (fileDir.exists()) {
File[] files = fileDir.listFiles();
for (File file : files) {
content += file.getName()+"\n";
}
}
response.getOutputStream().write(content.getBytes());
Log.debug("WebDAVListServlet: Service = "+service+", room = "+room);
}
}
/**
* Handles a PUT request for uploading files.
*
* @param request Object representing the HTTP request.
* @param response Object representing the HTTP response.
* @throws ServletException If there was a servlet related exception.
* @throws IOException If there was an IO error while setting the error.
*/
@Override
protected void doPut(HttpServletRequest request,
HttpServletResponse response) throws
ServletException, IOException {
// Verify authentication
if (!isAuthenticated(request, response)) return;
String path = request.getPathInfo();
Log.debug("WebDAVLiteServlet: PUT with path = "+path);
if (request.getContentLength() <= 0) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
if (path == null || !path.startsWith("/rooms/")) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
String[] pathPcs = path.split("/");
if (pathPcs.length != 5) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
String service = pathPcs[2];
String room = pathPcs[3];
String filename = pathPcs[4];
// Verify authorization
if (!isAuthorized(request, response, service, room)) return;
Log.debug("WebDAVListServlet: Service = "+service+", room = "+room+", file = "+filename);
File file = getFileReference(service, room, filename);
Boolean overwriteFile = file.exists();
FileOutputStream fileStream = new FileOutputStream(file, false);
ServletInputStream inputStream = request.getInputStream();
byte[] byteArray = new byte[request.getContentLength()];
int bytesRead = 0;
while (bytesRead != -1) {
bytesRead = inputStream.read(byteArray, bytesRead, request.getContentLength());
}
fileStream.write(byteArray);
fileStream.close();
inputStream.close();
if (overwriteFile) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
response.setHeader("Location", request.getRequestURI());
}
else {
response.setStatus(HttpServletResponse.SC_CREATED);
response.setHeader("Location", request.getRequestURI());
}
}
/**
* Handles a DELETE request for deleting files.
*
* @param request Object representing the HTTP request.
* @param response Object representing the HTTP response.
* @throws ServletException If there was a servlet related exception.
* @throws IOException If there was an IO error while setting the error.
*/
@Override
protected void doDelete(HttpServletRequest request,
HttpServletResponse response) throws
ServletException, IOException {
// Verify authentication
if (!isAuthenticated(request, response)) return;
String path = request.getPathInfo();
Log.debug("WebDAVLiteServlet: DELETE with path = "+path);
if (path == null || !path.startsWith("/rooms/")) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
String[] pathPcs = path.split("/");
if (pathPcs.length != 5) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
String service = pathPcs[2];
String room = pathPcs[3];
String filename = pathPcs[4];
// Verify authorization
if (!isAuthorized(request, response, service, room)) return;
Log.debug("WebDAVListServlet: Service = "+service+", room = "+room+", file = "+filename);
File file = getFileReference(service, room, filename);
if (file.exists()) {
file.delete();
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
else {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}
}