Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for cross-domain WebDAV access (CORS) #3131

Open
xMartin opened this issue Jan 17, 2017 · 103 comments · May be fixed by #40537
Open

Support for cross-domain WebDAV access (CORS) #3131

xMartin opened this issue Jan 17, 2017 · 103 comments · May be fixed by #40537

Comments

@xMartin
Copy link
Member

xMartin commented Jan 17, 2017

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

@jancborchardt
Copy link
Member

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

@jancborchardt
Copy link
Member

@LukasReschke what are the next steps here?

@doleraj
Copy link

doleraj commented Jun 30, 2017

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.

@jancborchardt
Copy link
Member

@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

@perry-mitchell
Copy link

perry-mitchell commented Jun 30, 2017

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 😕

@doleraj
Copy link

doleraj commented Jun 30, 2017

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.

@jancborchardt
Copy link
Member

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

@doleraj
Copy link

doleraj commented Jul 1, 2017

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

@georgehrke
Copy link
Member

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

@LukasReschke might know more security wise.

@blizzz
Copy link
Member

blizzz commented Jul 3, 2017

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.

@tcitworld
Copy link
Member

I discovered that owncloud/core#28457 is merged upstream, maybe we can integrate this.

@jancborchardt
Copy link
Member

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

@mehrdad-shokri
Copy link

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.

@mehrdad-shokri
Copy link

@perry-mitchell hey, could you solve it?

@perry-mitchell
Copy link

perry-mitchell commented Oct 21, 2017

@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.

@anmol26s
Copy link

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?

@perry-mitchell
Copy link

perry-mitchell commented Dec 2, 2017

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.

@PeterNerlich
Copy link

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

@BeardedDonut
Copy link

BeardedDonut commented Jun 2, 2018

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?

@mabuch
Copy link

mabuch commented Jun 22, 2018

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)

@dagobit
Copy link

dagobit commented Jul 13, 2018

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");

@perry-mitchell
Copy link

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.

@janLo
Copy link

janLo commented Feb 22, 2021

But it would for example make a difference if some webpage with a huge amount of traffic would use your website as CDN for e.g. images you are hosting, wouldn't it? (that could be the result if you would allow all origins with CORS)

I honestly don't think that this is any objective of the strict-origin policy. Its more about leaking sensitive information. If you want to protect yourself from high-traffic usage that you should not rely on any client-side measures.

Nextcloud already has a builtin mechanism to request app passwords. This could get extended to add a lost of allowed origins.

@neben
Copy link

neben commented Feb 22, 2021

But it would for example make a difference if some webpage with a huge amount of traffic would use your website as CDN for e.g. images you are hosting, wouldn't it? 😁 (that could be the result if you would allow all origins with CORS)

I'm afraid CORS doesn't help with that. You can always embed cross-origin images, you're just limited in the way you can access/manipulate that image data with JS. You can try it, share an image file in nextcloud, and embed that image on a different domain using the share URL + '/preview':

<img src="https://nextcloud.com/s/1234/preview">

It'll load and display without issues.

@fschrempf
Copy link

Is there a way for whitelisting a specific origin from which I want to access a public calendar link like https://nextcloud.example.com/apps/calendar/p/xyz1234 without needing user authentication?

If not I think there should be one. If I have a website/webapp that I'm controlling myself I want to have the possibility to let it access NC data without running into CORS issues.

@photopea
Copy link

Guys, could you simply always enable CORS by default in all Nextcloud instances?

Authors of any webapp can always create a server-side "mirroring" of the HTTP requests, and their server does not care about the CORS. So not having CORS headers does not add any security, it only makes everything more complicated.

@Utopiah
Copy link

Utopiah commented Oct 21, 2022

Also interested in this.

If others are stuck but use NextCloud behind nginx as a reverse proxy I can recommend https://www.williamjbowman.com/blog/2021/05/13/enabling-cors-for-nginx-webdav-and-caldav-reverse-proxy/ which worked for me for https://github.com/bfren/docker-nginx-webdav

