Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions apps/dav/lib/Connector/Sabre/CorsPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php
/**
* @author Noveen Sachdeva <[email protected]>
*
* @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;
}
}
}
1 change: 1 addition & 0 deletions apps/dav/lib/Connector/Sabre/ServerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public function createServer($baseUri,
$server->setBaseUri($baseUri);

// Load plugins
$server->addPlugin(new \OCA\DAV\Connector\Sabre\CorsPlugin($this->userSession));
$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));
Expand Down
2 changes: 2 additions & 0 deletions apps/dav/lib/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin;
use OCA\DAV\Connector\Sabre\CommentPropertiesPlugin;
use OCA\DAV\Connector\Sabre\CopyEtagHeaderPlugin;
use OCA\DAV\Connector\Sabre\CorsPlugin;
use OCA\DAV\Connector\Sabre\DavAclPlugin;
use OCA\DAV\Connector\Sabre\DummyGetResponsePlugin;
use OCA\DAV\Connector\Sabre\FakeLockerPlugin;
Expand Down Expand Up @@ -84,6 +85,7 @@ public function __construct(IRequest $request, $baseUri) {
$this->server->addPlugin(new MaintenancePlugin($config));
$this->server->addPlugin(new ValidateRequestPlugin('dav'));
$this->server->addPlugin(new BlockLegacyClientPlugin($config));
$this->server->addPlugin(new CorsPlugin(\OC::$server->getUserSession()));
$authPlugin = new Plugin();
$authPlugin->addBackend(new PublicAuth());
$this->server->addPlugin($authPlugin);
Expand Down
3 changes: 3 additions & 0 deletions apps/provisioning_api/appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
\OC::$server->getLogger(),
\OC::$server->getTwoFactorAuthManager()
);

API::register('get', '/cloud/users', [$users, 'getUsers'], 'provisioning_api', API::SUBADMIN_AUTH);
API::register('post', '/cloud/users', [$users, 'addUser'], 'provisioning_api', API::SUBADMIN_AUTH);
API::register('get', '/cloud/users/{userid}', [$users, 'getUser'], 'provisioning_api', API::USER_AUTH);
Expand All @@ -60,6 +61,7 @@
\OC::$server->getUserSession(),
\OC::$server->getRequest()
);

API::register('get', '/cloud/groups', [$groups, 'getGroups'], 'provisioning_api', API::SUBADMIN_AUTH);
API::register('post', '/cloud/groups', [$groups, 'addGroup'], 'provisioning_api', API::SUBADMIN_AUTH);
API::register('get', '/cloud/groups/{groupid}', [$groups, 'getGroup'], 'provisioning_api', API::SUBADMIN_AUTH);
Expand All @@ -68,6 +70,7 @@

// Apps
$apps = new Apps(\OC::$server->getAppManager());

API::register('get', '/cloud/apps', [$apps, 'getApps'], 'provisioning_api', API::ADMIN_AUTH);
API::register('get', '/cloud/apps/{appid}', [$apps, 'getAppInfo'], 'provisioning_api', API::ADMIN_AUTH);
API::register('post', '/cloud/apps/{appid}', [$apps, 'enable'], 'provisioning_api', API::ADMIN_AUTH);
Expand Down
8 changes: 4 additions & 4 deletions apps/provisioning_api/lib/Users.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public function getUsers() {
}

if($offset === null) {
$offset = 0;
$offset = 0;
}

$users = [];
Expand Down Expand Up @@ -153,7 +153,7 @@ public function addUser() {
return new Result(null, 106, 'no group specified (required for subadmins)');
}
}

try {
$newUser = $this->userManager->createUser($userId, $password);
$this->logger->info('Successful addUser call with userid: '.$userId, ['app' => 'ocs_api']);
Expand Down Expand Up @@ -219,7 +219,7 @@ public function getUser($parameters) {
return new Result($data);
}

/**
/**
* edit users
*
* @param array $parameters
Expand Down Expand Up @@ -440,7 +440,7 @@ public function getUsersGroups($parameters) {
return new Result(null, 997);
}
}

}

/**
Expand Down
4 changes: 3 additions & 1 deletion core/Controller/CloudController.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public function __construct($appName, IRequest $request) {
/**
* @NoAdminRequired
* @NoCSRFRequired
* @CORS
*
* @return array
*/
Expand All @@ -49,7 +50,7 @@ public function getCapabilities() {
'string' => \OC_Util::getVersionString(),
'edition' => \OC_Util::getEditionString(),
];

