Skip to content

Commit

Permalink
webdav: add cors support for uploading files
Browse files Browse the repository at this point in the history
Motivation:

Most browsers do send a preflight request with OPTION method for security
reason. The preflight request help the browser to find out if the main
request is allowed by the server. If allowed 200 status code is returned
with all the allowed headers. The current webdav implementation lack the
neccessary support the OPTION method.

Modification:

1. add support for OPTION method
2. make Access-Control-Allow-Origin header configurable

Result:

Able to upload files with a PUT using the browser.

Target: trunk
Require-notes: no
Require-book: no
Acked-by: Tigran Mkrtchyan <tigran.mkrtchyan@desy.de>
Acked-by: Paul Millar <paul.millar@desy.de>

Reviewed at https://rb.dcache.org/r/9614/
  • Loading branch information
Olufemi Adeyemi committed Sep 21, 2016
1 parent c5aa25e commit 9969b3b
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 0 deletions.
@@ -1,5 +1,6 @@
package org.dcache.webdav;

import com.google.common.collect.ImmutableList;
import io.milton.http.Auth;
import io.milton.http.HttpManager;
import io.milton.servlet.ServletRequest;
Expand All @@ -16,7 +17,15 @@

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.AccessController;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import dmg.cells.nucleus.CDC;
import dmg.cells.nucleus.CellAddressCore;
Expand All @@ -25,6 +34,8 @@
import org.dcache.auth.Subjects;
import org.dcache.util.Transfer;

import static com.google.common.base.Preconditions.checkArgument;

/**
* A Jetty handler that wraps a Milton HttpManager. Makes it possible
* to embed Milton in Jetty without using the Milton servlet.
Expand All @@ -33,14 +44,71 @@ public class MiltonHandler
extends AbstractHandler
implements CellIdentityAware
{
private static final ImmutableList<String> ALLOWED_ORIGIN_PROTOCOL = ImmutableList.of("http", "https");

private HttpManager _httpManager;
private CellAddressCore _myAddress;
private List<String> _allowedClientOrigins;

public void setHttpManager(HttpManager httpManager)
{
_httpManager = httpManager;
}

public void setAllowedClientOrigins(String origins)
{
if (origins.isEmpty()) {
_allowedClientOrigins = Collections.emptyList();
} else {
List<String> originList = Arrays.stream(origins.split(","))
.map(String::trim)
.collect(Collectors.toList());
originList.forEach(MiltonHandler::checkOrigin);
_allowedClientOrigins = originList;
}
}

private static void checkOrigin(String s)
{
try {
URI uri = new URI(s);

checkArgument(ALLOWED_ORIGIN_PROTOCOL.contains(uri.toURL().getProtocol()), "Invalid URL: The URL: %s " +
"contain unsupported protocol. Use either http or https.", s);
checkArgument(!uri.getHost().isEmpty(), "Invalid URL: the host name is not provided in %s:", s);
checkArgument(uri.getUserInfo() == null, "The URL: %s is invalid. User information is not allowed " +
"to be part of the URL.", s);
checkArgument(uri.toURL().getPath().isEmpty(), "The URL: %s is invalid. Remove the \"path\" part of the " +
"URL.", s);
checkArgument(uri.toURL().getQuery().isEmpty(), "The URL: %s is invalid. Remove the query-path part of " +
"the URL.", s);
checkArgument(uri.toURL().getRef() == null, "URL: %s is invalid. Reason: no reference or fragment " +
"allowed in the URL.", s);
checkArgument(!uri.isOpaque(), "URL: %s is invalid. Check the scheme part of the URL", s);
} catch (MalformedURLException | URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}

private void setCORSHeaders (HttpServletRequest request, HttpServletResponse response)
{
String clientOrigin = request.getHeader("origin");
if (Objects.equals(request.getMethod(), "OPTIONS")) {
response.setHeader("Access-Control-Allow-Methods", "PUT");
response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Origin", clientOrigin);
if (_allowedClientOrigins.size() > 1) {
response.setHeader("Vary", "Origin");
}
} else {
response.setHeader("Access-Control-Allow-Origin", clientOrigin);
if (_allowedClientOrigins.size() > 1) {
response.setHeader("Vary", "Origin");
}
}
}

@Override
public void setCellAddress(CellAddressCore address)
{
Expand All @@ -55,10 +123,22 @@ public void handle(String target, Request baseRequest,
try (CDC ignored = CDC.reset(_myAddress)) {
Transfer.initSession(false, false);
ServletContext context = ContextHandler.getCurrentContext();
String clientOrigin = request.getHeader("origin");

boolean isOriginAllow = _allowedClientOrigins.contains(clientOrigin);
if (isOriginAllow) {
setCORSHeaders(request, response);
}

switch (request.getMethod()) {
case "USERINFO":
response.sendError(501, "Not implemented");
break;
case "OPTIONS":
if (isOriginAllow) {
setCORSHeaders(request, response);
}
break;
default:
Subject subject = Subject.getSubject(AccessController.getContext());
ServletRequest req = new DcacheServletRequest(request, context);
Expand All @@ -78,6 +158,7 @@ public void handle(String target, Request baseRequest,
}
}


/**
* Dcache specific subclass to workaround various Jetty/Milton problems.
*/
Expand Down
Expand Up @@ -326,6 +326,7 @@
<list>
<bean class="org.dcache.webdav.MiltonHandler">
<property name="httpManager" ref="http-manager"/>
<property name="allowedClientOrigins" value="${webdav.allowed.client.origins}"/>
</bean>
<bean class="org.eclipse.jetty.server.handler.DefaultHandler"/>
</list>
Expand Down
14 changes: 14 additions & 0 deletions skel/share/defaults/webdav.properties
Expand Up @@ -589,3 +589,17 @@ webdav.service.gplazma.cache.timeout.unit = MINUTES

#spnego properties file
webdav.authz.spnego.file=${dcache.paths.etc}/spnego.properties

# ---- Cross-Origin Resource Sharing (CORS)
#
# The websites that may provide JavaScript to
# a web browser that then accesses this door.
# Values are a comma-separated list of website
# URLs without any path; e.g.,
# https://example.org, http://example.org:8080
#
# If empty then no website is authorised.
# Non-JavaScript clients are unaffected by
# this property.
#
webdav.allowed.client.origins =
1 change: 1 addition & 0 deletions skel/share/services/webdav.batch
Expand Up @@ -77,6 +77,7 @@ check -strong webdav.limits.graceful-shutdown.unit
check webdav.net.internal
check webdav.mover.queue
check webdav.authn.ciphers
check webdav.allowed.client.origins

# Our /robots.txt file. This advertises which parts of the HTTP
# service indexing robots (web-crawlers) should index. The particular
Expand Down

0 comments on commit 9969b3b

Please sign in to comment.