Skip to content

Support for CORS on OCS and Webdav APIs + domain whitelisting - #28457

Merged
PVince81 merged 1 commit into
masterfrom
enablingCORS
Aug 29, 2017
Merged

Support for CORS on OCS and Webdav APIs + domain whitelisting#28457
PVince81 merged 1 commit into
masterfrom
enablingCORS

Conversation

@noveens

@noveens noveens commented Jul 21, 2017

Copy link
Copy Markdown
Contributor

Description

Manually added the * header

Motivation and Context

Trying to enable CORS.

Testing instructions:

  1. Download this file: https://github.com/noveens/js-owncloud-client/blob/master/browser/bundle.js
  2. Create a html file in the same directory with the following code.
<!DOCTYPE html>
<html>
	<head>
		<title>owncloud Lib. test</title>
		<script type="text/javascript" src="./bundle.js"></script>

		<script type="text/javascript">
			// var oc is global
			oc.setInstance('PUT OC INSTANCE HERE');
			
			oc.login('USERNAME', 'PASSWORD').then(status => {
				alert(status);
			}).catch(error => {
				alert(error);
			});

		</script>
	</head>

	<body>
		Check the console.
	</body>
</html>
  1. Try sniffing the request to see it's response headers (Can be done using the Network tab on the right to Console in browser)

@PVince81

@noveens

noveens commented Jul 21, 2017

Copy link
Copy Markdown
Contributor Author

Screenshot of the request and response headers :

asd

Do I need to remove any more headers or add some more, maybe?