$result['capabilities'] = \OC::$server->getCapabilitiesManager()->getCapabilities();

return ['data' => $result];
Expand All @@ -58,6 +59,7 @@ public function getCapabilities() {
/**
* @NoAdminRequired
* @NoCSRFRequired
* @CORS
*
* @return array
*/
Expand Down
3 changes: 2 additions & 1 deletion lib/private/AppFramework/DependencyInjection/DIContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,8 @@ public function __construct($appName, $urlParams = []){
return new CORSMiddleware(
$c['Request'],
$c['ControllerMethodReflector'],
$c['OCP\IUserSession']
$c['OCP\IUserSession'],
$c['OCP\IConfig']
);
});

Expand Down
61 changes: 23 additions & 38 deletions lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@
use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
use OC\AppFramework\Utility\ControllerMethodReflector;
use OC\Authentication\Exceptions\PasswordLoginForbiddenException;
use OC\User\Session;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
use OCP\IRequest;
use OCP\IUserSession;
use OCP\IConfig;

/**
* This middleware sets the correct CORS headers on a response if the
Expand All @@ -55,50 +56,29 @@ class CORSMiddleware extends Middleware {
private $reflector;

/**
* @var Session
* @var IUserSession
*/
private $session;

/**
* @var IConfig
*/
private $config;

/**
* @param IRequest $request
* @param ControllerMethodReflector $reflector
* @param Session $session
* @param IUserSession $session
* @param IConfig $config
*/
public function __construct(IRequest $request,
ControllerMethodReflector $reflector,
Session $session) {
IUserSession $session,
IConfig $config) {
$this->request = $request;
$this->reflector = $reflector;
$this->session = $session;
}

/**
* This is being run in normal order before the controller is being
* called which allows several modifications and checks
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
* @throws SecurityException
* @since 6.0.0
*/
public function beforeController($controller, $methodName){
// ensure that @CORS annotated API routes are not used in conjunction
// with session authentication since this enables CSRF attack vectors
if ($this->reflector->hasAnnotation('CORS') &&
!$this->reflector->hasAnnotation('PublicPage')) {
$user = $this->request->server['PHP_AUTH_USER'];
$pass = $this->request->server['PHP_AUTH_PW'];

$this->session->logout();
try {
if (!$this->session->logClientIn($user, $pass, $this->request)) {
throw new SecurityException('CORS requires basic auth', Http::STATUS_UNAUTHORIZED);
}
} catch (PasswordLoginForbiddenException $ex) {
throw new SecurityException('Password login forbidden, use token instead', Http::STATUS_UNAUTHORIZED);
}
}
$this->config = $config;
}

/**
Expand All @@ -114,9 +94,17 @@ public function beforeController($controller, $methodName){
*/
public function afterController($controller, $methodName, Response $response){
// only react if its a CORS request and if the request sends origin and
$userId = null;
if (!is_null($this->session->getUser())) {
$userId = $this->session->getUser()->getUID();
}

if($this->request->getHeader("Origin") !== null &&
$this->reflector->hasAnnotation('CORS') && !is_null($userId)) {

if(isset($this->request->server['HTTP_ORIGIN']) &&
$this->reflector->hasAnnotation('CORS')) {
$requesterDomain = $this->request->getHeader("Origin");

\OC_Response::setCorsHeaders($userId, $requesterDomain, $response, $this->config);

// allow credentials headers must not be true or CSRF is possible
// otherwise
Expand All @@ -128,9 +116,6 @@ public function afterController($controller, $methodName, Response $response){
throw new SecurityException($msg);
}
}

$origin = $this->request->server['HTTP_ORIGIN'];
$response->addHeader('Access-Control-Allow-Origin', $origin);
}
return $response;
}
Expand Down
35 changes: 35 additions & 0 deletions lib/private/Route/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());

// Return since no more processing for an OPTIONS request is required
return;
} catch (ResourceNotFoundException $e) {
if (substr($url, -1) !== '/') {
// 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) {
Expand Down
Loading