diff --git a/apps/dav/lib/Connector/Sabre/CorsPlugin.php b/apps/dav/lib/Connector/Sabre/CorsPlugin.php
new file mode 100644
index 000000000000..50f1bb6607f3
--- /dev/null
+++ b/apps/dav/lib/Connector/Sabre/CorsPlugin.php
@@ -0,0 +1,109 @@
+
+ *
+ * @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
+ *
+ */
+
+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;
+ }
+ }
+}
diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php
index b61f50548ee2..00c828095f9f 100644
--- a/apps/dav/lib/Connector/Sabre/ServerFactory.php
+++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php
@@ -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));
diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php
index 6f5b140e1dc3..9ab1a1cddf37 100644
--- a/apps/dav/lib/Server.php
+++ b/apps/dav/lib/Server.php
@@ -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;
@@ -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);
diff --git a/apps/provisioning_api/appinfo/routes.php b/apps/provisioning_api/appinfo/routes.php
index dc588e951a56..4bbd8abca9aa 100644
--- a/apps/provisioning_api/appinfo/routes.php
+++ b/apps/provisioning_api/appinfo/routes.php
@@ -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);
@@ -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);
@@ -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);
diff --git a/apps/provisioning_api/lib/Users.php b/apps/provisioning_api/lib/Users.php
index 9c31498a2b2f..80346b39bae3 100644
--- a/apps/provisioning_api/lib/Users.php
+++ b/apps/provisioning_api/lib/Users.php
@@ -100,7 +100,7 @@ public function getUsers() {
}
if($offset === null) {
- $offset = 0;
+ $offset = 0;
}
$users = [];
@@ -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']);
@@ -219,7 +219,7 @@ public function getUser($parameters) {
return new Result($data);
}
- /**
+ /**
* edit users
*
* @param array $parameters
@@ -440,7 +440,7 @@ public function getUsersGroups($parameters) {
return new Result(null, 997);
}
}
-
+
}
/**
diff --git a/core/Controller/CloudController.php b/core/Controller/CloudController.php
index 21edae5ad516..9cbdd2899100 100644
--- a/core/Controller/CloudController.php
+++ b/core/Controller/CloudController.php
@@ -36,6 +36,7 @@ public function __construct($appName, IRequest $request) {
/**
* @NoAdminRequired
* @NoCSRFRequired
+ * @CORS
*
* @return array
*/
@@ -49,7 +50,7 @@ public function getCapabilities() {
'string' => \OC_Util::getVersionString(),
'edition' => \OC_Util::getEditionString(),
];
-
+
$result['capabilities'] = \OC::$server->getCapabilitiesManager()->getCapabilities();
return ['data' => $result];
@@ -58,6 +59,7 @@ public function getCapabilities() {
/**
* @NoAdminRequired
* @NoCSRFRequired
+ * @CORS
*
* @return array
*/
diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php
index 5a73a10a82af..fe9d167326c2 100644
--- a/lib/private/AppFramework/DependencyInjection/DIContainer.php
+++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php
@@ -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']
);
});
diff --git a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
index 576b8c356a59..26368a648992 100644
--- a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
@@ -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
@@ -55,21 +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->config = $config;
}
/**
@@ -114,9 +123,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(isset($this->request->server['HTTP_ORIGIN']) &&
- $this->reflector->hasAnnotation('CORS')) {
+ if($this->request->getHeader("Origin") !== null &&
+ $this->reflector->hasAnnotation('CORS') && !is_null($userId)) {
+
+ $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
@@ -128,9 +145,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;
}
diff --git a/lib/private/Route/Router.php b/lib/private/Route/Router.php
index 258bff72559f..71a66ef31765 100644
--- a/lib/private/Route/Router.php
+++ b/lib/private/Route/Router.php
@@ -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) {
diff --git a/lib/private/Settings/SettingsManager.php b/lib/private/Settings/SettingsManager.php
index c4fee9228785..3724eb7390a2 100644
--- a/lib/private/Settings/SettingsManager.php
+++ b/lib/private/Settings/SettingsManager.php
@@ -46,6 +46,7 @@
use OC\Settings\Panels\Personal\Clients;
use OC\Settings\Panels\Personal\Version;
use OC\Settings\Panels\Personal\Tokens;
+use OC\Settings\Panels\Personal\Cors;
use OC\Settings\Panels\Personal\Quota;
use OC\Settings\Panels\Admin\BackgroundJobs;
use OC\Settings\Panels\Admin\Certificates;
@@ -246,6 +247,7 @@ private function getBuiltInPanels($type) {
LegacyPersonal::class,
Version::class,
Tokens::class,
+ Cors::class,
Quota::class
];
}
@@ -268,6 +270,10 @@ public function getBuiltInPanel($className) {
Clients::class => new Clients($this->config, $this->defaults),
Version::class => new Version(),
Tokens::class => new Tokens(),
+ Cors::class => new Cors(
+ $this->userSession,
+ $this->urlGenerator,
+ $this->config),
Quota::class => new Quota($this->helper),
// Admin
BackgroundJobs::class => new BackgroundJobs($this->config),
diff --git a/lib/private/legacy/api.php b/lib/private/legacy/api.php
index 80993630f543..6436d8479824 100644
--- a/lib/private/legacy/api.php
+++ b/lib/private/legacy/api.php
@@ -115,11 +115,12 @@ class OC_API {
* @param int $authLevel the level of authentication required for the call
* @param array $defaults
* @param array $requirements
+ * @param boolean $cors whether to enable cors for this route
*/
public static function register($method, $url, $action, $app,
$authLevel = API::USER_AUTH,
$defaults = [],
- $requirements = []) {
+ $requirements = [], $cors = true) {
$name = strtolower($method).$url;
$name = str_replace(['/', '{', '}'], '_', $name);
if(!isset(self::$actions[$name])) {
@@ -133,7 +134,7 @@ public static function register($method, $url, $action, $app,
self::$actions[$name] = [];
OC::$server->getRouter()->useCollection($oldCollection);
}
- self::$actions[$name][] = ['app' => $app, 'action' => $action, 'authlevel' => $authLevel];
+ self::$actions[$name][] = ['app' => $app, 'action' => $action, 'authlevel' => $authLevel, 'cors' => $cors];
}
/**
@@ -179,6 +180,16 @@ public static function call($parameters) {
];
}
$response = self::mergeResponses($responses);
+
+ // If CORS is set to active for some method, try to add CORS headers
+ if (self::$actions[$name][0]['cors'] &&
+ !is_null(\OC::$server->getUserSession()->getUser()) &&
+ !is_null(\OC::$server->getRequest()->getHeader('Origin'))) {
+ $requesterDomain = \OC::$server->getRequest()->getHeader('Origin');
+ $userId = \OC::$server->getUserSession()->getUser()->getUID();
+ $response = \OC_Response::setCorsHeaders($userId, $requesterDomain, $response);
+ }
+
$format = self::requestedFormat();
if (self::$logoutRequired) {
\OC::$server->getUserSession()->logout();
diff --git a/lib/private/legacy/response.php b/lib/private/legacy/response.php
index bb6b53e90782..485240c4fc8d 100644
--- a/lib/private/legacy/response.php
+++ b/lib/private/legacy/response.php
@@ -252,7 +252,7 @@ public static function addSecurityHeaders() {
. 'frame-src *; '
. 'img-src * data: blob:; '
. 'font-src \'self\' data:; '
- . 'media-src *; '
+ . 'media-src *; '
. 'connect-src *';
header('Content-Security-Policy:' . $policy);
@@ -268,4 +268,80 @@ public static function addSecurityHeaders() {
}
}
+ /**
+ * This function adds the CORS headers if the requester domain is white-listed
+ *
+ * @param string $userId
+ * @param string $domain
+ * @param Sabre\HTTP\ResponseInterface $response
+ * @param \OCP\IConfig $config
+ * @param Array $headers
+ *
+ * Format of $headers:
+ * Array [
+ * "Access-Control-Allow-Headers": ["a", "b", "c"],
+ * "Access-Control-Allow-Origin": ["a", "b", "c"],
+ * "Access-Control-Allow-Methods": ["a", "b", "c"]
+ * ]
+ *
+ * @return Sabre\HTTP\ResponseInterface $response
+ */
+ public static function setCorsHeaders($userId, $domain, $response, $config = null, $headers = []) {
+ if (is_null($config)) {
+ $config = \OC::$server->getConfig();
+ }
+ $allowedDomains = json_decode($config->getUserValue($userId, 'core', 'domains'));
+ if (in_array($domain, $allowedDomains)) {
+ // TODO: infer allowed verbs from existing known routes
+ $allHeaders['Access-Control-Allow-Headers'] = ["authorization", "OCS-APIREQUEST", "Origin", "X-Requested-With", "Content-Type", "Access-Control-Allow-Origin"];
+ $allHeaders['Access-Control-Allow-Origin'] = [$domain];
+ $allHeaders['Access-Control-Allow-Methods'] =["GET", "OPTIONS", "POST", "PUT", "DELETE", "MKCOL", "PROPFIND", "PATCH", "PROPPATCH", "REPORT"];
+
+ foreach ($headers as $key => $value) {
+ if (array_key_exists($key, $allHeaders)) {
+ $allHeaders[$key] = array_merge($allHeaders[$key], $value);
+ }
+ }
+
+ foreach ($allHeaders as $key => $value) {
+ $response->addHeader($key, implode(",", $value));
+ }
+ }
+ return $response;
+ }
+
+ /**
+ * This function adds the CORS headers for all domains
+ *
+ * @param Sabre\HTTP\ResponseInterface $response
+ * @param Array $headers
+ *
+ * Format of $headers:
+ * Array [
+ * "Access-Control-Allow-Headers": ["a", "b", "c"],
+ * "Access-Control-Allow-Origin": ["a", "b", "c"],
+ * "Access-Control-Allow-Methods": ["a", "b", "c"]
+ * ]
+ *
+ * @return Sabre\HTTP\ResponseInterface $response
+ */
+ public static function setOptionsRequestHeaders($response, $headers = []) {
+ // TODO: infer allowed verbs from existing known routes
+ $allHeaders['Access-Control-Allow-Headers'] = ["authorization", "OCS-APIREQUEST", "Origin", "X-Requested-With", "Content-Type", "Access-Control-Allow-Origin"];
+ $allHeaders['Access-Control-Allow-Origin'] = ['*'];
+ $allHeaders['Access-Control-Allow-Methods'] =["GET", "OPTIONS", "POST", "PUT", "DELETE", "MKCOL", "PROPFIND", "PATCH", "PROPPATCH", "REPORT"];
+
+ foreach ($headers as $key => $value) {
+ if (array_key_exists($key, $allHeaders)) {
+ $allHeaders[$key] = array_merge($allHeaders[$key], $value);
+ }
+ }
+
+ foreach ($allHeaders as $key => $value) {
+ $response->addHeader($key, implode(",", $value));
+ }
+
+ return $response;
+ }
+
}
diff --git a/lib/public/API.php b/lib/public/API.php
index 9062d458457d..a29fcfdb1769 100644
--- a/lib/public/API.php
+++ b/lib/public/API.php
@@ -69,8 +69,8 @@ class API {
* @since 5.0.0
*/
public static function register($method, $url, $action, $app, $authLevel = self::USER_AUTH,
- $defaults = [], $requirements = []){
- \OC_API::register($method, $url, $action, $app, $authLevel, $defaults, $requirements);
+ $defaults = [], $requirements = [], $cors = true){
+ \OC_API::register($method, $url, $action, $app, $authLevel, $defaults, $requirements, $cors);
}
}
diff --git a/ocs/v1.php b/ocs/v1.php
index 2573568f1be6..f9cc4dc640ba 100644
--- a/ocs/v1.php
+++ b/ocs/v1.php
@@ -105,4 +105,3 @@
} catch (Exception $ex) {
OC_API::respond($ex->getResult(), OC_API::requestedFormat());
}
-
diff --git a/settings/Application.php b/settings/Application.php
index 41df506135e2..ff30ee77e2f3 100644
--- a/settings/Application.php
+++ b/settings/Application.php
@@ -34,6 +34,7 @@
use OC\Files\View;
use OC\Server;
use OC\AppFramework\Utility\TimeFactory;
+use OC\Settings\Controller\CorsController;
use OC\Settings\Controller\SettingsPageController;
use OC\Settings\Controller\AppSettingsController;
use OC\Settings\Controller\AuthSettingsController;
diff --git a/settings/Controller/CorsController.php b/settings/Controller/CorsController.php
new file mode 100644
index 000000000000..5dffa6b7de1e
--- /dev/null
+++ b/settings/Controller/CorsController.php
@@ -0,0 +1,158 @@
+
+ */
+
+namespace OC\Settings\Controller;
+
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\RedirectResponse;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\ILogger;
+use OCP\IRequest;
+use OCP\IURLGenerator;
+use OCP\IConfig;
+use OCP\IUserSession;
+
+/**
+ * This controller is responsible for managing white-listed domains for CORS
+ *
+ * @package OC\Settings\Controller
+ */
+class CorsController extends Controller {
+
+ /** @var ILogger */
+ private $logger;
+
+ /** @var IURLGenerator */
+ private $urlGenerator;
+
+ /** @var string */
+ private $userId;
+
+ /** @var IConfig */
+ private $config;
+
+ /**
+ * CorsController constructor.
+ *
+ * @param string $AppName The app's name.
+ * @param IRequest $request The request.
+ * @param IUserSession $userSession Logged in user's session
+ * @param ILogger $logger The logger.
+ * @param IURLGenerator $urlGenerator Use for url generation
+ * @param IConfig $config
+ */
+ public function __construct($AppName, IRequest $request,
+ IUserSession $userSession,
+ ILogger $logger,
+ IURLGenerator $urlGenerator,
+ IConfig $config) {
+ parent::__construct($AppName, $request);
+
+ $this->AppName = $AppName;
+ $this->config = $config;
+ $this->userId = $userSession->getUser()->getUID();
+ $this->logger = $logger;
+ $this->urlGenerator = $urlGenerator;
+ }
+
+ /**
+ * Returns a redirect response
+ * @return RedirectResponse
+ */
+ private function getRedirectResponse() {
+ return new RedirectResponse(
+ $this->urlGenerator->linkToRouteAbsolute(
+ 'settings.SettingsPage.getPersonal',
+ ['sectionid' => 'security']
+ ) . '#cors'
+ );
+ }
+
+ /**
+ * Gets all White-listed domains
+ *
+ * @return JSONResponse All the White-listed domains
+ */
+ public function getDomains() {
+ $userId = $this->userId;
+
+ if (empty($this->config->getUserValue($userId, 'core', 'domains'))) {
+ $domains = [];
+ } else {
+ $domains = json_decode($this->config->getUserValue($userId, 'core', 'domains'));
+ }
+
+ return new JSONResponse($domains);
+ }
+
+ /**
+ * WhiteLists a domain for CORS
+ *
+ * @param string $domain The domain to whitelist
+ * @return RedirectResponse Redirection to the settings page.
+ */
+ public function addDomain($domain) {
+ if (!isset($domain) || !self::isValidUrl($domain)) {
+ return $this->getRedirectResponse();
+ }
+
+ $userId = $this->userId;
+ $domains = json_decode($this->config->getUserValue($userId, 'core', 'domains'));
+ $domains = array_filter($domains);
+ array_push($domains, $domain);
+
+ // In case same domain is added
+ $domains = array_unique($domains);
+
+ // Store as comma seperated string
+ $domainsString = json_encode($domains);
+
+ $this->config->setUserValue($userId, 'core', 'domains', $domainsString);
+ $this->logger->debug("The domain {$domain} has been white-listed.", ['app' => $this->appName]);
+
+ return $this->getRedirectResponse();
+ }
+
+ /**
+ * Removes a WhiteListed Domain
+ *
+ * @param string $domain Domain to remove
+ * @return RedirectResponse Redirection to the settings page.
+ */
+ public function removeDomain($id) {
+ $userId = $this->userId;
+ $domains = json_decode($this->config->getUserValue($userId, 'core', 'domains'));
+
+ if ($id >= 0 && $id < count($domains)) {
+ unset($domains[$id]);
+ $this->config->setUserValue($userId, 'core', 'domains', json_encode($domains));
+ }
+
+ return $this->getRedirectResponse();
+ }
+
+ /**
+ * Checks whether a URL is valid
+ * @param string $url URL to check
+ * @return boolean whether URL is valid
+ */
+ private static function isValidUrl($url) {
+ return (filter_var($url, FILTER_VALIDATE_URL) !== false);
+ }
+
+}
diff --git a/settings/Panels/Personal/Cors.php b/settings/Panels/Personal/Cors.php
new file mode 100644
index 000000000000..9b67cb007538
--- /dev/null
+++ b/settings/Panels/Personal/Cors.php
@@ -0,0 +1,75 @@
+
+ */
+
+namespace OC\Settings\Panels\Personal;
+
+use OCP\IURLGenerator;
+use OCP\IUserSession;
+use OCP\IConfig;
+use OCP\Settings\ISettings;
+use OCP\Template;
+
+class Cors implements ISettings {
+
+ /**
+ * @var IUserSession
+ */
+ protected $userSession;
+
+ /**
+ * @var IURLGenerator
+ */
+ protected $urlGenerator;
+
+ /** @var IConfig */
+ private $config;
+
+ public function __construct(
+ IUserSession $userSession,
+ IURLGenerator $urlGenerator,
+ IConfig $config) {
+
+ $this->config = $config;
+ $this->userSession = $userSession;
+ $this->urlGenerator = $urlGenerator;
+ }
+
+ public function getSectionID() {
+ return 'security';
+ }
+
+ /**
+ * @return Template
+ */
+ public function getPanel() {
+ $userId = $this->userSession->getUser()->getUID();
+ $domains = json_decode($this->config->getUserValue($userId, 'core', 'domains'));
+
+ $t = new Template('settings', 'panels/personal/cors');
+ $t->assign('user_id', $userId);
+ $t->assign('domains', $domains);
+ $t->assign('urlGenerator', $this->urlGenerator);
+ return $t;
+ }
+
+
+ public function getPriority() {
+ return 20;
+ }
+
+}
diff --git a/settings/js/panels/cors.js b/settings/js/panels/cors.js
new file mode 100644
index 000000000000..af6c1aa4171e
--- /dev/null
+++ b/settings/js/panels/cors.js
@@ -0,0 +1,33 @@
+$(document).ready(function () {
+ $('.removeDomainButton').on('click', function () {
+ var id = $(this).attr('data-id');
+ var confirmText = $(this).attr('data-confirm');
+ var token = OC.requestToken;
+ var $el = $(this);
+
+ OC.dialogs.confirm(
+ t('settings', confirmText), t('settings','CORS'),
+ function (result) {
+ if (result) {
+ $.ajax({
+ type: 'DELETE',
+ url: OC.generateUrl('/settings/domains/{id}', {id: id}),
+ data: {
+ requesttoken: token
+ }
+ }).success(function() {
+ var numDomains = $("#cors .grid tbody tr").length;
+ if ($el.closest('tr').length === 1 && numDomains === 1) {
+ // Means only one domain row remains and that is to be deleted
+ // Show the No domains text
+ $("#noDomains").text("No Domains.");
+ // Remove the domain listing table
+ $("#cors .grid").remove();
+ }
+ $el.closest('tr').remove();
+ });
+ }
+ }, true
+ );
+ });
+});
diff --git a/settings/routes.php b/settings/routes.php
index 46d3d5643a0d..c54892fdfb7b 100644
--- a/settings/routes.php
+++ b/settings/routes.php
@@ -63,6 +63,9 @@
['name' => 'SettingsPage#getPersonal', 'url' => '/settings/personal', 'verb' => 'GET'],
['name' => 'SettingsPage#getAdmin', 'url' => '/settings/admin', 'verb' => 'GET'],
['name' => 'Users#changeMail', 'url' => '/settings/mailaddress/change/{token}/{userId}', 'verb' => 'GET'],
+ ['name' => 'Cors#getDomains', 'url' => '/settings/domains', 'verb' => 'GET'],
+ ['name' => 'Cors#addDomain', 'url' => '/settings/domains', 'verb' => 'POST'],
+ ['name' => 'Cors#removeDomain', 'url' => '/settings/domains/{id}', 'verb' => 'DELETE']
]
]);
diff --git a/settings/templates/panels/personal/cors.php b/settings/templates/panels/personal/cors.php
new file mode 100644
index 000000000000..8a45d570d043
--- /dev/null
+++ b/settings/templates/panels/personal/cors.php
@@ -0,0 +1,60 @@
+
+ */
+
+script('settings', 'panels/cors');
+
+?>
+
+
+
t('CORS')); ?>
+
+
t('White-listed Domains')); ?>
+
+ t('No Domains.'));
+ } ?>
+
+
+
+
+
+
+
t('Add Domain')); ?>
+
+
diff --git a/tests/Settings/Controller/CorsControllerTest.php b/tests/Settings/Controller/CorsControllerTest.php
new file mode 100644
index 000000000000..925435012615
--- /dev/null
+++ b/tests/Settings/Controller/CorsControllerTest.php
@@ -0,0 +1,179 @@
+
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @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
+ *
+ */
+
+namespace Tests\Settings\Controller;
+
+use OC\Settings\Controller\CorsController;
+use OCP\AppFramework\Http\JSONResponse;
+use Test\TestCase;
+use OCP\IRequest;
+use OCP\IConfig;
+use OCP\ILogger;
+use OCP\IUser;
+use OCP\IUserSession;
+use OCP\IURLGenerator;
+use OCP\AppFramework\Http\RedirectResponse;
+
+/**
+ * Class CorsControllerTest
+ *
+ * @package Tests\Settings\Controller
+ */
+class CorsControllerTest extends TestCase {
+ /** @var CorsController */
+ private $corsController;
+
+ /** @var IRequest */
+ private $request;
+
+ /** @var ILogger */
+ private $logger;
+
+ /** @var IURLGenerator */
+ private $urlGenerator;
+
+ /** @var IConfig */
+ private $config;
+
+ /** @var IUser */
+ private $userSession;
+
+ public function setUp() {
+ parent::setUp();
+
+ $this->request = $this->createMock(IRequest::class);
+ $this->logger = $this->createMock(ILogger::class);
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
+
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')->willReturn('user');
+
+ $this->userSession = $this->createMock(IUserSession::class);
+ $this->userSession->method('getUser')->willReturn($user);
+
+ $this->config = $this->createMock(IConfig::class);
+ $this->config->method('getUserValue')->willReturn('["http:\/\/www.test.com"]');
+ $this->config->method('setUserValue')->willReturn(true);
+
+ $this->corsController = new CorsController(
+ 'core',
+ $this->request,
+ $this->userSession,
+ $this->logger,
+ $this->urlGenerator,
+ $this->config
+ );
+ }
+
+ public function testAddInvalidDomain() {
+ // Since this domain is invalid,
+ // the success message that the domain is white-listed, wouldn't be triggered
+ $this->logger
+ ->expects($this->never())
+ ->method('debug');
+
+ $this->config
+ ->expects($this->never())
+ ->method("setUserValue");
+
+ $response = $this->corsController->addDomain("non-valid domain");
+
+ $expectedResponse = new RedirectResponse(
+ $this->urlGenerator->linkToRouteAbsolute(
+ 'settings.SettingsPage.getPersonal',
+ ['sectionid' => 'security']
+ ) . '#cors'
+ );
+
+ $this->assertEquals($response, $expectedResponse);
+ }
+
+ public function testAddValidDomain() {
+ // Since this domain is valid,
+ // the success message that the domain is white-listed, would be triggered exactly once
+ $this->logger
+ ->expects($this->once())
+ ->method('debug');
+
+ $this->config
+ ->expects($this->once())
+ ->method("setUserValue");
+
+ $response = $this->corsController->addDomain("http://www.test1.com");
+
+ $expectedResponse = new RedirectResponse(
+ $this->urlGenerator->linkToRouteAbsolute(
+ 'settings.SettingsPage.getPersonal',
+ ['sectionid' => 'security']
+ ) . '#cors'
+ );
+
+ $this->assertEquals($response, $expectedResponse);
+ }
+
+ public function testRemoveInvalidDomain() {
+ // Since this domain id passed is invalid,
+ // the error message that invalid domain ID passed, would be triggered
+ $this->config
+ ->expects($this->never())
+ ->method("setUserValue");
+
+ // The argument for removing domain is the ID of the white-listed domain
+ // and not the domain itself
+ $response = $this->corsController->removeDomain(100);
+
+ $expectedResponse = new RedirectResponse(
+ $this->urlGenerator->linkToRouteAbsolute(
+ 'settings.SettingsPage.getPersonal',
+ ['sectionid' => 'security']
+ ) . '#cors'
+ );
+
+ $this->assertEquals($response, $expectedResponse);
+ }
+
+ public function testRemoveValidDomain() {
+ // Since this domain-ID is valid,
+ // the error message that invalid domain ID passed, would never be triggered
+ $this->config
+ ->expects($this->once())
+ ->method("setUserValue");
+
+ // The argument for removing domain is the ID of the white-listed domain
+ // and not the domain itself
+ $response = $this->corsController->removeDomain(0);
+
+ $expectedResponse = new RedirectResponse(
+ $this->urlGenerator->linkToRouteAbsolute(
+ 'settings.SettingsPage.getPersonal',
+ ['sectionid' => 'security']
+ ) . '#cors'
+ );
+
+ $this->assertEquals($response, $expectedResponse);
+ }
+
+ public function testGetDomains() {
+ // since their are no domains, empty JSON response will be sent back
+ $expected = new JSONResponse(["http://www.test.com"]);
+ $this->assertEquals($expected, $this->corsController->getDomains());
+ }
+}
diff --git a/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php
index 324d541e0340..1bb9e2b344eb 100644
--- a/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php
+++ b/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php
@@ -18,8 +18,14 @@
use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
+use OCP\IUserSession;
+use OCP\IUser;
+use OCP\IConfig;
+/**
+ * Class CORSMiddlewareTest
+ */
class CORSMiddlewareTest extends \Test\TestCase {
private $reflector;
@@ -27,10 +33,23 @@ class CORSMiddlewareTest extends \Test\TestCase {
protected function setUp() {
parent::setUp();
+
+ $this->config = $this->createMock(IConfig::class);
+ $this->config->method('getUserValue')->willReturn('["http:\/\/www.test.com"]');
+ $this->config->method('setUserValue')->willReturn(true);
+
$this->reflector = new ControllerMethodReflector();
+
$this->session = $this->getMockBuilder('\OC\User\Session')
->disableOriginalConstructor()
->getMock();
+
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')->willReturn('user');
+ $userSession = $this->createMock(IUserSession::class);
+ $userSession->method('getUser')->willReturn($user);
+
+ $this->fakeSession = $userSession;
}
/**
@@ -40,18 +59,24 @@ public function testSetCORSAPIHeader() {
$request = new Request(
[
'server' => [
- 'HTTP_ORIGIN' => 'test'
+ 'HTTP_ORIGIN' => 'http://www.test.com'
]
],
$this->createMock('\OCP\Security\ISecureRandom'),
- $this->createMock('\OCP\IConfig')
+ $this->config
);
+
$this->reflector->reflect($this, __FUNCTION__);
- $middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+ $middleware = new CORSMiddleware(
+ $request,
+ $this->reflector,
+ $this->fakeSession,
+ $this->config
+ );
$response = $middleware->afterController($this, __FUNCTION__, new Response());
$headers = $response->getHeaders();
- $this->assertEquals('test', $headers['Access-Control-Allow-Origin']);
+ $this->assertEquals('http://www.test.com', $headers['Access-Control-Allow-Origin']);
}
@@ -65,7 +90,12 @@ public function testNoAnnotationNoCORSHEADER() {
$this->createMock('\OCP\Security\ISecureRandom'),
$this->createMock('\OCP\IConfig')
);
- $middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+ $middleware = new CORSMiddleware(
+ $request,
+ $this->reflector,
+ $this->fakeSession,
+ $this->config
+ );
$response = $middleware->afterController($this, __FUNCTION__, new Response());
$headers = $response->getHeaders();
@@ -83,7 +113,12 @@ public function testNoOriginHeaderNoCORSHEADER() {
$this->createMock('\OCP\IConfig')
);
$this->reflector->reflect($this, __FUNCTION__);
- $middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+ $middleware = new CORSMiddleware(
+ $request,
+ $this->reflector,
+ $this->fakeSession,
+ $this->config
+ );
$response = $middleware->afterController($this, __FUNCTION__, new Response());
$headers = $response->getHeaders();
@@ -99,14 +134,19 @@ public function testCorsIgnoredIfWithCredentialsHeaderPresent() {
$request = new Request(
[
'server' => [
- 'HTTP_ORIGIN' => 'test'
+ 'HTTP_ORIGIN' => 'http://www.test.com',
]
],
$this->createMock('\OCP\Security\ISecureRandom'),
$this->createMock('\OCP\IConfig')
);
$this->reflector->reflect($this, __FUNCTION__);
- $middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+ $middleware = new CORSMiddleware(
+ $request,
+ $this->reflector,
+ $this->fakeSession,
+ $this->config
+ );
$response = new Response();
$response->addHeader('AcCess-control-Allow-Credentials ', 'TRUE');
@@ -124,7 +164,12 @@ public function testNoCORSShouldAllowCookieAuth() {
$this->createMock('\OCP\IConfig')
);
$this->reflector->reflect($this, __FUNCTION__);
- $middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+ $middleware = new CORSMiddleware(
+ $request,
+ $this->reflector,
+ $this->fakeSession,
+ $this->config
+ );
$this->session->expects($this->never())
->method('logout');
$this->session->expects($this->never())
@@ -146,7 +191,7 @@ public function testCORSShouldRelogin() {
'PHP_AUTH_PW' => 'pass'
]],
$this->createMock('\OCP\Security\ISecureRandom'),
- $this->createMock('\OCP\IConfig')
+ $this->config
);
$this->session->expects($this->once())
->method('logout');
@@ -155,7 +200,12 @@ public function testCORSShouldRelogin() {
->with($this->equalTo('user'), $this->equalTo('pass'))
->will($this->returnValue(true));
$this->reflector->reflect($this, __FUNCTION__);
- $middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+ $middleware = new CORSMiddleware(
+ $request,
+ $this->reflector,
+ $this->session,
+ $this->config
+ );
$middleware->beforeController($this, __FUNCTION__, new Response());
}
@@ -180,7 +230,12 @@ public function testCORSShouldFailIfPasswordLoginIsForbidden() {
->with($this->equalTo('user'), $this->equalTo('pass'))
->will($this->throwException(new \OC\Authentication\Exceptions\PasswordLoginForbiddenException));
$this->reflector->reflect($this, __FUNCTION__);
- $middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+ $middleware = new CORSMiddleware(
+ $request,
+ $this->reflector,
+ $this->session,
+ $this->config
+ );
$middleware->beforeController($this, __FUNCTION__, new Response());
}
@@ -205,7 +260,12 @@ public function testCORSShouldNotAllowCookieAuth() {
->with($this->equalTo('user'), $this->equalTo('pass'))
->will($this->returnValue(false));
$this->reflector->reflect($this, __FUNCTION__);
- $middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+ $middleware = new CORSMiddleware(
+ $request,
+ $this->reflector,
+ $this->session,
+ $this->config
+ );
$middleware->beforeController($this, __FUNCTION__, new Response());
}
@@ -219,7 +279,12 @@ public function testAfterExceptionWithSecurityExceptionNoStatus() {
$this->createMock('\OCP\Security\ISecureRandom'),
$this->createMock('\OCP\IConfig')
);
- $middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+ $middleware = new CORSMiddleware(
+ $request,
+ $this->reflector,
+ $this->fakeSession,
+ $this->config
+ );
$response = $middleware->afterException($this, __FUNCTION__, new SecurityException('A security exception'));
$expected = new JSONResponse(['message' => 'A security exception'], 500);
@@ -235,7 +300,12 @@ public function testAfterExceptionWithSecurityExceptionWithStatus() {
$this->createMock('\OCP\Security\ISecureRandom'),
$this->createMock('\OCP\IConfig')
);
- $middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+ $middleware = new CORSMiddleware(
+ $request,
+ $this->reflector,
+ $this->fakeSession,
+ $this->config
+ );
$response = $middleware->afterException($this, __FUNCTION__, new SecurityException('A security exception', 501));
$expected = new JSONResponse(['message' => 'A security exception'], 501);
@@ -255,7 +325,12 @@ public function testAfterExceptionWithRegularException() {
$this->createMock('\OCP\Security\ISecureRandom'),
$this->createMock('\OCP\IConfig')
);
- $middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+ $middleware = new CORSMiddleware(
+ $request,
+ $this->reflector,
+ $this->fakeSession,
+ $this->config
+ );
$middleware->afterException($this, __FUNCTION__, new \Exception('A regular exception'));
}