Server: Support for cross-domain WebDAV access (CORS)

Created on 17 Jan 2017  路  51Comments  路  Source: nextcloud/server

It would be great if web applications hosted on a different domain could access Nextcloud files via WebDAV. This would enable web app developers to offer users to store personal application data in their Nextcloud without the need to provide a dedicated Nextcloud app.

Currently this is not possible as the necessary CORS headers are not set.

@LukasReschke

1. to develop enhancement integration

Most helpful comment

Hello, I wrote simple Sabre plugin (not published yet) that adds CORS headers https://gitlab.com/kinolaev/nextcloud-dav-cors. Just download it to apps directory and add array of allowed domains to dav_cors.origins in config.php.

All 51 comments

Also related to this as a future step 禄Dropbox like file picker javascript integration #2028芦

@LukasReschke what are the next steps here?

I'd like to help as much as I can with this issue - my end goal is to be able to parse the calendar data out with some JS so my personal blog can detail conferences I've spoken at and places I will be speaking soon.

Hit me up if you need to bounce ideas for the solution off someone, or need example use cases etc. I don't have a dev environment for nextcloud set up, but if the problem is a shortage of hands I can take a stab at implementing it if I get some guidance.

@doleraj most often the reason is a shortage of hands, so your help would be much appreciated! :) For any guidance best join our IRC channel #nextcloud-dev or ask here.

Also cc @perry-mitchell for his work on connecting Nextcloud to web apps: https://nextcloud.com/blog/using-webdav-fs-to-access-files-in-nextcloud/ and cc @nextcloud/javascript

We're currently using webdav+Nextcloud to read and write our password archives both in the browser and in NodeJS, using this project (disclaimer: I'm the author). You can see here that we write to the storage without any specifics (besides auth headers).

I don't see why there'd be any problems, but perhaps you might have a specific example in terms of code or cURL etc.? Our library does use a transpiled copy of node-fetch however, which may have some affect on the outcome. It may be that items like native browser fetch somehow behaves differently.

So it seems that at least in out browser extension, it does indeed look like requests to Nextcloud servers (at least the demo ones) are failing with perm errors:

image

Also seems that I was not so successful in trying to add the Authorization header:

image

I've been trying with the following JS:

var request = new Request("https://demo.nextcloud.com/vaeshah9/remote.php/webdav/test.txt", {
    method: "GET",
    credentials: "include",
    headers: new Headers({
        Accept: "text/plain",
        Authorization: "Basic " + btoa("admin:admin")
    })
});

fetch(request).then(function(res) {
    console.log(res);
})

At the moment these do look like CORS issues 馃槙

The "with authentication" thing may be the trick. I'm trying to get at a public calendar without auth since the code'll be running browser-side.

As an example, hitting (https://cloud.arthurdoler.com/remote.php/dav/public-calendars/230G65ZAI9PV9KAT?export ) as an actual URL works fine, causing the .ics to be downloaded.
But trying the following:

const url = 'https://cloud.arthurdoler.com/remote.php/dav/public-calendars/230G65ZAI9PV9KAT?export';
        fetch(url).then((response) => {
            console.log(response)
        });

causes a CORS error:
Fetch API cannot load https://cloud.arthurdoler.com/remote.php/dav/public-calendars/230G65ZAI9PV9KAT?export. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:63342' is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Also cc @georgehrke @tcitworld of the Calendar app for @doleraj's specific question. :)

... oops! I apologize, I'd forgotten that Calendar is actually a separate app.

@doleraj But public-calendars is part of the dav app which belongs to the server ;)

@LukasReschke might know more security wise.

We've had a similar issue in bookmarks and ended up having an internal controller being sort of a facade for the public one which incorporates the @CORS annotation. The issue with the later is that it disables sessions. This solution feels more like an ugly hack, however.

I discovered that https://github.com/owncloud/core/pull/28457 is merged upstream, maybe we can integrate this.

@tcitworld mind opening a pull request to downstream that change? :) Or @LukasReschke what do you say?

Any updates on this?
Anyone partitioned in this issue: How to solve Same Origin Policy in Nextcloud?
What I want to do is send request to WebDAV server from another url. no more, no less.

@perry-mitchell hey, could you solve it?

@mehrdaad We have Nextcloud somewhat working in our password management software, but I believe it's a bit buggy still. The NodeJS side works it seems (no credentials or cookies) , but when fetching on the client side it get's all sorts of weird. I'll be checking into Nextcloud support again (for the browser) in the coming week, so I'll post back here if I find anything.

Can I add a sub domain like app.domain.tld to trusted domain for Nextcloud and use that app with webdav without having the CORS issue?

Writing back after some time.. I should've tested Nextcloud again sooner. We're trying to integrate with Buttercup (our password manager), but it doesn't work in the browser. We were originally having CORS issues but when trying to connect to a demo instance (on 12.0.0) we get 503 errors: #7365