@noveens
noveens requested review from LukasReschke, PVince81 and peterprochaska and removed request for LukasReschke July 22, 2017 07:24
Comment thread core/Controller/CloudController.php Outdated
* @return array
*/
public function getCapabilities() {
header("Access-Control-Allow-Origin: *");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use * as a wildcard domain list. We should use the value of the origin header in the initial request. A much better solution ist to have a whitelist with domains that are allowed to request content from the other domain.

@noveens noveens Jul 28, 2017

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Peter-Prochaska ,

In this Pull Request, I was first trying to manually enable CORS for all (The * header) and I later attempt to make an OC app, which would maintain a table of the authenticated users authenticated via OAuth2 and then enable CORS for them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to discuss about a concept for this.

I'm not sure if CORS can be used per authenticated user/domain. If it can, then the OPTIONS call might need to be pre-authenticated to be able to go through and receive the headers.
You'd then need a list of whitelisted domains per users. Maybe this can work, I suggest you experiment with this.

Comment thread core/Controller/CloudController.php Outdated
*/
public function options() {
// for cross-domain request checks
header("Access-Control-Allow-Origin: *");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above

Comment thread lib/private/legacy/response.php Outdated
break;
}
header($protocol.' '.$status);
header("Access-Control-Allow-Origin: *");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above

@PVince81 PVince81 added this to the development milestone Jul 27, 2017
Comment thread apps/provisioning_api/lib/Users.php Outdated
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: authorization, OCS-APIREQUEST, Origin, X-Requested-With, Content-Type, Access-Control-Allow-Origin");
header("Access-Control-Allow-Methods: GET, OPTIONS, POST, PUT, DELETE, MKCOL, PROPFIND");
header("Access-Control-Allow-Credentials: true");

@peterprochaska peterprochaska Aug 2, 2017

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use Access-Control-Allow-Origin: * together with Access-Control-Allow-Credentials: true. With this you can send cookies, headers and certificates from every other domain to the server. Chrome and firefox will block this with an error. You should use one or more domain names, coming from a whitelist, in Access-Control-Allow-Origin.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true". This is the error

@noveens

noveens commented Aug 3, 2017

Copy link
Copy Markdown
Contributor Author

@PVince81 @Peter-Prochaska

Please review.
I have stored the white-listed domains using my app: https://github.com/noveens/cors
After storing the white-listed domains, I check if the requester domain is white-listed and if is, I add the CORS Headers.

Steps For Testing:

  1. Install the CORS App ( https://github.com/noveens/cors )
  2. Enable the App.
  3. Add a domain from which you will request under Settings->Personal->security (I used a virtual host) (Don't forget to add "http://www.")
  4. Use the js-owncloud-client library to request Provisioning API methods.
  5. For example use this html:
<!DOCTYPE html>
<html>
	<head>
		<title>owncloud Lib. test</title>
		<script type="text/javascript" src="./bundle.js"></script>
		<script type="text/javascript">
				// var oc is global
				oc.setInstance('INSTANCE');

				oc.login('USERNAME', 'PASSWORD').then(status => {
					// alert(status);
					return oc.users.getUsers();
				}).then(users => {
					console.log(users);
					return oc.groups.groupExists('admin');
				}).then(groups => {
					console.log(groups);
					return oc.apps.getApps();
				}).then(apps => {
					console.log(apps);
				}).catch(error => {
					alert(error);
				});
		</script>
	</head>

	<body>
		Check the console.
	</body>
</html>
  1. Don't forget to use this bundle: https://github.com/noveens/js-owncloud-client/blob/master/browser/bundle.js

@noveens
noveens requested a review from DeepDiver1975 August 3, 2017 16:12
\OC::$server->getLogger(),
\OC::$server->getTwoFactorAuthManager()
);
API::register('options', '/cloud/users', [$users, 'options'], 'provisioning_api', API::GUEST_AUTH);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe better implement this directly in the OCS router ?

One idea would be to have a @CORS annotation in every method that allow it which would tell the router to also automatically accept the OPTIONS method.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah right... OCS might not be as flexible...

But the app framework supports annotations so it would work there.

I thought we already rewired OCS to App framework somewhere... needs some research

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a quick look at API::register(), it seems we might not be able to use annotations easily as OCS is not the AppFramework unfortunately. One alternative would be to add an extra argument $options to register() and then register the routes this way:

    API::register('post', '/cloud/users', [$users, 'options'], 'provisioning_api', API::GUEST_AUTH, ['cors' => true]);

but then multiple methods might be registered for the same routes and CORS doesn't restrict by verb.

So yet another approach, add a new method:

API::enableCors('/cloud/users');

This would enable CORS for the given route.

Comment thread core/Controller/CloudController.php Outdated
* @return array
*/
public function options() {
// for cross-domain request checks

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this duplicate, again... yeah I think the annotation / route approach is way better so all this header logic would be in the router code instead

Comment thread core/Controller/CloudController.php Outdated
}

/**
* @PublicPage

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, looks like annotations do work, hmm...

Comment thread lib/private/legacy/response.php Outdated
if (in_array($domain, $allowedDomains)) {
header("Access-Control-Allow-Origin: " . $domain);
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Access-Control-Allow-Origin");
header("Access-Control-Allow-Methods: GET, OPTIONS, POST, PUT, DELETE, MKCOL, PROPFIND");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this list could be (optionally) retrieved from all known registered routes.

So if only "GET" and "POST" were registered for that route, only allow these then.

Nice to have, not a requirement I think.

@PVince81

PVince81 commented Aug 4, 2017

Copy link
Copy Markdown
Contributor

@noveens can you explain the CORS approach that you wanted to use ?

I remember you said that you had to pick "*" for the first request / preflight and then restrict the domain for the second one ? Can you summarize the flow step by step ?

@noveens

noveens commented Aug 4, 2017

Copy link
Copy Markdown
Contributor Author

My Approach:

Q1. First, store domains for which the user wants to allow CORS for. (User savable, not admin)
A1. I made an OC App for this: https://github.com/noveens/cors

Q2. Pre-flight request(OPTIONS)?
A2. Give the " * " header as the response since these requests are not authenticated.

Q3. For the main request?
A3. - Retrieve the user white-listed domains
- Check the requester domain
- Check if requester domain is white-listed
- If is, add "Access-Control-Allow-Origin: White-listed Domain" and other headers.

@PVince81 @Peter-Prochaska Please Check the approach.

Comment thread apps/dav/appinfo/v1/webdav.php Outdated
);
$requestUri = \OC::$server->getRequest()->getRequestUri();

if (\OC::$server->getRequest()->getMethod() === "OPTIONS") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be written as a Sabre plugin instead, if possible

* @param array $parameters
* @return OC_OCS_Result
*/
public function getApps($parameters) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use @CORS annotation instead ? or is it having trouble with OCS API ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @CORS notation doesn't work with OCS (Basically all those APIs whose routes are registered using the API::register method).

Comment thread ocs/v1.php Outdated
OC::handleLogin(\OC::$server->getRequest());
}

if (\OC::$server->getRequest()->getMethod() === "OPTIONS") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any chance to put this inside the OCS router instead ?

@noveens

noveens commented Aug 7, 2017

Copy link
Copy Markdown
Contributor Author

@PVince81 @Peter-Prochaska ,

Please check this static web-app I built:
https://github.com/noveens/staticOwncloudWebapp

if(isset($this->request->server['HTTP_ORIGIN']) &&
$this->reflector->hasAnnotation('CORS')) {

if (\OC::$server->getAppManager()->isEnabledForUser('cors')) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably put the whitelisting thing directly into the core. I don't see any benefit of having this in a separate app.

Also having core depend on apps and hard-coded app names is usually not a good idea.

Can you bring the whitelist logic into this PR ? The likely right location is under settings/ somewhere

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the other alternative is bundling the "cors" app into core's "apps/cors" but I don't see any benefit of having this code into a separate app.

@DeepDiver1975 thoughts ? https://github.com/noveens/cors

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @PVince81. Why should we have a separate app?

@peterprochaska peterprochaska left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks good. But no need to have a separate app.

if(isset($this->request->server['HTTP_ORIGIN']) &&
$this->reflector->hasAnnotation('CORS')) {

if (\OC::$server->getAppManager()->isEnabledForUser('cors')) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @PVince81. Why should we have a separate app?

@noveens

noveens commented Aug 10, 2017

Copy link
Copy Markdown
Contributor Author

@PVince81 @Peter-Prochaska,

Thanks for your approvals, I just have one doubt now:
For now, I am returning the response from here: https://github.com/owncloud/core/pull/28457/files#diff-c738eef186bd6ff91da09dff428779d1L120

So I am worried that whether the previous implementation of @CORS might break some functionality since it used to give cross origin access to all requester domains.

@owncloud owncloud deleted a comment from PVince81 Aug 10, 2017
@PVince81

Copy link
Copy Markdown
Contributor

Not sure but since @Peter-Prochaska said that the wildcard domain is not acceptable, it is likely that the way how the old @CORS worked is also insecure so having the whitelist approach is a security improvement.

@Peter-Prochaska can you confirm ?

@PVince81 PVince81 changed the title [WIP] Trying enabling CORS Support for CORS on OCS and Webdav APIs + domain whitelisting Aug 11, 2017
Comment thread lib/private/legacy/response.php Outdated
* This function adds the CORS headers if the requester domain is white-listed
*/
public static function setCorsHeaders($userId, $domain) {
$allowedDomains = explode(",", \OC::$server->getConfig()->getUserValue($userId, 'cors', 'domains'));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest renaming "cors" to "core" and the key to "cors_domains" as there is no more "cors" app.
Please check if other locations in code require a similar change.

@peterprochaska

Copy link
Copy Markdown
Contributor

Yes, i can confirm

@PVince81

Copy link
Copy Markdown
Contributor

@noveens please fix//enhance the unit tests

@PVince81

Copy link
Copy Markdown
Contributor

Looks like the HTTP_ORIGIN header is not guaranteed to be always there ?

6. mkcol_over_plain...... [Mon Aug 14 16:32:59 2017] Undefined index: HTTP_ORIGIN at /home/travis/build/owncloud/core/apps/dav/lib/Connector/Sabre/CorsPlugin.php#62

from the Travis failures.

@noveens Maybe add a isset and proper logic to fallback to if the header is no set.

@PVince81

Copy link
Copy Markdown
Contributor

backport for 10.0.4, I'd like @Peter-Prochaska to have a second look

@PVince81
PVince81 merged commit 1af53bc into master Aug 29, 2017
@PVince81
PVince81 deleted the enablingCORS branch August 29, 2017 07:28
@PVince81

Copy link
Copy Markdown
Contributor

@noveens please backport to stable10 (PR for stable10 with the same commit)

@phil-davis

Copy link
Copy Markdown
Contributor

Note: needs #28833 backported on top of the backport of this.

@SamuAlfageme

SamuAlfageme commented Aug 30, 2017

Copy link
Copy Markdown

@jesmrec I think this one might be the one behind the behavior we saw today with OAuth app; i.e. getting no-content on the /ocs endpoints' replies

EDIT: Error seems to be located in (the lack of)

$user = $this->request->server['PHP_AUTH_USER'];

(see #28860 (comment)) & likely to break on other PHP_AUTH_USER-less authentication flows (SAML?)

@jesmrec

jesmrec commented Aug 30, 2017

Copy link
Copy Markdown
Member

thanks @SamuAlfageme. I will check. The point is mobile apps that are using OAuth2 as auth method are broken with the owncloud daily master from today (30th Aug). It is caused at least for those request with "format=json" as parameter, that are being returned an empty body from the server side. I will send more info when i confirm.

CC @PVince81

@PVince81

Copy link
Copy Markdown
Contributor

@SamuAlfageme @jesmrec any detailed steps to reproduce ? Does the oauth app use the OPTIONS method ?

@PVince81

Copy link
Copy Markdown
Contributor

@SamuAlfageme @jesmrec once the problem identified, if any fixing needed, please raise a new ticket and ping @noveens

@jesmrec

jesmrec commented Aug 30, 2017

Copy link
Copy Markdown
Member

#28860

@noveens noveens mentioned this pull request Aug 30, 2017
9 tasks
@PVince81

Copy link
Copy Markdown
Contributor

Problem was fixed in #28864, good job!

@PVince81 PVince81 mentioned this pull request Sep 7, 2017
4 tasks
PVince81 pushed a commit that referenced this pull request Sep 18, 2017
[stable10] Backport of #28457 (Enabled CORS on ownCloud)
@Emil-G

Emil-G commented Feb 28, 2018

Copy link
Copy Markdown

Why is this option shown to end users if an Admin only has permissions to edit?

@PVince81

Copy link
Copy Markdown
Contributor

No, this option is editable by users only, not the admin. At some point there should be a new page where the admin can see the CORS domains set up by all users.

@Emil-G

Emil-G commented Feb 28, 2018

Copy link
Copy Markdown

@PVince81 : Well in 10.0.7 if a user attempts, they are shown this error:
Access forbidden
Logged in user must be an admin

@PVince81

Copy link
Copy Markdown
Contributor

Ouch, then it's a bug...

@lock

lock Bot commented Jul 31, 2019

Copy link
Copy Markdown

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock Bot locked as resolved and limited conversation to collaborators Jul 31, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants