Browse Source
Merge pull request #8504 from owncloud/cors-middleware
Merge pull request #8504 from owncloud/cors-middleware
Add cors middlewareremotes/origin/ldap_group_count
9 changed files with 321 additions and 5 deletions
-
6lib/private/appframework/dependencyinjection/dicontainer.php
-
72lib/private/appframework/middleware/security/corsmiddleware.php
-
93lib/public/appframework/apicontroller.php
-
13lib/public/appframework/controller.php
-
4lib/public/appframework/http/response.php
-
55tests/lib/appframework/controller/ApiControllerTest.php
-
4tests/lib/appframework/controller/ControllerTest.php
-
2tests/lib/appframework/http/ResponseTest.php
-
77tests/lib/appframework/middleware/security/CORSMiddlewareTest.php
@ -0,0 +1,72 @@ |
|||
<?php |
|||
/** |
|||
* ownCloud - App Framework |
|||
* |
|||
* This file is licensed under the Affero General Public License version 3 or |
|||
* later. See the COPYING file. |
|||
* |
|||
* @author Bernhard Posselt <dev@bernhard-posselt.com> |
|||
* @copyright Bernhard Posselt 2014 |
|||
*/ |
|||
|
|||
namespace OC\AppFramework\Middleware\Security; |
|||
|
|||
use OC\AppFramework\Utility\MethodAnnotationReader; |
|||
use OCP\IRequest; |
|||
use OCP\AppFramework\Http\Response; |
|||
use OCP\AppFramework\Middleware; |
|||
|
|||
/** |
|||
* This middleware sets the correct CORS headers on a response if the |
|||
* controller has the @CORS annotation. This is needed for webapps that want |
|||
* to access an API and dont run on the same domain, see |
|||
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS |
|||
*/ |
|||
class CORSMiddleware extends Middleware { |
|||
|
|||
private $request; |
|||
|
|||
/** |
|||
* @param IRequest $request |
|||
*/ |
|||
public function __construct(IRequest $request) { |
|||
$this->request = $request; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* This is being run after a successful controllermethod call and allows |
|||
* the manipulation of a Response object. The middleware is run in reverse order |
|||
* |
|||
* @param Controller $controller the controller that is being called |
|||
* @param string $methodName the name of the method that will be called on |
|||
* the controller |
|||
* @param Response $response the generated response from the controller |
|||
* @return Response a Response object |
|||
*/ |
|||
public function afterController($controller, $methodName, Response $response){ |
|||
// only react if its a CORS request and if the request sends origin and
|
|||
$reflector = new MethodAnnotationReader($controller, $methodName); |
|||
|
|||
if(isset($this->request->server['HTTP_ORIGIN']) && |
|||
$reflector->hasAnnotation('CORS')) { |
|||
|
|||
// allow credentials headers must not be true or CSRF is possible
|
|||
// otherwise
|
|||
foreach($response->getHeaders() as $header => $value ) { |
|||
if(strtolower($header) === 'access-control-allow-credentials' && |
|||
strtolower(trim($value)) === 'true') { |
|||
$msg = 'Access-Control-Allow-Credentials must not be '. |
|||
'set to true in order to prevent CSRF'; |
|||
throw new SecurityException($msg); |
|||
} |
|||
} |
|||
|
|||
$origin = $this->request->server['HTTP_ORIGIN']; |
|||
$response->addHeader('Access-Control-Allow-Origin', $origin); |
|||
} |
|||
return $response; |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
<?php |
|||
/** |
|||
* ownCloud - App Framework |
|||
* |
|||
* @author Bernhard Posselt |
|||
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com |
|||
* |
|||
* This library is free software; you can redistribute it and/or |
|||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
* License as published by the Free Software Foundation; either |
|||
* version 3 of the License, or any later version. |
|||
* |
|||
* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
|
|||
/** |
|||
* Public interface of ownCloud for apps to use. |
|||
* AppFramework\Controller class |
|||
*/ |
|||
|
|||
namespace OCP\AppFramework; |
|||
|
|||
use OCP\AppFramework\Http\Response; |
|||
use OCP\IRequest; |
|||
|
|||
|
|||
/** |
|||
* Base class to inherit your controllers from that are used for RESTful APIs |
|||
*/ |
|||
abstract class ApiController extends Controller { |
|||
|
|||
private $corsMethods; |
|||
private $corsAllowedHeaders; |
|||
private $corsMaxAge; |
|||
|
|||
/** |
|||
* constructor of the controller |
|||
* @param string $appName the name of the app |
|||
* @param IRequest $request an instance of the request |
|||
* @param string $corsMethods: comma seperated string of HTTP verbs which |
|||
* should be allowed for websites or webapps when calling your API, defaults to |
|||
* 'PUT, POST, GET, DELETE, PATCH' |
|||
* @param string $corsAllowedHeaders: comma seperated string of HTTP headers |
|||
* which should be allowed for websites or webapps when calling your API, |
|||
* defaults to 'Authorization, Content-Type, Accept' |
|||
* @param int $corsMaxAge number in seconds how long a preflighted OPTIONS |
|||
* request should be cached, defaults to 1728000 seconds |
|||
*/ |
|||
public function __construct($appName, |
|||
IRequest $request, |
|||
$corsMethods='PUT, POST, GET, DELETE, PATCH', |
|||
$corsAllowedHeaders='Authorization, Content-Type, Accept', |
|||
$corsMaxAge=1728000){ |
|||
parent::__construct($appName, $request); |
|||
$this->corsMethods = $corsMethods; |
|||
$this->corsAllowedHeaders = $corsAllowedHeaders; |
|||
$this->corsMaxAge = $corsMaxAge; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* This method implements a preflighted cors response for you that you can |
|||
* link to for the options request |
|||
* |
|||
* @NoAdminRequired |
|||
* @NoCSRFRequired |
|||
* @PublicPage |
|||
*/ |
|||
public function preflightedCors() { |
|||
if(isset($this->request->server['HTTP_ORIGIN'])) { |
|||
$origin = $this->request->server['HTTP_ORIGIN']; |
|||
} else { |
|||
$origin = '*'; |
|||
} |
|||
|
|||
$response = new Response(); |
|||
$response->addHeader('Access-Control-Allow-Origin', $origin); |
|||
$response->addHeader('Access-Control-Allow-Methods', $this->corsMethods); |
|||
$response->addHeader('Access-Control-Max-Age', $this->corsMaxAge); |
|||
$response->addHeader('Access-Control-Allow-Headers', $this->corsAllowedHeaders); |
|||
$response->addHeader('Access-Control-Allow-Credentials', 'false'); |
|||
return $response; |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
<?php |
|||
|
|||
/** |
|||
* ownCloud - App Framework |
|||
* |
|||
* @author Bernhard Posselt |
|||
* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com |
|||
* |
|||
* This library is free software; you can redistribute it and/or |
|||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
* License as published by the Free Software Foundation; either |
|||
* version 3 of the License, or any later version. |
|||
* |
|||
* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
|
|||
|
|||
namespace OCP\AppFramework; |
|||
|
|||
use OC\AppFramework\Http\Request; |
|||
use OCP\AppFramework\Http\TemplateResponse; |
|||
|
|||
|
|||
class ChildApiController extends ApiController {}; |
|||
|
|||
|
|||
class ApiControllerTest extends \PHPUnit_Framework_TestCase { |
|||
|
|||
|
|||
public function testCors() { |
|||
$request = new Request( |
|||
array('server' => array('HTTP_ORIGIN' => 'test')) |
|||
); |
|||
$this->controller = new ChildApiController('app', $request, 'verbs', |
|||
'headers', 100); |
|||
|
|||
$response = $this->controller->preflightedCors(); |
|||
|
|||
$headers = $response->getHeaders(); |
|||
|
|||
$this->assertEquals('test', $headers['Access-Control-Allow-Origin']); |
|||
$this->assertEquals('verbs', $headers['Access-Control-Allow-Methods']); |
|||
$this->assertEquals('headers', $headers['Access-Control-Allow-Headers']); |
|||
$this->assertEquals('false', $headers['Access-Control-Allow-Credentials']); |
|||
$this->assertEquals(100, $headers['Access-Control-Max-Age']); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
<?php |
|||
/** |
|||
* ownCloud - App Framework |
|||
* |
|||
* This file is licensed under the Affero General Public License version 3 or |
|||
* later. See the COPYING file. |
|||
* |
|||
* @author Bernhard Posselt <dev@bernhard-posselt.com> |
|||
* @copyright Bernhard Posselt 2014 |
|||
*/ |
|||
|
|||
|
|||
namespace OC\AppFramework\Middleware\Security; |
|||
|
|||
use OC\AppFramework\Http\Request; |
|||
use OCP\AppFramework\Http\Response; |
|||
|
|||
|
|||
class CORSMiddlewareTest extends \PHPUnit_Framework_TestCase { |
|||
|
|||
/** |
|||
* @CORS |
|||
*/ |
|||
public function testSetCORSAPIHeader() { |
|||
$request = new Request( |
|||
array('server' => array('HTTP_ORIGIN' => 'test')) |
|||
); |
|||
|
|||
$middleware = new CORSMiddleware($request); |
|||
$response = $middleware->afterController($this, __FUNCTION__, new Response()); |
|||
$headers = $response->getHeaders(); |
|||
|
|||
$this->assertEquals('test', $headers['Access-Control-Allow-Origin']); |
|||
} |
|||
|
|||
|
|||
public function testNoAnnotationNoCORSHEADER() { |
|||
$request = new Request( |
|||
array('server' => array('HTTP_ORIGIN' => 'test')) |
|||
); |
|||
$middleware = new CORSMiddleware($request); |
|||
|
|||
$response = $middleware->afterController($this, __FUNCTION__, new Response()); |
|||
$headers = $response->getHeaders(); |
|||
$this->assertFalse(array_key_exists('Access-Control-Allow-Origin', $headers)); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @CORS |
|||
*/ |
|||
public function testNoOriginHeaderNoCORSHEADER() { |
|||
$request = new Request(); |
|||
|
|||
$middleware = new CORSMiddleware($request); |
|||
$response = $middleware->afterController($this, __FUNCTION__, new Response()); |
|||
$headers = $response->getHeaders(); |
|||
$this->assertFalse(array_key_exists('Access-Control-Allow-Origin', $headers)); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @CORS |
|||
* @expectedException \OC\AppFramework\Middleware\Security\SecurityException |
|||
*/ |
|||
public function testCorsIgnoredIfWithCredentialsHeaderPresent() { |
|||
$request = new Request( |
|||
array('server' => array('HTTP_ORIGIN' => 'test')) |
|||
); |
|||
$middleware = new CORSMiddleware($request); |
|||
|
|||
$response = new Response(); |
|||
$response->addHeader('AcCess-control-Allow-Credentials ', 'TRUE'); |
|||
$response = $middleware->afterController($this, __FUNCTION__, $response); |
|||
} |
|||
|
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue