-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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 CORS on OCS and Webdav APIs + domain whitelisting #28457
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
<?php | ||
/** | ||
* @author Noveen Sachdeva <noveen.sachdeva@research.iiit.ac.in> | ||
* | ||
* @copyright Copyright (c) 2017, ownCloud GmbH | ||
* @license AGPL-3.0 | ||
* | ||
* This code is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License, version 3, | ||
* as published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License, version 3, | ||
* along with this program. If not, see <http://www.gnu.org/licenses/> | ||
* | ||
*/ | ||
|
||
namespace OCA\DAV\Connector\Sabre; | ||
|
||
use Sabre\HTTP\ResponseInterface; | ||
use Sabre\HTTP\RequestInterface; | ||
|
||
/** | ||
* Class CorsPlugin is a plugin which adds CORS headers to the responses | ||
*/ | ||
class CorsPlugin extends \Sabre\DAV\ServerPlugin { | ||
|
||
/** | ||
* Reference to main server object | ||
* | ||
* @var \Sabre\DAV\Server | ||
*/ | ||
private $server; | ||
|
||
/** | ||
* Reference to logged in user's session | ||
* | ||
* @var \OCP\IUserSession | ||
*/ | ||
private $userSession; | ||
|
||
/** | ||
* @param \OCP\IUserSession $userSession | ||
*/ | ||
public function __construct(\OCP\IUserSession $userSession) { | ||
$this->userSession = $userSession; | ||
$this->extraHeaders['Access-Control-Allow-Headers'] = ["X-OC-Mtime", "OC-Checksum", "OC-Total-Length", "Depth", "Destination", "Overwrite"]; | ||
$this->extraHeaders['Access-Control-Allow-Methods'] = ["MOVE", "COPY"]; | ||
} | ||
|
||
/** | ||
* This initializes the plugin. | ||
* | ||
* This function is called by \Sabre\DAV\Server, after | ||
* addPlugin is called. | ||
* | ||
* This method should set up the required event subscriptions. | ||
* | ||
* @param \Sabre\DAV\Server $server | ||
* @return void | ||
*/ | ||
public function initialize(\Sabre\DAV\Server $server) { | ||
$this->server = $server; | ||
|
||
$this->server->on('beforeMethod', [$this, 'setCorsHeaders']); | ||
$this->server->on('beforeMethod:OPTIONS', [$this, 'setOptionsRequestHeaders']); | ||
} | ||
|
||
/** | ||
* This method sets the cors headers for all requests | ||
* | ||
* @return void | ||
*/ | ||
public function setCorsHeaders(RequestInterface $request, ResponseInterface $response) { | ||
if ($request->getHeader('origin') !== null && !is_null($this->userSession->getUser())) { | ||
$requesterDomain = $request->getHeader('origin'); | ||
$userId = $this->userSession->getUser()->getUID(); | ||
$response = \OC_Response::setCorsHeaders($userId, $requesterDomain, $response, null, $this->extraHeaders); | ||
} | ||
} | ||
|
||
/** | ||
* Handles the OPTIONS request | ||
* | ||
* @param RequestInterface $request | ||
* @param ResponseInterface $response | ||
* | ||
* @return false | ||
*/ | ||
public function setOptionsRequestHeaders(RequestInterface $request, ResponseInterface $response) { | ||
$authorization = $request->getHeader('Authorization'); | ||
if ($authorization === null || $authorization === '') { | ||
// Set the proper response | ||
$response->setStatus(200); | ||
$response = \OC_Response::setOptionsRequestHeaders($response, $this->extraHeaders); | ||
|
||
// Since All OPTIONS requests are unauthorized, we will have to return false from here | ||
// If we don't return false, due to no authorization, a 401-Unauthorized will be thrown | ||
// Which we don't want here | ||
// Hence this sendResponse | ||
$this->server->sapi->sendResponse($response); | ||
return false; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -100,6 +100,7 @@ public function createServer($baseUri, | |
$server->setBaseUri($baseUri); | ||
|
||
// Load plugins | ||
$server->addPlugin(new \OCA\DAV\Connector\Sabre\CorsPlugin($this->userSession)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove it and only allow Cors on the new DAV endpoint which is "remote.php/dav". For files it's "remote.php/dav/files/$user/" I assume that this is what you are targetting with your library ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How to? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there are two webdav endpoints: remote.php/webdav/path/to/file is the old one. The backend is slightly different as you might have noticed. And remote.php/dav is built to be more extensible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The library uses remote.php/webdav and not dav There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's leave it then |
||
$server->addPlugin(new \OCA\DAV\Connector\Sabre\MaintenancePlugin($this->config)); | ||
$server->addPlugin(new \OCA\DAV\Connector\Sabre\ValidateRequestPlugin('webdav')); | ||
$server->addPlugin(new \OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin($this->config)); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -271,6 +271,41 @@ public function match($url) { | |
} | ||
|
||
$matcher = new UrlMatcher($this->root, $this->context); | ||
|
||
if (\OC::$server->getRequest()->getMethod() === "OPTIONS") { | ||
try { | ||
// Checking whether the actual request (one which OPTIONS is pre-flight for) | ||
// Is actually valid | ||
$requestingMethod = \OC::$server->getRequest()->getHeader('Access-Control-Request-Method'); | ||
$tempContext = $this->context; | ||
$tempContext->setMethod($requestingMethod); | ||
$tempMatcher = new UrlMatcher($this->root, $tempContext); | ||
$parameters = $tempMatcher->match($url); | ||
|
||
// Reach here if it's valid | ||
$response = new \OC\OCS\Result(null, 100, 'OPTIONS request successful'); | ||
$response = \OC_Response::setOptionsRequestHeaders($response); | ||
\OC_API::respond($response, \OC_API::requestedFormat()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will send an OCS format response even if the route itself isn't from an OCS API call. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a need for all that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. my concern is mostly whether the response contains XML stuff from OCS. If the response is completely empty then I'm fine. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's totally empty. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. great |
||
|
||
// Return since no more processing for an OPTIONS request is required | ||
return; | ||
} catch (ResourceNotFoundException $e) { | ||
if (substr($url, -1) !== '/') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest to just rethrow, calling OPTIONS on the root path doesn't make much sense I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not calling options on the root path. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, fine |
||
// We allow links to apps/files? for backwards compatibility reasons | ||
// However, since Symfony does not allow empty route names, the route | ||
// we need to match is '/', so we need to append the '/' here. | ||
try { | ||
$parameters = $matcher->match($url . '/'); | ||
} catch (ResourceNotFoundException $newException) { | ||
// If we still didn't match a route, we throw the original exception | ||
throw $e; | ||
} | ||
} else { | ||
throw $e; | ||
} | ||
} | ||
} | ||
|
||
try { | ||
$parameters = $matcher->match($url); | ||
} catch (ResourceNotFoundException $e) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just like this. But use
$response
for setting the headers, not\OC_Response
as there is a risk of framework interference. Sabre has its own way to buffer the headers until the last moment.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't add headers using the
$response->addHeader
here, because all OPTIONS requests are unauthorized, I'll have toreturn false
from this method.If I don't return, I'll get a 401-Unauthorized
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Response and unauthorized shouldn't matter. Whatever you set on
$response
, Sabre should set into the response even if you return false.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you debug into the Sabre code to find out what it does ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@PVince81 ,
Can you check out my new approach?
I tried to find out why sabre doesn't set headers from the
$response
object after returning false, but couldn't find anything.So, I made a method (https://github.com/owncloud/core/pull/28457/files#diff-f7c504a05e1213746f92d28316dcdf95R297) which would set PHP headers from a response object.
Hence, I called that method when I was returning (https://github.com/owncloud/core/pull/28457/files#diff-99f175128bced5bdb6cf1610acbc9c33R82)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, that's already better
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a unit test for this method ? I suggest to mock RequestInterface, ResponseInterface and $sapi.