Return proper status code in case of a CORS exception

When returning a 500 statuscode external applications may interpret this as an error instead of handling this more gracefully. This will now make return a 401 thus.

Fixes https://github.com/owncloud/core/issues/17742
This commit is contained in:
Lukas Reschke 2015-07-20 12:54:22 +02:00
parent 1e4496c1cb
commit 7dda86f371
3 changed files with 91 additions and 11 deletions

View File

@ -2,6 +2,7 @@
/** /**
* @author Bernhard Posselt <dev@bernhard-posselt.com> * @author Bernhard Posselt <dev@bernhard-posselt.com>
* @author Morris Jobke <hey@morrisjobke.de> * @author Morris Jobke <hey@morrisjobke.de>
* @author Lukas Reschke <lukas@owncloud.com>
* *
* @copyright Copyright (c) 2015, ownCloud, Inc. * @copyright Copyright (c) 2015, ownCloud, Inc.
* @license AGPL-3.0 * @license AGPL-3.0
@ -23,6 +24,9 @@
namespace OC\AppFramework\Middleware\Security; namespace OC\AppFramework\Middleware\Security;
use OC\AppFramework\Utility\ControllerMethodReflector; use OC\AppFramework\Utility\ControllerMethodReflector;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest; use OCP\IRequest;
use OCP\IUserSession; use OCP\IUserSession;
use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\Response;
@ -57,8 +61,8 @@ class CORSMiddleware extends Middleware {
* @param IUserSession $session * @param IUserSession $session
*/ */
public function __construct(IRequest $request, public function __construct(IRequest $request,
ControllerMethodReflector $reflector, ControllerMethodReflector $reflector,
IUserSession $session) { IUserSession $session) {
$this->request = $request; $this->request = $request;
$this->reflector = $reflector; $this->reflector = $reflector;
$this->session = $session; $this->session = $session;
@ -71,6 +75,7 @@ class CORSMiddleware extends Middleware {
* @param Controller $controller the controller that is being called * @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on * @param string $methodName the name of the method that will be called on
* the controller * the controller
* @throws SecurityException
* @since 6.0.0 * @since 6.0.0
*/ */
public function beforeController($controller, $methodName){ public function beforeController($controller, $methodName){
@ -83,7 +88,7 @@ class CORSMiddleware extends Middleware {
$this->session->logout(); $this->session->logout();
if(!$this->session->login($user, $pass)) { if(!$this->session->login($user, $pass)) {
throw new SecurityException('CORS requires basic auth'); throw new SecurityException('CORS requires basic auth', Http::STATUS_UNAUTHORIZED);
} }
} }
} }
@ -97,6 +102,7 @@ class CORSMiddleware extends Middleware {
* the controller * the controller
* @param Response $response the generated response from the controller * @param Response $response the generated response from the controller
* @return Response a Response object * @return Response a Response object
* @throws SecurityException
*/ */
public function afterController($controller, $methodName, Response $response){ public function afterController($controller, $methodName, Response $response){
// only react if its a CORS request and if the request sends origin and // only react if its a CORS request and if the request sends origin and
@ -106,7 +112,7 @@ class CORSMiddleware extends Middleware {
// allow credentials headers must not be true or CSRF is possible // allow credentials headers must not be true or CSRF is possible
// otherwise // otherwise
foreach($response->getHeaders() as $header => $value ) { foreach($response->getHeaders() as $header => $value) {
if(strtolower($header) === 'access-control-allow-credentials' && if(strtolower($header) === 'access-control-allow-credentials' &&
strtolower(trim($value)) === 'true') { strtolower(trim($value)) === 'true') {
$msg = 'Access-Control-Allow-Credentials must not be '. $msg = 'Access-Control-Allow-Credentials must not be '.
@ -121,5 +127,28 @@ class CORSMiddleware extends Middleware {
return $response; return $response;
} }
/**
* If an SecurityException is being caught return a JSON error response
*
* @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 \Exception $exception the thrown exception
* @throws \Exception the passed in exception if it cant handle it
* @return Response a Response object or null in case that the exception could not be handled
*/
public function afterException($controller, $methodName, \Exception $exception){
if($exception instanceof SecurityException){
$response = new JSONResponse(['message' => $exception->getMessage()]);
if($exception->getCode() !== 0) {
$response->setStatus($exception->getCode());
} else {
$response->setStatus(Http::STATUS_INTERNAL_SERVER_ERROR);
}
return $response;
}
throw $exception;
}
} }

View File

@ -69,13 +69,13 @@ class SecurityMiddleware extends Middleware {
* @param bool $isAdminUser * @param bool $isAdminUser
*/ */
public function __construct(IRequest $request, public function __construct(IRequest $request,
ControllerMethodReflector $reflector, ControllerMethodReflector $reflector,
INavigationManager $navigationManager, INavigationManager $navigationManager,
IURLGenerator $urlGenerator, IURLGenerator $urlGenerator,
ILogger $logger, ILogger $logger,
$appName, $appName,
$isLoggedIn, $isLoggedIn,
$isAdminUser){ $isAdminUser){
$this->navigationManager = $navigationManager; $this->navigationManager = $navigationManager;
$this->request = $request; $this->request = $request;
$this->reflector = $reflector; $this->reflector = $reflector;

View File

@ -15,6 +15,8 @@ namespace OC\AppFramework\Middleware\Security;
use OC\AppFramework\Http\Request; use OC\AppFramework\Http\Request;
use OC\AppFramework\Utility\ControllerMethodReflector; use OC\AppFramework\Utility\ControllerMethodReflector;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\Response;
@ -181,4 +183,53 @@ class CORSMiddlewareTest extends \Test\TestCase {
$middleware->beforeController($this, __FUNCTION__, new Response()); $middleware->beforeController($this, __FUNCTION__, new Response());
} }
public function testAfterExceptionWithSecurityExceptionNoStatus() {
$request = new Request(
['server' => [
'PHP_AUTH_USER' => 'user',
'PHP_AUTH_PW' => 'pass'
]],
$this->getMock('\OCP\Security\ISecureRandom'),
$this->getMock('\OCP\IConfig')
);
$middleware = new CORSMiddleware($request, $this->reflector, $this->session);
$response = $middleware->afterException($this, __FUNCTION__, new SecurityException('A security exception'));
$expected = new JSONResponse(['message' => 'A security exception'], 500);
$this->assertEquals($expected, $response);
}
public function testAfterExceptionWithSecurityExceptionWithStatus() {
$request = new Request(
['server' => [
'PHP_AUTH_USER' => 'user',
'PHP_AUTH_PW' => 'pass'
]],
$this->getMock('\OCP\Security\ISecureRandom'),
$this->getMock('\OCP\IConfig')
);
$middleware = new CORSMiddleware($request, $this->reflector, $this->session);
$response = $middleware->afterException($this, __FUNCTION__, new SecurityException('A security exception', 501));
$expected = new JSONResponse(['message' => 'A security exception'], 501);
$this->assertEquals($expected, $response);
}
/**
* @expectedException \Exception
* @expectedExceptionMessage A regular exception
*/
public function testAfterExceptionWithRegularException() {
$request = new Request(
['server' => [
'PHP_AUTH_USER' => 'user',
'PHP_AUTH_PW' => 'pass'
]],
$this->getMock('\OCP\Security\ISecureRandom'),
$this->getMock('\OCP\IConfig')
);
$middleware = new CORSMiddleware($request, $this->reflector, $this->session);
$middleware->afterException($this, __FUNCTION__, new \Exception('A regular exception'));
}
} }