diff options
Diffstat (limited to 'tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php')
-rw-r--r-- | tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php | 344 |
1 files changed, 344 insertions, 0 deletions
diff --git a/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php new file mode 100644 index 00000000000..c325ae638fb --- /dev/null +++ b/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php @@ -0,0 +1,344 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2014-2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace Test\AppFramework\Middleware\Security; + +use OC\AppFramework\Http\Request; +use OC\AppFramework\Middleware\Security\CORSMiddleware; +use OC\AppFramework\Middleware\Security\Exceptions\SecurityException; +use OC\AppFramework\Utility\ControllerMethodReflector; +use OC\Authentication\Exceptions\PasswordLoginForbiddenException; +use OC\User\Session; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Http\Response; +use OCP\IConfig; +use OCP\IRequest; +use OCP\IRequestId; +use OCP\Security\Bruteforce\IThrottler; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; +use Test\AppFramework\Middleware\Security\Mock\CORSMiddlewareController; + +class CORSMiddlewareTest extends \Test\TestCase { + /** @var ControllerMethodReflector */ + private $reflector; + /** @var Session|MockObject */ + private $session; + /** @var IThrottler|MockObject */ + private $throttler; + /** @var CORSMiddlewareController */ + private $controller; + private LoggerInterface $logger; + + protected function setUp(): void { + parent::setUp(); + $this->reflector = new ControllerMethodReflector(); + $this->session = $this->createMock(Session::class); + $this->throttler = $this->createMock(IThrottler::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->controller = new CORSMiddlewareController( + 'test', + $this->createMock(IRequest::class) + ); + } + + public static function dataSetCORSAPIHeader(): array { + return [ + ['testSetCORSAPIHeader'], + ['testSetCORSAPIHeaderAttribute'], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataSetCORSAPIHeader')] + public function testSetCORSAPIHeader(string $method): void { + $request = new Request( + [ + 'server' => [ + 'HTTP_ORIGIN' => 'test' + ] + ], + $this->createMock(IRequestId::class), + $this->createMock(IConfig::class) + ); + $this->reflector->reflect($this->controller, $method); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + + $response = $middleware->afterController($this->controller, $method, new Response()); + $headers = $response->getHeaders(); + $this->assertEquals('test', $headers['Access-Control-Allow-Origin']); + } + + public function testNoAnnotationNoCORSHEADER(): void { + $request = new Request( + [ + 'server' => [ + 'HTTP_ORIGIN' => 'test' + ] + ], + $this->createMock(IRequestId::class), + $this->createMock(IConfig::class) + ); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + + $response = $middleware->afterController($this->controller, __FUNCTION__, new Response()); + $headers = $response->getHeaders(); + $this->assertFalse(array_key_exists('Access-Control-Allow-Origin', $headers)); + } + + public static function dataNoOriginHeaderNoCORSHEADER(): array { + return [ + ['testNoOriginHeaderNoCORSHEADER'], + ['testNoOriginHeaderNoCORSHEADERAttribute'], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataNoOriginHeaderNoCORSHEADER')] + public function testNoOriginHeaderNoCORSHEADER(string $method): void { + $request = new Request( + [], + $this->createMock(IRequestId::class), + $this->createMock(IConfig::class) + ); + $this->reflector->reflect($this->controller, $method); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + + $response = $middleware->afterController($this->controller, $method, new Response()); + $headers = $response->getHeaders(); + $this->assertFalse(array_key_exists('Access-Control-Allow-Origin', $headers)); + } + + public static function dataCorsIgnoredIfWithCredentialsHeaderPresent(): array { + return [ + ['testCorsIgnoredIfWithCredentialsHeaderPresent'], + ['testCorsAttributeIgnoredIfWithCredentialsHeaderPresent'], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataCorsIgnoredIfWithCredentialsHeaderPresent')] + public function testCorsIgnoredIfWithCredentialsHeaderPresent(string $method): void { + $this->expectException(SecurityException::class); + + $request = new Request( + [ + 'server' => [ + 'HTTP_ORIGIN' => 'test' + ] + ], + $this->createMock(IRequestId::class), + $this->createMock(IConfig::class) + ); + $this->reflector->reflect($this->controller, $method); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + + $response = new Response(); + $response->addHeader('AcCess-control-Allow-Credentials ', 'TRUE'); + $middleware->afterController($this->controller, $method, $response); + } + + public static function dataNoCORSOnAnonymousPublicPage(): array { + return [ + ['testNoCORSOnAnonymousPublicPage'], + ['testNoCORSOnAnonymousPublicPageAttribute'], + ['testNoCORSAttributeOnAnonymousPublicPage'], + ['testNoCORSAttributeOnAnonymousPublicPageAttribute'], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataNoCORSOnAnonymousPublicPage')] + public function testNoCORSOnAnonymousPublicPage(string $method): void { + $request = new Request( + [], + $this->createMock(IRequestId::class), + $this->createMock(IConfig::class) + ); + $this->reflector->reflect($this->controller, $method); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + $this->session->expects($this->once()) + ->method('isLoggedIn') + ->willReturn(false); + $this->session->expects($this->never()) + ->method('logout'); + $this->session->expects($this->never()) + ->method('logClientIn') + ->with($this->equalTo('user'), $this->equalTo('pass')) + ->willReturn(true); + $this->reflector->reflect($this->controller, $method); + + $middleware->beforeController($this->controller, $method); + } + + public static function dataCORSShouldNeverAllowCookieAuth(): array { + return [ + ['testCORSShouldNeverAllowCookieAuth'], + ['testCORSShouldNeverAllowCookieAuthAttribute'], + ['testCORSAttributeShouldNeverAllowCookieAuth'], + ['testCORSAttributeShouldNeverAllowCookieAuthAttribute'], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataCORSShouldNeverAllowCookieAuth')] + public function testCORSShouldNeverAllowCookieAuth(string $method): void { + $request = new Request( + [], + $this->createMock(IRequestId::class), + $this->createMock(IConfig::class) + ); + $this->reflector->reflect($this->controller, $method); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + $this->session->expects($this->once()) + ->method('isLoggedIn') + ->willReturn(true); + $this->session->expects($this->once()) + ->method('logout'); + $this->session->expects($this->never()) + ->method('logClientIn') + ->with($this->equalTo('user'), $this->equalTo('pass')) + ->willReturn(true); + + $this->expectException(SecurityException::class); + $middleware->beforeController($this->controller, $method); + } + + public static function dataCORSShouldRelogin(): array { + return [ + ['testCORSShouldRelogin'], + ['testCORSAttributeShouldRelogin'], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataCORSShouldRelogin')] + public function testCORSShouldRelogin(string $method): void { + $request = new Request( + ['server' => [ + 'PHP_AUTH_USER' => 'user', + 'PHP_AUTH_PW' => 'pass' + ]], + $this->createMock(IRequestId::class), + $this->createMock(IConfig::class) + ); + $this->session->expects($this->once()) + ->method('logout'); + $this->session->expects($this->once()) + ->method('logClientIn') + ->with($this->equalTo('user'), $this->equalTo('pass')) + ->willReturn(true); + $this->reflector->reflect($this->controller, $method); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + + $middleware->beforeController($this->controller, $method); + } + + public static function dataCORSShouldFailIfPasswordLoginIsForbidden(): array { + return [ + ['testCORSShouldFailIfPasswordLoginIsForbidden'], + ['testCORSAttributeShouldFailIfPasswordLoginIsForbidden'], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataCORSShouldFailIfPasswordLoginIsForbidden')] + public function testCORSShouldFailIfPasswordLoginIsForbidden(string $method): void { + $this->expectException(SecurityException::class); + + $request = new Request( + ['server' => [ + 'PHP_AUTH_USER' => 'user', + 'PHP_AUTH_PW' => 'pass' + ]], + $this->createMock(IRequestId::class), + $this->createMock(IConfig::class) + ); + $this->session->expects($this->once()) + ->method('logout'); + $this->session->expects($this->once()) + ->method('logClientIn') + ->with($this->equalTo('user'), $this->equalTo('pass')) + ->willThrowException(new PasswordLoginForbiddenException); + $this->reflector->reflect($this->controller, $method); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + + $middleware->beforeController($this->controller, $method); + } + + public static function dataCORSShouldNotAllowCookieAuth(): array { + return [ + ['testCORSShouldNotAllowCookieAuth'], + ['testCORSAttributeShouldNotAllowCookieAuth'], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataCORSShouldNotAllowCookieAuth')] + public function testCORSShouldNotAllowCookieAuth(string $method): void { + $this->expectException(SecurityException::class); + + $request = new Request( + ['server' => [ + 'PHP_AUTH_USER' => 'user', + 'PHP_AUTH_PW' => 'pass' + ]], + $this->createMock(IRequestId::class), + $this->createMock(IConfig::class) + ); + $this->session->expects($this->once()) + ->method('logout'); + $this->session->expects($this->once()) + ->method('logClientIn') + ->with($this->equalTo('user'), $this->equalTo('pass')) + ->willReturn(false); + $this->reflector->reflect($this->controller, $method); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + + $middleware->beforeController($this->controller, $method); + } + + public function testAfterExceptionWithSecurityExceptionNoStatus(): void { + $request = new Request( + ['server' => [ + 'PHP_AUTH_USER' => 'user', + 'PHP_AUTH_PW' => 'pass' + ]], + $this->createMock(IRequestId::class), + $this->createMock(IConfig::class) + ); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + $response = $middleware->afterException($this->controller, __FUNCTION__, new SecurityException('A security exception')); + + $expected = new JSONResponse(['message' => 'A security exception'], 500); + $this->assertEquals($expected, $response); + } + + public function testAfterExceptionWithSecurityExceptionWithStatus(): void { + $request = new Request( + ['server' => [ + 'PHP_AUTH_USER' => 'user', + 'PHP_AUTH_PW' => 'pass' + ]], + $this->createMock(IRequestId::class), + $this->createMock(IConfig::class) + ); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + $response = $middleware->afterException($this->controller, __FUNCTION__, new SecurityException('A security exception', 501)); + + $expected = new JSONResponse(['message' => 'A security exception'], 501); + $this->assertEquals($expected, $response); + } + + public function testAfterExceptionWithRegularException(): void { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('A regular exception'); + + $request = new Request( + ['server' => [ + 'PHP_AUTH_USER' => 'user', + 'PHP_AUTH_PW' => 'pass' + ]], + $this->createMock(IRequestId::class), + $this->createMock(IConfig::class) + ); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + $middleware->afterException($this->controller, __FUNCTION__, new \Exception('A regular exception')); + } +} |