As of now, I've not been able to get WebDAV working at all in the browser. Connecting from NodeJS works fine every time.

We were finally able to get Nextcloud working in the browser, by simply using fetch with Basic Authorization. Using window.fetch with that header, to request something like https://server.com/remote.php/webdav, works for me in Chrome.

Any update on this? Is there anyone looking into this at the moment? Or is this almost a lost cause?

Same question here, I want to use some features of the Nextcloud on a different client with a different view(limited view), I keep getting 'unauthorized 401' error, any solutions?

Adding the code from https://stackoverflow.com/questions/8719276/cors-with-php-headers/9866124#9866124 to remote.php (just after the already present header-function call) solves the issue for me.
Update: Methods PUT and MOVE need to be added as well (they are not present in the codesnippet from stackoverflow)

Update: Example code (remote.php):

try {
        require_once __DIR__ . '/lib/base.php';

        // All resources served via the DAV endpoint should have the strictest possible
        // policy. Exempted from this is the SabreDAV browser plugin which overwrites
        // this policy with a softer one if debug mode is enabled.
        header("Content-Security-Policy: default-src 'none';");

        // Allow from any origin
        if (isset($_SERVER['HTTP_ORIGIN'])) {
            // Decide if the origin in $_SERVER['HTTP_ORIGIN'] is one
            // you want to allow, and if so:
            header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
            header('Access-Control-Allow-Credentials: true');
            header('Access-Control-Max-Age: 86400');    // cache for 1 day
        }

        // Access-Control headers are received during OPTIONS requests
        if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {

            if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
                // may also be using PATCH, HEAD etc
                header("Access-Control-Allow-Methods: GET, POST, PUT, MOVE, OPTIONS");

            if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
                header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");

            exit(0);
        }


        if (\OCP\Util::needUpgrade()) {
              // [...] (end of snippet)

It could be useful to also add the POPFIND to the Access-Control-Allow-Methods, like:

header("Access-Control-Allow-Methods: GET, POST, PUT, MOVE, OPTIONS, PROPFIND");

Any update on this? Over at Buttercup we see issues pop up now and then about users not being able to access their Nextcloud servers via WebDAV, which we provide using my library webdav-client. It also has a couple of issues regarding Nextcloud access from the browser.

Are there any plans on supporting this? Will OAuth be the only supported access method via websites? Some official feedback would be much appreciated here.

@rullzer can you have a look? This probably has security implications, but we'd like third parties like buttercup to be able to work with nextcloud of course!

Thanks! This鈥檇 be super good. WebDAV really makes things easy, and will definitely do so in a cross platform sense when this works in a browser.

Also open to ideas of using the OAuth system I鈥檝e seen in the dashboard but WebDAV would definitely bring less friction.

I'll look into this. But no time line promises.

So I have been thinking about this while traveling. A few things that come to mind. I need to really dive into the specs. But.

I would say that you can only use webdav via CORS if you are authenticated via an app token.

In a later stage we can then enhance our OAuth API to allow user to more easily obtain OAuth tokens for their apps.

Hmm.. how would such a token be generated? Is there an OAuth flow in Nextcloud (I鈥檓 not familiar with any such feature) or do the users need to manually do it in the admin interface?

This is still a bit more work than with WebDAV on other devices, hence it adds more friction and can be a turn off for some looking for an integrated solution. We鈥檝e historically suggested Nextcloud to users due to how easy it鈥檚 been but this might be a difficult sell.

WebDAV with Nextcloud works on all devices, except in the browser, using only username and password - it鈥檚 just CORS that鈥檚 causing an issue.

when CORS with a token is working, I'm intrested to build a Nextcloud module for remotestorage.js with webdav-client

maybe we will find a way then to host pictures in Mastodon in our own cloud accounts and not on the Mastadon pods

@perry-mitchell Improved OAuth flow would be up next then :)

Hmm.. how would such a token be generated? Is there an OAuth flow in Nextcloud (I鈥檓 not familiar with any such feature) or do the users need to manually do it in the admin interface?

Current OAuth flow in Nextcloud does not support browser side clients: #12667

Looking forward to this feature implementation!

So there's no way to make requests to a Nextcloud server from the browser then?

Sadly this means that we might have to move on from Nextcloud as a storage backend until browser access is properly supported. CORS is a huge pain but a necessity when building anything with an API these days. I look forward to having such support.. Thank you.

@memen45 @perry-mitchell I'll look into https://tools.ietf.org/html/rfc7636 and see if we can support that

I'm also facing an issue with this: I try to access a file via remote.php from an external Web-Application (running in Browser).

The CORS preflight is not successfull. The Nextcloud-Server is expecting an 'Authorization' header for the OPTIONS request (see: curl -X OPTIONS http://MYSERVER/remote.php/webdav/SOME/FILE.png).
But Browser tend to not send the Authorization headers in OPTIONS requests, since that is required by the CORS spec saying

for a cross-origin request with preflight [...] Exclude user credentials

I'm not into the details here but wouldn't it be sufficient to change the server in a way to response to OPTIONS call without requiring Authorization header (but leaving it for GET, POST, ...)?

(Found here: https://stackoverflow.com/questions/52072001/the-authorization-header-is-not-sent-in-options-call-by-the-browser)

Hello!

I solved this problem with following additions to recommended nginx config:

map $http_origin $access_control_allow {
    default 0;
    https://first-origin.com 1;
    https://second-origin.com 1;
}
map $access_control_allow $access_control_allow_origin {
    default '';
    1 $http_origin;
}
map $access_control_allow $access_control_allow_methods {
    default '';
    1 $http_access_control_request_method;
}
map $access_control_allow $access_control_allow_headers {
    default '';
    1 $http_access_control_request_headers;
}
map $access_control_allow $access_control_expose_headers {
    default '';
    1 'etag';
}
map "$access_control_allow:$request_method" $access_control_return_204 {
    default 0;
    '1:OPTIONS' 1;
}
# ...
server {
    # ...
    location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+)\.php(?:$|\/) {
        add_header Access-Control-Allow-Origin $access_control_allow_origin;
        add_header Access-Control-Allow-Methods $access_control_allow_methods;
        add_header Access-Control-Allow-Headers $access_control_allow_headers;
        add_header Access-Control-Expose-Headers $access_control_expose_headers;
        if ($access_control_return_204) {
            return 204;
        }
        # ...
}

@rullzer what do you think about this?

Is this link relevant? https://docs.nextcloud.com/server/stable/developer_manual/app/requests/api.html
I realise you would prefer to have CORS headers on the main WebDAV interface, but that doc seems to explain how to develop a NextCloud app that exposes an API with CORS headers on it, right?

@michielbdejong your link is gone for me. I guess you mean this: https://github.com/nextcloud/documentation/blob/3aecb6899b79650a0efc17936f69a2f4ca575288/developer_manual/app/api.rst#modifying-the-cors-headers

(no idea if that helps, but I'd also love to be able to set those headers)

Doing a basic OPTIONS request is failing incorrectly when not including authentication. The HTTP spec is that OPTIONS should NOT have authentication, so NextCloud is at fault.

For example, this should succeed:

curl -i -X OPTIONS https://mynextcloud.com/remote.php/webdav/MailAttachments/myfile.zip
HTTP/1.1 401 Unauthorized
...

If I use a password or token it succeeds, but this is not how it should work...

Hello, I wrote simple Sabre plugin (not published yet) that adds CORS headers https://gitlab.com/kinolaev/nextcloud-dav-cors. Just download it to apps directory and add array of allowed domains to dav_cors.origins in config.php.

Will try that out, thanks!

Thank you, @kinolaev! Is anyone successfully using the app? For me already the OPTIONS requests didn't make it to the OCA\DAV\Connector\Sabre::addPlugin listener on NC 18. And even if I waved the OPTIONS requests through in remote.php the real request (e.g. PROPFIND) didn't make it to the listener neither.

Hello @pbek,
I updated plugin for nc 19, for nc 18 please use initial commit

Do you mean https://gitlab.com/kinolaev/nextcloud-dav-cors/-/commit/d47a941635020ef356768affb467698853b8b4a2, @kinolaev? Thank you, I'll try reverting that. But so far nothing made it into the event listener lambda, the code to add the plugin was never executed for me... Even though the event was fired by Sabr/Dav...

Yes, you can just replace beforeMethod:* with beforeMethod for nc 18 (sabre/dav below 4).

That really did it! Thank you very much! Should I a make a pull requests to increase your min-version to 19? :grin:

No thanks, I will try to add version check with next few days)

Great! Do you care also switching away from the deprecated (as in NC 18) \OC::$server->getEventDispatcher() to \OCP\EventDispatcher\IEventDispatcher? :grin:

I updated the plugin:

  • getEventDispatcher replaced with IEventDispatcher
  • lastest version works with nextcloud 18 and 19
  • fixed phpdoc

Awesome, I'll test that tomorrow!

Works great, @kinolaev. Thank you very much!

There now is an app in the app store for setting the CORS header:
https://apps.nextcloud.com/apps/webapppassword

It also allows the generation of temporary app passwords to be able access the Nextcloud WebDAV service from inside web applications.

There is an example implementation that uses this app to generate a temporary password and make a WebDAV request to a Nextcloud instance (docker container included).

Here are some screenshots for https://apps.nextcloud.com/apps/webapppassword:

Settings

screenshot

WebDAV File Picker

screenshot-filepicker

Temporary password generation and WebDAV PROPFIND

screenshot-webdav

Was this page helpful?
0 / 5 - 0 ratings