@photopea do you mean something like https://github.com/sebastienvercammen/node-cors-proxy-server and if not do you have a recommendation until this is implemented?

@pbek
Copy link
Member

pbek commented Nov 1, 2022

There still is https://github.com/digital-blueprint/webapppassword.

@salonikumawat28
Copy link

Hello everyone, I have created a NextCloud app which adds a file menu item in Files app. When you click this menu item, it opens my website. From this website, I want to login into NextCloud so that I can get credentials which I can use to download and upload files.

First step I did was using the hardcoded username:password and passed it in Authorization header to download and upload URLs which failed on CORS. I was able to resolve the CORS issues once I installed WebAppPassword and whitelisted my website.

As a second step, I replaced the hardcoded username:password with the login v2 flow and it failed on CORS. This was surprising as my website origin was already whitelisted in WebAppPassword and was working for download and upload URLs. I also tried OAuth2 flow but it also failed on CORS and preflight.

Is there a way to avoid CORS and preflight issues in login v2 flow or OAuth flow?
You can find more details at this issue I created: #34898

@bonnebulle
Copy link

bonnebulle commented Nov 3, 2022

There still is https://github.com/digital-blueprint/webapppassword.

Yes, the android app say to add "https://app.super-productivity.com", it is the same with linux/desktop app ?

EDIT : It works... I do not have create the .json file...

Thanks (here, is not working)

@apfohl
Copy link

apfohl commented Dec 2, 2022

I was also trying to upload a file from a browser client to https://{{host}}/public.php/webdav/sample.jpg. Therefore the browser sends an OPTIONS request to the same endpoint. It returns with a 401. An OPTIONS request is not supposed to be authenticated, because it breaks the mechanism as I can't tell the browser it should authenticate it somehow.

I have work around in place now locally with my Traefik proxy that responds to the OPTIONS request correctly instead of the Nextcloud instance.

labels:
  - "traefik.http.routers.nextcloud-secure.middlewares=cors"
  - "traefik.http.middlewares.cors.headers.accesscontrolallowmethods=GET,OPTIONS,PUT"
  - "traefik.http.middlewares.cors.headers.accesscontrolalloworiginlist=https://app.local"
  - "traefik.http.middlewares.cors.headers.accesscontrolmaxage=100"
  - "traefik.http.middlewares.cors.headers.addvaryheader=true"
  - "traefik.http.middlewares.cors.headers.accesscontrolallowcredentials=true"
  - "traefik.http.middlewares.cors.headers.accesscontrolallowheaders=authorization"

It would be really nice to not be forced to do it like that.

@pbek
Copy link
Member

pbek commented Dec 2, 2022

That's a great workaround!

@apfohl
Copy link

apfohl commented Dec 2, 2022

That's a great workaround!

The problem is, that I as a client developer can not rely on having this in place for random Nextcloud instances.

@pbek
Copy link
Member

pbek commented Dec 2, 2022

...nor random Nextcloud Apps for that matter.

@apfohl
Copy link

apfohl commented Dec 2, 2022

...nor random Nextcloud Apps for that matter.

The Apps have probably different methods for pushing files. But idk.

@pbek
Copy link
Member

pbek commented Dec 2, 2022

I meant "the permissions to install an app that sets headers", like https://github.com/digital-blueprint/webapppassword. 😉

@jthoward64
Copy link

I don't understand why simply allowing access for login v2 is such an issue, NextCloud already rejects unauthorized requests, so setting CORS for the WebDAV gateway shouldn't be an issue either, but if that is the blocker oh well, SSR web apps can get around that easily enough. The main issue for web apps, as far as I can tell, is the /login/v2 route. If that got support (even if it behind a config file flag, though not preferable) then it would open up a world of opportunities. And I doubt anyone is going to be using my Nextcloud for a CDN for my login page.

