import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
+import org.apache.commons.fileupload.util.Streams;
+import org.apache.commons.httpclient.util.DateParseException;
+import org.apache.commons.httpclient.util.DateUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONArray;
private static final int TRACK_PROGRESS_PERCENT = 5;
/**
+ * The form parameter name that contains the signature in a browser POST upload.
+ */
+ private static final String AUTHORIZATION_PARAMETER = "Authorization";
+
+ /**
+ * The form parameter name that contains the date in a browser POST upload.
+ */
+ private static final String DATE_PARAMETER = "Date";
+
+ /**
* The logger.
*/
private static Log logger = LogFactory.getLog(FilesHandler.class);
* @exception IOException if an input/output error occurs
*/
void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
- if (req.getParameterMap().size() > 1) {
+ boolean authDeferred = getAuthDeferred(req);
+ if (!authDeferred && req.getParameterMap().size() > 1) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
String path = getInnerPath(req, PATH_FILES);
path = path.endsWith("/")? path: path + '/';
path = URLDecoder.decode(path, "UTF-8");
+ // We only defer authenticating multipart POST requests.
+ if (authDeferred) {
+ if (!ServletFileUpload.isMultipartContent(req)) {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ handleMultipart(req, resp, path);
+ return;
+ }
+
String newName = req.getParameter(NEW_FOLDER_PARAMETER);
boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
copyResource(req, resp, path, copyTo);
else if (moveTo != null)
moveResource(req, resp, path, moveTo);
- else if (ServletFileUpload.isMultipartContent(req))
- handleMultipart(req, resp, path);
else
resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
}
if (logger.isDebugEnabled())
logger.debug("Multipart POST for resource: " + path);
- User user = getUser(request);
User owner = getOwner(request);
boolean exists = true;
Object resource = null;
StatusProgressListener progressListener = new StatusProgressListener(getService());
upload.setProgressListener(progressListener);
iter = upload.getItemIterator(request);
+ String dateParam = null;
+ String auth = null;
while (iter.hasNext()) {
FileItemStream item = iter.next();
+ String name = item.getFieldName();
InputStream stream = item.openStream();
- if (!item.isFormField()) {
+ if (item.isFormField()) {
+ final String value = Streams.asString(stream);
+ if (name.equals(DATE_PARAMETER))
+ dateParam = value;
+ else if (name.equals(AUTHORIZATION_PARAMETER))
+ auth = value;
+
+ if (logger.isDebugEnabled())
+ logger.debug(name + ":" + value);
+ } else {
+ // Fetch the timestamp used to guard against replay attacks.
+ if (dateParam == null) {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
+ return;
+ }
+
+ long timestamp;
+ try {
+ timestamp = DateUtil.parseDate(dateParam).getTime();
+ } catch (DateParseException e) {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
+ return;
+ }
+ if (!isTimeValid(timestamp)) {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ // Fetch the Authorization parameter and find the user specified in it.
+ if (auth == null) {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
+ return;
+ }
+ String[] authParts = auth.split(" ");
+ if (authParts.length != 2) {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ String username = authParts[0];
+ String signature = authParts[1];
+ User user = null;
+ try {
+ user = getService().findUser(username);
+ } catch (RpcException e) {
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
+ return;
+ }
+ if (user == null) {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ request.setAttribute(USER_ATTRIBUTE, user);
+
+ // Validate the signature in the Authorization parameter.
+ String data = request.getMethod() + dateParam + URLEncoder.encode(request.getPathInfo(), "UTF-8");
+ if (!isSignatureValid(signature, user, data)) {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
progressListener.setUserId(user.getId());
progressListener.setFilename(fileName);
String contentType = item.getContentType();
logger.debug("[" + method + "] " + path);
if (!isRequestValid(request)) {
- if (!method.equals(METHOD_GET) && !method.equals(METHOD_HEAD)) {
+ if (!method.equals(METHOD_GET) && !method.equals(METHOD_HEAD) &&
+ !method.equals(METHOD_POST)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ boolean authDeferred = getAuthDeferred(req);
// Strip the username part
String path;
try {
path = getUserPath(req);
} catch (ObjectNotFoundException e) {
+ if (authDeferred) {
+ // We do not want to leak information if the request
+ // was not authenticated.
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
return;
}
+ if (authDeferred && !path.startsWith(PATH_FILES)) {
+ // Only POST to files may be authenticated without an Authorization header.
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
if (path.startsWith(PATH_OTHERS)) {
resp.addHeader("Allow", methodsAllowed.get(PATH_OTHERS));
} catch (IllegalArgumentException e) {
return false;
}
- if (timestamp == -1)
- return false;
- Calendar cal = Calendar.getInstance();
- if (logger.isDebugEnabled())
- logger.debug("Time: server=" + cal.getTimeInMillis() + ", client=" + timestamp);
- // Ignore the request if the timestamp is too far off.
- if (Math.abs(timestamp - cal.getTimeInMillis()) > TIME_SKEW)
+ if (!isTimeValid(timestamp))
return false;
+
+ // Fetch the Authorization header and find the user specified in it.
String auth = request.getHeader(AUTHORIZATION_HEADER);
String[] authParts = auth.split(" ");
if (authParts.length != 2)
return false;
request.setAttribute(USER_ATTRIBUTE, user);
+
+ // Validate the signature in the Authorization header.
String dateHeader = useGssDateHeader? request.getHeader(GSS_DATE_HEADER):
- request.getHeader(DATE_HEADER);
+ request.getHeader(DATE_HEADER);
String data;
try {
data = request.getMethod() + dateHeader + URLEncoder.encode(request.getPathInfo(), "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
+ return isSignatureValid(signature, user, data);
+ }
+
+ /**
+ * Calculates the signature for the specified data String and then
+ * compares it against the provided signature. If the signatures match,
+ * the method returns true. Otherwise it returns false.
+ *
+ * @param signature the signature to compare against
+ * @param user the current user
+ * @param data the data to sign
+ * @return true if the calculated signature matches the supplied one
+ */
+ protected boolean isSignatureValid(String signature, User user, String data) {
if (logger.isDebugEnabled())
logger.debug("server pre-signing data: "+data);
String serverSignature = null;
return true;
}
+ /**
+ * A helper method that checks if the timestamp of the request
+ * is within TIME_SKEW milliseconds of the current time. If
+ * the timestamp is older (or even newer) than that, it is
+ * considered invalid.
+ *
+ * @param timestamp the time of the request
+ * @return true if the timestamp is valid
+ */
+ protected boolean isTimeValid(long timestamp) {
+ if (timestamp == -1)
+ return false;
+ Calendar cal = Calendar.getInstance();
+ if (logger.isDebugEnabled())
+ logger.debug("Time: server=" + cal.getTimeInMillis() + ", client=" + timestamp);
+ // Ignore the request if the timestamp is too far off.
+ if (Math.abs(timestamp - cal.getTimeInMillis()) > TIME_SKEW)
+ return false;
+ return true;
+ }
+
protected boolean getAuthDeferred(HttpServletRequest req) {
Boolean attr = (Boolean) req.getAttribute(AUTH_DEFERRED_ATTR);
return attr == null? false: attr;
var file = document.getElementById("file").value;\r
var form = document.getElementById("form").value;\r
var update = document.getElementById("update").value;\r
+ var formfile = document.getElementById('formfile');\r
var params = null;\r
var now = (new Date()).toUTCString();\r
var q = resource.indexOf('?');\r
else if (update)\r
params = update;\r
\r
+ // Browser upload with POST.\r
+ if (formfile.value) {\r
+ var formdate = document.getElementById('formdate');\r
+ var formauth = document.getElementById('formauth');\r
+ res = resource+formfile.value;\r
+ data = 'POST' + now + encodeURIComponent(decodeURIComponent(res));\r
+ sig = b64_hmac_sha1(atob(token), data);\r
+ formauth.value = user + " " + sig;\r
+ formdate.value = now;\r
+ var upload = document.upload;\r
+ upload.action = '/gss/rest'+res;\r
+ upload.submit();\r
+ return;\r
+ }\r
+\r
+ // All other API operations.\r
var req = new XMLHttpRequest();\r
req.open(method, '/gss/rest'+resource, true);\r
req.onreadystatechange = function (event) {\r
<tr><td>POST form </td><td><input id="form"></td></tr>\r
<tr><td>POST JSON update </td><td><input id="update"></td></tr>\r
</table>\r
+<form id="upload" name="upload" method="post" action="/gss/rest" enctype="multipart/form-data">\r
+<input id="formdate" type="hidden" name="Date" value="">\r
+<input id="formauth" type="hidden" name="Authorization" value="">\r
+File upload<input id="formfile" type="file" name="formfile">\r
+<input type="submit">\r
+</form>\r
<button onclick="send()">send</button><br>\r
<div id="result" style="width: 200px; height: 200px"></div>\r
</body>\r