diff options
Diffstat (limited to 'tests/lib')
5 files changed, 183 insertions, 32 deletions
diff --git a/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php index 00538dda4b0..1e01f99a5d9 100644 --- a/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php +++ b/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php @@ -10,6 +10,8 @@ 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\Security\CSRF\CsrfTokenManager; +use OC\Security\CSRF\CsrfValidator; use OC\User\Session; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\Response; @@ -28,6 +30,8 @@ class CORSMiddlewareTest extends \Test\TestCase { private $session; /** @var IThrottler|MockObject */ private $throttler; + private CsrfTokenManager $csrfTokenManager; + private CsrfValidator $csrfValidator; /** @var CORSMiddlewareController */ private $controller; private LoggerInterface $logger; @@ -38,6 +42,8 @@ class CORSMiddlewareTest extends \Test\TestCase { $this->session = $this->createMock(Session::class); $this->throttler = $this->createMock(IThrottler::class); $this->logger = $this->createMock(LoggerInterface::class); + $this->csrfTokenManager = $this->createMock(CsrfTokenManager::class); + $this->csrfValidator = new CsrfValidator($this->csrfTokenManager); $this->controller = new CORSMiddlewareController( 'test', $this->createMock(IRequest::class) @@ -65,7 +71,7 @@ class CORSMiddlewareTest extends \Test\TestCase { $this->createMock(IConfig::class) ); $this->reflector->reflect($this->controller, $method); - $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger, $this->csrfValidator); $response = $middleware->afterController($this->controller, $method, new Response()); $headers = $response->getHeaders(); @@ -82,7 +88,7 @@ class CORSMiddlewareTest extends \Test\TestCase { $this->createMock(IRequestId::class), $this->createMock(IConfig::class) ); - $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger, $this->csrfValidator); $response = $middleware->afterController($this->controller, __FUNCTION__, new Response()); $headers = $response->getHeaders(); @@ -106,7 +112,7 @@ class CORSMiddlewareTest extends \Test\TestCase { $this->createMock(IConfig::class) ); $this->reflector->reflect($this->controller, $method); - $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger, $this->csrfValidator); $response = $middleware->afterController($this->controller, $method, new Response()); $headers = $response->getHeaders(); @@ -136,7 +142,7 @@ class CORSMiddlewareTest extends \Test\TestCase { $this->createMock(IConfig::class) ); $this->reflector->reflect($this->controller, $method); - $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger, $this->csrfValidator); $response = new Response(); $response->addHeader('AcCess-control-Allow-Credentials ', 'TRUE'); @@ -162,7 +168,7 @@ class CORSMiddlewareTest extends \Test\TestCase { $this->createMock(IConfig::class) ); $this->reflector->reflect($this->controller, $method); - $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger, $this->csrfValidator); $this->session->expects($this->once()) ->method('isLoggedIn') ->willReturn(false); @@ -196,7 +202,7 @@ class CORSMiddlewareTest extends \Test\TestCase { $this->createMock(IConfig::class) ); $this->reflector->reflect($this->controller, $method); - $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger, $this->csrfValidator); $this->session->expects($this->once()) ->method('isLoggedIn') ->willReturn(true); @@ -237,7 +243,7 @@ class CORSMiddlewareTest extends \Test\TestCase { ->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 = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger, $this->csrfValidator); $middleware->beforeController($this->controller, $method); } @@ -270,7 +276,7 @@ class CORSMiddlewareTest extends \Test\TestCase { ->with($this->equalTo('user'), $this->equalTo('pass')) ->will($this->throwException(new \OC\Authentication\Exceptions\PasswordLoginForbiddenException)); $this->reflector->reflect($this->controller, $method); - $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger, $this->csrfValidator); $middleware->beforeController($this->controller, $method); } @@ -303,7 +309,7 @@ class CORSMiddlewareTest extends \Test\TestCase { ->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 = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger, $this->csrfValidator); $middleware->beforeController($this->controller, $method); } @@ -317,7 +323,7 @@ class CORSMiddlewareTest extends \Test\TestCase { $this->createMock(IRequestId::class), $this->createMock(IConfig::class) ); - $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger, $this->csrfValidator); $response = $middleware->afterException($this->controller, __FUNCTION__, new SecurityException('A security exception')); $expected = new JSONResponse(['message' => 'A security exception'], 500); @@ -333,7 +339,7 @@ class CORSMiddlewareTest extends \Test\TestCase { $this->createMock(IRequestId::class), $this->createMock(IConfig::class) ); - $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger, $this->csrfValidator); $response = $middleware->afterException($this->controller, __FUNCTION__, new SecurityException('A security exception', 501)); $expected = new JSONResponse(['message' => 'A security exception'], 501); @@ -352,7 +358,7 @@ class CORSMiddlewareTest extends \Test\TestCase { $this->createMock(IRequestId::class), $this->createMock(IConfig::class) ); - $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger, $this->csrfValidator); $middleware->afterException($this->controller, __FUNCTION__, new \Exception('A regular exception')); } } diff --git a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php index fb457579fac..f4a2bd7e884 100644 --- a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php +++ b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php @@ -18,6 +18,9 @@ use OC\AppFramework\Middleware\Security\Exceptions\SecurityException; use OC\Appframework\Middleware\Security\Exceptions\StrictCookieMissingException; use OC\AppFramework\Middleware\Security\SecurityMiddleware; use OC\AppFramework\Utility\ControllerMethodReflector; +use OC\Security\CSRF\CsrfToken; +use OC\Security\CSRF\CsrfTokenManager; +use OC\Security\CSRF\CsrfValidator; use OC\Settings\AuthorizedGroupMapper; use OC\User\Session; use OCP\App\IAppManager; @@ -65,6 +68,8 @@ class SecurityMiddlewareTest extends \Test\TestCase { private $userSession; /** @var AuthorizedGroupMapper|\PHPUnit\Framework\MockObject\MockObject */ private $authorizedGroupMapper; + private CsrfTokenManager $csrfTokenManager; + private CsrfValidator $csrfValidator; protected function setUp(): void { parent::setUp(); @@ -81,6 +86,8 @@ class SecurityMiddlewareTest extends \Test\TestCase { $this->navigationManager = $this->createMock(INavigationManager::class); $this->urlGenerator = $this->createMock(IURLGenerator::class); $this->l10n = $this->createMock(IL10N::class); + $this->csrfTokenManager = $this->createMock(CsrfTokenManager::class); + $this->csrfValidator = new CsrfValidator($this->csrfTokenManager); $this->middleware = $this->getMiddleware(true, true, false); $this->secException = new SecurityException('hey', false); $this->secAjaxException = new SecurityException('hey', true); @@ -108,7 +115,8 @@ class SecurityMiddlewareTest extends \Test\TestCase { $this->l10n, $this->authorizedGroupMapper, $this->userSession, - $remoteIpAddress + $remoteIpAddress, + $this->csrfValidator, ); } @@ -321,12 +329,18 @@ class SecurityMiddlewareTest extends \Test\TestCase { public function testCsrfCheck(string $method): void { $this->expectException(\OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException::class); - $this->request->expects($this->once()) - ->method('passesCSRFCheck') - ->willReturn(false); - $this->request->expects($this->once()) + $this->request->expects($this->exactly(2)) ->method('passesStrictCookieCheck') ->willReturn(true); + $this->request->expects($this->once()) + ->method('getParam') + ->with('requesttoken', '') + ->willReturn(''); + $this->request->expects($this->once()) + ->method('getHeader') + ->with('REQUESTTOKEN') + ->willReturn(''); + $this->reader->reflect($this->controller, $method); $this->middleware->beforeController($this->controller, $method); } @@ -347,11 +361,20 @@ class SecurityMiddlewareTest extends \Test\TestCase { * @dataProvider dataPublicPage */ public function testPassesCsrfCheck(string $method): void { - $this->request->expects($this->once()) - ->method('passesCSRFCheck') + $this->request->expects($this->exactly(2)) + ->method('passesStrictCookieCheck') ->willReturn(true); $this->request->expects($this->once()) - ->method('passesStrictCookieCheck') + ->method('getParam') + ->with('requesttoken', '') + ->willReturn(''); + $this->request->expects($this->once()) + ->method('getHeader') + ->with('REQUESTTOKEN') + ->willReturn('foobar'); + $this->csrfTokenManager->expects($this->once()) + ->method('isTokenValid') + ->with(new CsrfToken('foobar')) ->willReturn(true); $this->reader->reflect($this->controller, $method); @@ -364,12 +387,21 @@ class SecurityMiddlewareTest extends \Test\TestCase { public function testFailCsrfCheck(string $method): void { $this->expectException(\OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException::class); - $this->request->expects($this->once()) - ->method('passesCSRFCheck') - ->willReturn(false); - $this->request->expects($this->once()) + $this->request->expects($this->exactly(2)) ->method('passesStrictCookieCheck') ->willReturn(true); + $this->request->expects($this->once()) + ->method('getParam') + ->with('requesttoken', '') + ->willReturn(''); + $this->request->expects($this->once()) + ->method('getHeader') + ->with('REQUESTTOKEN') + ->willReturn('foobar'); + $this->csrfTokenManager->expects($this->once()) + ->method('isTokenValid') + ->with(new CsrfToken('foobar')) + ->willReturn(false); $this->reader->reflect($this->controller, $method); $this->middleware->beforeController($this->controller, $method); @@ -386,6 +418,12 @@ class SecurityMiddlewareTest extends \Test\TestCase { $this->request->expects($this->once()) ->method('passesStrictCookieCheck') ->willReturn(false); + $this->request->expects($this->never()) + ->method('getParam'); + $this->request->expects($this->never()) + ->method('getHeader'); + $this->csrfTokenManager->expects($this->never()) + ->method('isTokenValid'); $this->reader->reflect($this->controller, $method); $this->middleware->beforeController($this->controller, $method); @@ -441,6 +479,9 @@ class SecurityMiddlewareTest extends \Test\TestCase { $this->request ->method('getHeader') ->willReturnCallback(function ($header) use ($hasOcsApiHeader, $hasBearerAuth) { + if ($header === 'REQUESTTOKEN') { + return ''; + } if ($header === 'OCS-APIREQUEST' && $hasOcsApiHeader) { return 'true'; } @@ -449,9 +490,15 @@ class SecurityMiddlewareTest extends \Test\TestCase { } return ''; }); - $this->request->expects($this->once()) + $this->request->expects($this->exactly(2)) ->method('passesStrictCookieCheck') ->willReturn(true); + $this->request->expects($this->once()) + ->method('getParam') + ->with('requesttoken', '') + ->willReturn(''); + $this->csrfTokenManager->expects($this->never()) + ->method('isTokenValid'); $controller = new $controllerClass('test', $this->request); diff --git a/tests/lib/EventSourceFactoryTest.php b/tests/lib/EventSourceFactoryTest.php index c5e22a8fe34..abd23e53bb5 100644 --- a/tests/lib/EventSourceFactoryTest.php +++ b/tests/lib/EventSourceFactoryTest.php @@ -9,11 +9,13 @@ namespace Test; use OC\EventSourceFactory; use OCP\IEventSource; use OCP\IRequest; +use OCP\Security\CSRF\ICsrfValidator; class EventSourceFactoryTest extends TestCase { public function testCreate(): void { $request = $this->createMock(IRequest::class); - $factory = new EventSourceFactory($request); + $csrfValidator = $this->createMock(ICsrfValidator::class); + $factory = new EventSourceFactory($request, $csrfValidator); $instance = $factory->create(); $this->assertInstanceOf(IEventSource::class, $instance); diff --git a/tests/lib/Security/CSRF/CsrfTokenManagerTest.php b/tests/lib/Security/CSRF/CsrfTokenManagerTest.php index c4fd480654d..8c19bc6e82d 100644 --- a/tests/lib/Security/CSRF/CsrfTokenManagerTest.php +++ b/tests/lib/Security/CSRF/CsrfTokenManagerTest.php @@ -131,14 +131,14 @@ class CsrfTokenManagerTest extends \Test\TestCase { $xorB64 = 'BQcF'; $tokenVal = sprintf('%s:%s', $xorB64, base64_encode($a)); $this->storageInterface - ->expects($this->once()) - ->method('hasToken') - ->willReturn(true); + ->expects($this->once()) + ->method('hasToken') + ->willReturn(true); $token = new \OC\Security\CSRF\CsrfToken($tokenVal); $this->storageInterface - ->expects($this->once()) - ->method('getToken') - ->willReturn($b); + ->expects($this->once()) + ->method('getToken') + ->willReturn($b); $this->assertSame(true, $this->csrfTokenManager->isTokenValid($token)); } diff --git a/tests/lib/Security/CSRF/CsrfValidatorTest.php b/tests/lib/Security/CSRF/CsrfValidatorTest.php new file mode 100644 index 00000000000..30aac3c7039 --- /dev/null +++ b/tests/lib/Security/CSRF/CsrfValidatorTest.php @@ -0,0 +1,96 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Security\CSRF; + +use OC\Security\CSRF\CsrfTokenManager; +use OC\Security\CSRF\CsrfValidator; +use OCP\IRequest; +use Test\TestCase; + +class CsrfValidatorTest extends TestCase { + private CsrfTokenManager $csrfTokenManager; + private CsrfValidator $csrfValidator; + + protected function setUp(): void { + parent::setUp(); + + $this->csrfTokenManager = $this->createMock(CsrfTokenManager::class); + $this->csrfValidator = new CsrfValidator($this->csrfTokenManager); + } + + public function testFailStrictCookieCheck(): void { + $request = $this->createMock(IRequest::class); + $request->method('passesStrictCookieCheck') + ->willReturn(false); + + $this->assertFalse($this->csrfValidator->validate($request)); + } + + public function testFailMissingToken(): void { + $request = $this->createMock(IRequest::class); + $request->method('passesStrictCookieCheck') + ->willReturn(true); + $request->method('getParam') + ->with('requesttoken', '') + ->willReturn(''); + $request->method('getHeader') + ->with('REQUESTTOKEN') + ->willReturn(''); + + $this->assertFalse($this->csrfValidator->validate($request)); + } + + public function testFailInvalidToken(): void { + $request = $this->createMock(IRequest::class); + $request->method('passesStrictCookieCheck') + ->willReturn(true); + $request->method('getParam') + ->with('requesttoken', '') + ->willReturn('token123'); + $request->method('getHeader') + ->with('REQUESTTOKEN') + ->willReturn(''); + + $this->csrfTokenManager + ->method('isTokenValid') + ->willReturn(false); + + $this->assertFalse($this->csrfValidator->validate($request)); + } + + public function testPass(): void { + $request = $this->createMock(IRequest::class); + $request->method('passesStrictCookieCheck') + ->willReturn(true); + $request->method('getParam') + ->with('requesttoken', '') + ->willReturn('token123'); + $request->method('getHeader') + ->with('REQUESTTOKEN') + ->willReturn(''); + + $this->csrfTokenManager + ->method('isTokenValid') + ->willReturn(true); + + $this->assertTrue($this->csrfValidator->validate($request)); + } + + public function testPassWithOCSAPIRequestHeader(): void { + $request = $this->createMock(IRequest::class); + $request->method('passesStrictCookieCheck') + ->willReturn(true); + $request->method('getHeader') + ->with('OCS-APIRequest', '') + ->willReturn('yes'); + + $this->assertTrue($this->csrfValidator->validate($request)); + } +} |