@mmuman
Copy link

mmuman commented Sep 3, 2023

I have a similar problem with using the download link for a markdown file from a js app on a separate domain: the default CORS policy blocks it. I don't see any reason those would be… Should I open a separate issue?

@pkuegler
Copy link

pkuegler commented Apr 8, 2024

It does work using WebAppPassword

Solution:

  1. Install "https://github.com/digital-blueprint/webapppassword" on the nextcloud instance you want to access from a web-client
  2. In Administration Settings/WebAppPassword add the source domain as allowed origin for webdav/caldav
  3. Use some web-based code to access the resource. Make sure that the preflight is without authantication

Example:
If you want to retrieve a file listing on your webserver (webserver.somedomain.com) from your nextcloud (nextcloud.yourdomain.com) do as follows:

  1. Add "https://webserver.somedomain.com" as allowed origin in nextcloud/WebAppPassword
  2. Use e.g. something lioke this code on your website:
fetchDataFromWebDav(baseUrl, folderPath)
        .then(parseXmlResponse)
        .then(data => renderFileList(data, baseUrl))
        .catch(error => {
            console.error('Error:', error);
            widgetContainer.innerHTML = '<p>Error loading directory contents.</p>';
        });


function fetchDataFromWebDav(baseUrl, folderPath) {
    const fullUrl = `${baseUrl}${encodeURIComponent(folderPath)}`;
    const username = 'testuser';
    const password = 'testuserpwd'; // just for show - use SSO!
    const headers = new Headers({
        'Authorization': 'Basic ' + btoa(username + ":" + password),
        'Content-Type': 'application/xml; charset=utf-8'
    });

    return fetch(fullUrl, { method: 'PROPFIND', headers });
}

function parseXmlResponse(response) {
    if (!response.ok) {
        throw new Error('Network response was not ok');
    }
    return response.text()
        .then(str => (new window.DOMParser()).parseFromString(str, "text/xml"));
}


function renderFileList(data, baseUrl) {
    const files = data.querySelectorAll('response');
    let fileList = '<ul>';

    files.forEach(file => {
        const href = file.querySelector('href').textContent;
        const name = decodeURIComponent(href.split('/').pop());
        if (!name) return; // Skip empty names

        const isFolder = file.querySelector('resourcetype').querySelector('collection') !== null;
        const icon = isFolder ? '📁' : '📄';
        const itemPath = href.replace('/nextcloud/remote.php/dav/files/testuser', ''); // Adjust path as needed

        fileList += `<li>${icon} <a href="${href}" class="${isFolder ? 'folder-link' : ''}" data-path="${href}">${name}</a></li>`;
    });

    fileList += '</ul>';
    // Do something with fileList
}

@pkuegler
Copy link

pkuegler commented Apr 9, 2024

Does it work for public shares?

Unfortunately not. The above solution only works for "private" shares that are being accessed via "^/remote.php/dav/files...". If you also want to access public shares "^/public.php/webdav/...", then the WebAppPassword does nto help you, as it does not take care of the cors headers there. You can see that, because all preflight requests (done by the browser automatically before every fetch) will fail because of "missing basic authentication header". And just to be clear: This is a nextcloud bug, as based on specs, there must not be authentication headers in the preflight options request.

Can it work for public shares?

Yes, it can, but you need to modify the .htaccess for this (in the nextcloud root folder). There should be already a couple of rewrites there ;-) If you are using a reverse proxy, you can add the rewrites there as well, even though that might me more ressource-intensive, as the proxy needs to cache the outgoing response to be able to rewrite headers (you can use nginX/LUA for that).

Just add following code (adjust to your paths etc.) to the .htaccess:
Replace "https://webserver.somedomain.com" with your actual website.

<IfModule mod_headers.c>
    # Apply to all requests
    Header always set Access-Control-Allow-Origin "https://webserver.somedomain.com"
    Header always set Access-Control-Allow-Credentials "true"
    Header always set Access-Control-Allow-Methods "PROPFIND"
    Header always set Access-Control-Allow-Headers "authorization,content-type"

    # Specific handling for OPTIONS requests
    <If "%{REQUEST_METHOD} == 'OPTIONS'">
        SetEnvIf Origin "https://webserver.somedomain.com" PREFLIGHT_REQUEST_FROM_WEBSITE
        Header merge Access-Control-Allow-Origin "https://webserver.somedomain.com" env=PREFLIGHT_REQUEST_FROM_WEBSITE
        Header merge Access-Control-Allow-Methods "PROPFIND" env=PREFLIGHT_REQUEST_FROM_WEBSITE
        Header merge Access-Control-Allow-Headers "authorization,content-type" env=PREFLIGHT_REQUEST_FROM_WEBSITE
        Header merge Access-Control-Allow-Credentials "true" env=PREFLIGHT_REQUEST_FROM_WEBSITE
        # Respond with 204 No Content for OPTIONS requests
        # Note: This directive might not be respected depending on the server configuration and Apache version
        RewriteEngine On
        RewriteCond %{REQUEST_METHOD} OPTIONS
        RewriteRule .* - [R=204,L]
    </If>
</IfModule>

This enforces/overwrite the headers to tell the browser to move forward with the actual fetch request. Keep in mind, that this is not increasing any security risk, as the actual request (PROPFIND) will be checked correctly for the authorization headers. The problem you are facing is, that the browser never tries the fetch request (PROPFIND), as the preflight (OPTIONS) failes, due to this Nextcloud bug.

Have fun!

@joshyy1312
Copy link

I tried the htaccess rewrite to use a public share but the PROPFIND request still fails with a CORS error (OPTION passes with 204)

Am I correct to use "myNextCloud/public.php/webdav" with the share token as user and an empty password?

@pkuegler
Copy link

pkuegler commented Apr 11, 2024

Am I correct to use "myNextCloud/public.php/webdav" with the share token as user and an empty password?

Almost. When you create a public share, then nextcloud creates something like "https://mydomain/basepath/index.php/s/ideFb6rGMjgJa8y" and password "password".
In that case the path for webdav call is "myNextCloud/public.php/webdav/" the username is "ideFb6rGMjgJa8y" and the password is "password". If you then want to navigate to a subdirectory, you modify teh path to "myNextCloud/public.php/webdav/subfolder1/".

See also: https://docs.nextcloud.com/server/22/user_manual/en/files/access_webdav.html#accessing-public-shares-over-webdav

@joshyy1312
Copy link

joshyy1312 commented Apr 12, 2024

It still didn't work.
For me, with your htaccess code the OPTIONS request works and then the PROPFIND fails with a CORS error.

In my case the file is a public share without password, it just needs the token as login and an empty password. Everything is correct because I can see the XML file listing in the browser XHR console but it just isn't passed to my script.

From what I see in the browser inspector, with the PROPFIND request, the "always set" header lines are not set.

Fortunately, here is the solution I found and it works:

It might be related to my hosting provider and an apache/fastcgi bug as stated here.

Using one of the solution in the above article, namely adding the same headers through php directly, worked for me.

There is a file called .user.ini in the hosting root and it allows to set php properties or settings.

If you add the following line it will prepend a php file to every php file request.

auto_prepend_file="myheaders.php"

The myheaders.php file looks like this:

<?php
header('Access-Control-Allow-Origin: https://your.other.domain');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Methods: PROPFIND, GET');
header('Access-Control-Allow-Headers: authorization,content-type');
?>

It now seems to work without problem, I can retrieve the file listing and also GET a single file directly.

Many thanks pkuegler for pointing me in the right direction and taking the time to explain it in detail, I've been looking for weeks about a way to achieve this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.