Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 16543e07b4 |
@@ -36,6 +36,7 @@ use OCP\L10N\IFactory as IL10NFactory;
|
||||
use OCP\Security\Bruteforce\IThrottler;
|
||||
use OCP\Security\ISecureRandom;
|
||||
use OCP\Server;
|
||||
use OCP\Security\CSRF\ICsrfValidator;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
$authBackend = new Auth(
|
||||
@@ -44,6 +45,7 @@ $authBackend = new Auth(
|
||||
Server::get(IRequest::class),
|
||||
Server::get(\OC\Authentication\TwoFactorAuth\Manager::class),
|
||||
Server::get(IThrottler::class),
|
||||
Server::get(ICsrfValidator::class),
|
||||
'principals/'
|
||||
);
|
||||
$principalBackend = new Principal(
|
||||
|
||||
@@ -32,6 +32,7 @@ use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Security\Bruteforce\IThrottler;
|
||||
use OCP\Server;
|
||||
use OCP\Security\CSRF\ICsrfValidator;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sabre\CardDAV\Plugin;
|
||||
|
||||
@@ -41,6 +42,7 @@ $authBackend = new Auth(
|
||||
Server::get(IRequest::class),
|
||||
Server::get(\OC\Authentication\TwoFactorAuth\Manager::class),
|
||||
Server::get(IThrottler::class),
|
||||
Server::get(ICsrfValidator::class),
|
||||
'principals/'
|
||||
);
|
||||
$principalBackend = new Principal(
|
||||
|
||||
@@ -22,6 +22,7 @@ use OCP\IUserSession;
|
||||
use OCP\SabrePluginEvent;
|
||||
use OCP\Security\Bruteforce\IThrottler;
|
||||
use OCP\Server;
|
||||
use OCP\Security\CSRF\ICsrfValidator;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
// no php execution timeout for webdav
|
||||
@@ -55,6 +56,7 @@ $authBackend = new Auth(
|
||||
Server::get(IRequest::class),
|
||||
Server::get(\OC\Authentication\TwoFactorAuth\Manager::class),
|
||||
Server::get(IThrottler::class),
|
||||
Server::get(ICsrfValidator::class),
|
||||
'principals/'
|
||||
);
|
||||
$authPlugin = new \Sabre\DAV\Auth\Plugin($authBackend);
|
||||
|
||||
@@ -20,6 +20,7 @@ use OCP\ISession;
|
||||
use OCP\Security\Bruteforce\IThrottler;
|
||||
use OCP\Security\Bruteforce\MaxDelayReached;
|
||||
use OCP\Server;
|
||||
use OCP\Security\CSRF\ICsrfValidator;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sabre\DAV\Auth\Backend\AbstractBasic;
|
||||
use Sabre\DAV\Exception\NotAuthenticated;
|
||||
@@ -37,6 +38,7 @@ class Auth extends AbstractBasic {
|
||||
private IRequest $request,
|
||||
private Manager $twoFactorManager,
|
||||
private IThrottler $throttler,
|
||||
private ICsrfValidator $csrfValidator,
|
||||
string $principalPrefix = 'principals/users/',
|
||||
) {
|
||||
$this->principalPrefix = $principalPrefix;
|
||||
@@ -162,7 +164,7 @@ class Auth extends AbstractBasic {
|
||||
private function auth(RequestInterface $request, ResponseInterface $response): array {
|
||||
$forcedLogout = false;
|
||||
|
||||
if (!$this->request->passesCSRFCheck()
|
||||
if (!$this->csrfValidator->validate($this->request)
|
||||
&& $this->requiresCSRFCheck()) {
|
||||
// In case of a fail with POST we need to recheck the credentials
|
||||
if ($this->request->getMethod() === 'POST') {
|
||||
|
||||
@@ -98,6 +98,7 @@ use OCP\SabrePluginEvent;
|
||||
use OCP\Security\Bruteforce\IThrottler;
|
||||
use OCP\SystemTag\ISystemTagManager;
|
||||
use OCP\SystemTag\ISystemTagObjectMapper;
|
||||
use OCP\Security\CSRF\ICsrfValidator;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sabre\CardDAV\VCFExportPlugin;
|
||||
use Sabre\DAV\Auth\Plugin;
|
||||
@@ -138,7 +139,8 @@ class Server {
|
||||
\OCP\Server::get(IUserSession::class),
|
||||
\OCP\Server::get(IRequest::class),
|
||||
\OCP\Server::get(\OC\Authentication\TwoFactorAuth\Manager::class),
|
||||
\OCP\Server::get(IThrottler::class)
|
||||
\OCP\Server::get(IThrottler::class),
|
||||
\OCP\Server::get(ICsrfValidator::class)
|
||||
);
|
||||
|
||||
// Set URL explicitly due to reverse-proxy situations
|
||||
|
||||
@@ -18,6 +18,7 @@ use OCP\ISession;
|
||||
use OCP\IUser;
|
||||
use OCP\Security\Bruteforce\IThrottler;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use OCP\Security\CSRF\ICsrfValidator;
|
||||
use Sabre\DAV\Server;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
@@ -35,6 +36,7 @@ class AuthTest extends TestCase {
|
||||
private IRequest&MockObject $request;
|
||||
private Manager&MockObject $twoFactorManager;
|
||||
private IThrottler&MockObject $throttler;
|
||||
private ICsrfValidator $csrfValidator;
|
||||
private Auth $auth;
|
||||
|
||||
protected function setUp(): void {
|
||||
@@ -44,12 +46,14 @@ class AuthTest extends TestCase {
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->twoFactorManager = $this->createMock(Manager::class);
|
||||
$this->throttler = $this->createMock(IThrottler::class);
|
||||
$this->csrfValidator = $this->createMock(ICsrfValidator::class);
|
||||
$this->auth = new Auth(
|
||||
$this->session,
|
||||
$this->userSession,
|
||||
$this->request,
|
||||
$this->twoFactorManager,
|
||||
$this->throttler
|
||||
$this->throttler,
|
||||
$this->csrfValidator,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -228,9 +232,9 @@ class AuthTest extends TestCase {
|
||||
->expects($this->any())
|
||||
->method('getUser')
|
||||
->willReturn($user);
|
||||
$this->request
|
||||
$this->csrfValidator
|
||||
->expects($this->once())
|
||||
->method('passesCSRFCheck')
|
||||
->method('validate')
|
||||
->willReturn(false);
|
||||
|
||||
$expectedResponse = [
|
||||
@@ -269,9 +273,9 @@ class AuthTest extends TestCase {
|
||||
->expects($this->any())
|
||||
->method('getUser')
|
||||
->willReturn($user);
|
||||
$this->request
|
||||
$this->csrfValidator
|
||||
->expects($this->once())
|
||||
->method('passesCSRFCheck')
|
||||
->method('validate')
|
||||
->willReturn(false);
|
||||
$this->auth->check($request, $response);
|
||||
}
|
||||
@@ -308,9 +312,9 @@ class AuthTest extends TestCase {
|
||||
->expects($this->any())
|
||||
->method('getUser')
|
||||
->willReturn($user);
|
||||
$this->request
|
||||
$this->csrfValidator
|
||||
->expects($this->once())
|
||||
->method('passesCSRFCheck')
|
||||
->method('validate')
|
||||
->willReturn(true);
|
||||
$this->twoFactorManager->expects($this->once())
|
||||
->method('needsSecondFactor')
|
||||
@@ -351,9 +355,9 @@ class AuthTest extends TestCase {
|
||||
->expects($this->any())
|
||||
->method('getUser')
|
||||
->willReturn($user);
|
||||
$this->request
|
||||
$this->csrfValidator
|
||||
->expects($this->once())
|
||||
->method('passesCSRFCheck')
|
||||
->method('validate')
|
||||
->willReturn(false);
|
||||
$this->auth->check($request, $response);
|
||||
}
|
||||
@@ -386,9 +390,9 @@ class AuthTest extends TestCase {
|
||||
->expects($this->any())
|
||||
->method('getUser')
|
||||
->willReturn($user);
|
||||
$this->request
|
||||
$this->csrfValidator
|
||||
->expects($this->once())
|
||||
->method('passesCSRFCheck')
|
||||
->method('validate')
|
||||
->willReturn(false);
|
||||
|
||||
$this->auth->check($request, $response);
|
||||
@@ -443,9 +447,9 @@ class AuthTest extends TestCase {
|
||||
->expects($this->any())
|
||||
->method('getUser')
|
||||
->willReturn($user);
|
||||
$this->request
|
||||
$this->csrfValidator
|
||||
->expects($this->once())
|
||||
->method('passesCSRFCheck')
|
||||
->method('validate')
|
||||
->willReturn(true);
|
||||
|
||||
$response = $this->auth->check($request, $response);
|
||||
|
||||
@@ -44,6 +44,7 @@ use OCP\Notification\IManager;
|
||||
use OCP\Security\Bruteforce\IThrottler;
|
||||
use OCP\Security\ITrustedDomainHelper;
|
||||
use OCP\Server;
|
||||
use OCP\Security\CSRF\ICsrfValidator;
|
||||
use OCP\Util;
|
||||
|
||||
class LoginController extends Controller {
|
||||
@@ -67,6 +68,7 @@ class LoginController extends Controller {
|
||||
private IManager $manager,
|
||||
private IL10N $l10n,
|
||||
private IAppManager $appManager,
|
||||
private ICsrfValidator $csrfValidator,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
}
|
||||
@@ -301,7 +303,7 @@ class LoginController extends Controller {
|
||||
// This could have come from someone malicious who tries to block a user by triggering the bruteforce protection.
|
||||
$error = self::LOGIN_MSG_INVALID_ORIGIN;
|
||||
$throttle = false;
|
||||
} elseif (!$this->request->passesCSRFCheck()) {
|
||||
} elseif (!$this->csrfValidator->validate($this->request)) {
|
||||
if ($this->userSession->isLoggedIn()) {
|
||||
// If the user is already logged in and the CSRF check does not pass then
|
||||
// simply redirect the user to the correct page as required. This is the
|
||||
|
||||
@@ -757,6 +757,7 @@ return array(
|
||||
'OCP\\Security\\Bruteforce\\IThrottler' => $baseDir . '/lib/public/Security/Bruteforce/IThrottler.php',
|
||||
'OCP\\Security\\Bruteforce\\MaxDelayReached' => $baseDir . '/lib/public/Security/Bruteforce/MaxDelayReached.php',
|
||||
'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => $baseDir . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php',
|
||||
'OCP\\Security\\CSRF\\ICsrfValidator' => $baseDir . '/lib/public/Security/CSRF/ICsrfValidator.php',
|
||||
'OCP\\Security\\Events\\GenerateSecurePasswordEvent' => $baseDir . '/lib/public/Security/Events/GenerateSecurePasswordEvent.php',
|
||||
'OCP\\Security\\Events\\ValidatePasswordPolicyEvent' => $baseDir . '/lib/public/Security/Events/ValidatePasswordPolicyEvent.php',
|
||||
'OCP\\Security\\FeaturePolicy\\AddFeaturePolicyEvent' => $baseDir . '/lib/public/Security/FeaturePolicy/AddFeaturePolicyEvent.php',
|
||||
@@ -2032,6 +2033,7 @@ return array(
|
||||
'OC\\Security\\CSRF\\CsrfToken' => $baseDir . '/lib/private/Security/CSRF/CsrfToken.php',
|
||||
'OC\\Security\\CSRF\\CsrfTokenGenerator' => $baseDir . '/lib/private/Security/CSRF/CsrfTokenGenerator.php',
|
||||
'OC\\Security\\CSRF\\CsrfTokenManager' => $baseDir . '/lib/private/Security/CSRF/CsrfTokenManager.php',
|
||||
'OC\\Security\\CSRF\\CsrfValidator' => $baseDir . '/lib/private/Security/CSRF/CsrfValidator.php',
|
||||
'OC\\Security\\CSRF\\TokenStorage\\SessionStorage' => $baseDir . '/lib/private/Security/CSRF/TokenStorage/SessionStorage.php',
|
||||
'OC\\Security\\Certificate' => $baseDir . '/lib/private/Security/Certificate.php',
|
||||
'OC\\Security\\CertificateManager' => $baseDir . '/lib/private/Security/CertificateManager.php',
|
||||
|
||||
@@ -798,6 +798,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
||||
'OCP\\Security\\Bruteforce\\IThrottler' => __DIR__ . '/../../..' . '/lib/public/Security/Bruteforce/IThrottler.php',
|
||||
'OCP\\Security\\Bruteforce\\MaxDelayReached' => __DIR__ . '/../../..' . '/lib/public/Security/Bruteforce/MaxDelayReached.php',
|
||||
'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php',
|
||||
'OCP\\Security\\CSRF\\ICsrfValidator' => __DIR__ . '/../../..' . '/lib/public/Security/CSRF/ICsrfValidator.php',
|
||||
'OCP\\Security\\Events\\GenerateSecurePasswordEvent' => __DIR__ . '/../../..' . '/lib/public/Security/Events/GenerateSecurePasswordEvent.php',
|
||||
'OCP\\Security\\Events\\ValidatePasswordPolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/Events/ValidatePasswordPolicyEvent.php',
|
||||
'OCP\\Security\\FeaturePolicy\\AddFeaturePolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/FeaturePolicy/AddFeaturePolicyEvent.php',
|
||||
@@ -2073,6 +2074,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
||||
'OC\\Security\\CSRF\\CsrfToken' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfToken.php',
|
||||
'OC\\Security\\CSRF\\CsrfTokenGenerator' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfTokenGenerator.php',
|
||||
'OC\\Security\\CSRF\\CsrfTokenManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfTokenManager.php',
|
||||
'OC\\Security\\CSRF\\CsrfValidator' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfValidator.php',
|
||||
'OC\\Security\\CSRF\\TokenStorage\\SessionStorage' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/TokenStorage/SessionStorage.php',
|
||||
'OC\\Security\\Certificate' => __DIR__ . '/../../..' . '/lib/private/Security/Certificate.php',
|
||||
'OC\\Security\\CertificateManager' => __DIR__ . '/../../..' . '/lib/private/Security/CertificateManager.php',
|
||||
|
||||
@@ -59,6 +59,8 @@ use OCP\IServerContainer;
|
||||
use OCP\ISession;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Security\Bruteforce\IThrottler;
|
||||
use OCP\Security\CSRF\ICsrfValidator;
|
||||
use OCP\Security\Ip\IRemoteAddress;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -206,6 +208,7 @@ class DIContainer extends SimpleContainer implements IAppContainer {
|
||||
$c->get(AuthorizedGroupMapper::class),
|
||||
$c->get(IUserSession::class),
|
||||
$c->get(IRemoteAddress::class),
|
||||
$c->get(ICsrfValidator::class),
|
||||
);
|
||||
$dispatcher->registerMiddleware($securityMiddleware);
|
||||
$dispatcher->registerMiddleware($c->get(CSPMiddleware::class));
|
||||
|
||||
@@ -21,6 +21,7 @@ use OCP\AppFramework\Middleware;
|
||||
use OCP\IRequest;
|
||||
use OCP\ISession;
|
||||
use OCP\Security\Bruteforce\IThrottler;
|
||||
use OCP\Security\CSRF\ICsrfValidator;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ReflectionMethod;
|
||||
|
||||
@@ -46,6 +47,7 @@ class CORSMiddleware extends Middleware {
|
||||
Session $session,
|
||||
IThrottler $throttler,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly ICsrfValidator $csrfValidator,
|
||||
) {
|
||||
$this->request = $request;
|
||||
$this->reflector = $reflector;
|
||||
@@ -74,7 +76,7 @@ class CORSMiddleware extends Middleware {
|
||||
$pass = array_key_exists('PHP_AUTH_PW', $this->request->server) ? $this->request->server['PHP_AUTH_PW'] : null;
|
||||
|
||||
// Allow to use the current session if a CSRF token is provided
|
||||
if ($this->request->passesCSRFCheck()) {
|
||||
if ($this->csrfValidator->validate($this->request)) {
|
||||
return;
|
||||
}
|
||||
// Skip CORS check for requests with AppAPI auth.
|
||||
|
||||
@@ -43,6 +43,7 @@ use OCP\INavigationManager;
|
||||
use OCP\IRequest;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Security\CSRF\ICsrfValidator;
|
||||
use OCP\Security\Ip\IRemoteAddress;
|
||||
use OCP\Util;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -73,6 +74,7 @@ class SecurityMiddleware extends Middleware {
|
||||
private AuthorizedGroupMapper $groupAuthorizationMapper,
|
||||
private IUserSession $userSession,
|
||||
private IRemoteAddress $remoteAddress,
|
||||
private readonly ICsrfValidator $csrfValidator,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -228,7 +230,7 @@ class SecurityMiddleware extends Middleware {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !$this->request->passesCSRFCheck();
|
||||
return !$this->csrfValidator->validate($this->request);
|
||||
}
|
||||
|
||||
private function isValidOCSRequest(): bool {
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace OC;
|
||||
|
||||
use OCP\IEventSource;
|
||||
use OCP\IRequest;
|
||||
use OCP\Security\CSRF\ICsrfValidator;
|
||||
|
||||
class EventSource implements IEventSource {
|
||||
private bool $fallback = false;
|
||||
@@ -18,6 +19,7 @@ class EventSource implements IEventSource {
|
||||
|
||||
public function __construct(
|
||||
private IRequest $request,
|
||||
private ICsrfValidator $csrfValidator,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -54,7 +56,7 @@ class EventSource implements IEventSource {
|
||||
header('Location: ' . \OC::$WEBROOT);
|
||||
exit();
|
||||
}
|
||||
if (!$this->request->passesCSRFCheck()) {
|
||||
if (!$this->csrfValidator->validate($this->request)) {
|
||||
$this->send('error', 'Possible CSRF attack. Connection will be closed.');
|
||||
$this->close();
|
||||
exit();
|
||||
|
||||
@@ -12,14 +12,19 @@ namespace OC;
|
||||
use OCP\IEventSource;
|
||||
use OCP\IEventSourceFactory;
|
||||
use OCP\IRequest;
|
||||
use OCP\Security\CSRF\ICsrfValidator;
|
||||
|
||||
class EventSourceFactory implements IEventSourceFactory {
|
||||
public function __construct(
|
||||
private IRequest $request,
|
||||
private ICsrfValidator $csrfValidator,
|
||||
) {
|
||||
}
|
||||
|
||||
public function create(): IEventSource {
|
||||
return new EventSource($this->request);
|
||||
return new EventSource(
|
||||
$this->request,
|
||||
$this->csrfValidator,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OC\Security\CSRF;
|
||||
|
||||
use OCP\IRequest;
|
||||
use OCP\Security\CSRF\ICsrfValidator;
|
||||
|
||||
class CsrfValidator implements ICsrfValidator {
|
||||
public function __construct(
|
||||
private CsrfTokenManager $csrfTokenManager
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(IRequest $request): bool {
|
||||
if (!$request->passesStrictCookieCheck()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($request->getHeader('OCS-APIRequest') !== '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$token = $request->getParam('requesttoken', '');
|
||||
if ($token === '') {
|
||||
$token = $request->getHeader('REQUESTTOKEN');
|
||||
}
|
||||
if ($token === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$token = new CsrfToken($token);
|
||||
|
||||
return $this->csrfTokenManager->isTokenValid($token);
|
||||
}
|
||||
}
|
||||
@@ -101,6 +101,7 @@ use OC\Security\Crypto;
|
||||
use OC\Security\CSP\ContentSecurityPolicyManager;
|
||||
use OC\Security\CSP\ContentSecurityPolicyNonceManager;
|
||||
use OC\Security\CSRF\CsrfTokenManager;
|
||||
use OC\Security\CSRF\CsrfValidator;
|
||||
use OC\Security\CSRF\TokenStorage\SessionStorage;
|
||||
use OC\Security\Hasher;
|
||||
use OC\Security\Ip\RemoteAddress;
|
||||
@@ -209,6 +210,8 @@ use OCP\RichObjectStrings\IRichTextFormatter;
|
||||
use OCP\RichObjectStrings\IValidator;
|
||||
use OCP\Route\IRouter;
|
||||
use OCP\Security\Bruteforce\IThrottler;
|
||||
use OCP\Security\CSRF\ICsrfValidator;
|
||||
use OCP\Security\IContentSecurityPolicyManager;
|
||||
use OCP\Security\ICredentialsManager;
|
||||
use OCP\Security\ICrypto;
|
||||
use OCP\Security\IHasher;
|
||||
@@ -1245,6 +1248,8 @@ class Server extends ServerContainer implements IServerContainer {
|
||||
|
||||
$this->registerAlias(ISignatureManager::class, SignatureManager::class);
|
||||
|
||||
$this->registerAlias(ICsrfValidator::class, CsrfValidator::class);
|
||||
|
||||
$this->connectDispatcher();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
*/
|
||||
|
||||
use OC\Authentication\TwoFactorAuth\Manager as TwoFactorAuthManager;
|
||||
use OCP\IRequest;
|
||||
use OCP\Security\CSRF\ICsrfValidator;
|
||||
|
||||
class OC_JSON {
|
||||
/**
|
||||
@@ -45,12 +47,16 @@ class OC_JSON {
|
||||
* @suppress PhanDeprecatedFunction
|
||||
*/
|
||||
public static function callCheck() {
|
||||
if (!\OC::$server->getRequest()->passesStrictCookieCheck()) {
|
||||
$request = OC::$server->get(IRequest::class);
|
||||
|
||||
if (!$request->passesStrictCookieCheck()) {
|
||||
header('Location: ' . \OC::$WEBROOT);
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!\OC::$server->getRequest()->passesCSRFCheck()) {
|
||||
$csrfValidator = OC::$server->get(ICsrfValidator::class);
|
||||
|
||||
if (!$csrfValidator->validate($request)) {
|
||||
$l = \OC::$server->getL10N('lib');
|
||||
self::error([ 'data' => [ 'message' => $l->t('Token expired. Please reload page.'), 'error' => 'token_expired' ]]);
|
||||
exit();
|
||||
|
||||
@@ -177,6 +177,7 @@ interface IRequest {
|
||||
*
|
||||
* @return bool true if CSRF check passed
|
||||
* @since 6.0.0
|
||||
* @deprecated 33.0.0 use \OCP\Security\CSRF\ICsrfValidator::validate instead
|
||||
*/
|
||||
public function passesCSRFCheck(): bool;
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCP\Security\CSRF;
|
||||
|
||||
use OCP\IRequest;
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* @since 33.0.0
|
||||
*/
|
||||
interface ICsrfValidator {
|
||||
/**
|
||||
* Check if a request uses a valid csrf token.
|
||||
*
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function validate(IRequest $request): bool;
|
||||
}
|
||||
@@ -15,6 +15,9 @@ use OC\Authentication\Login\LoginData;
|
||||
use OC\Authentication\Login\LoginResult;
|
||||
use OC\Authentication\TwoFactorAuth\Manager;
|
||||
use OC\Core\Controller\LoginController;
|
||||
use OC\Security\CSRF\CsrfToken;
|
||||
use OC\Security\CSRF\CsrfTokenManager;
|
||||
use OC\Security\CSRF\CsrfValidator;
|
||||
use OC\User\Session;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\Http\RedirectResponse;
|
||||
@@ -80,6 +83,9 @@ class LoginControllerTest extends TestCase {
|
||||
/** @var IAppManager|MockObject */
|
||||
private $appManager;
|
||||
|
||||
private CsrfTokenManager $csrfTokenManager;
|
||||
private CsrfValidator $csrfValidator;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
@@ -102,6 +108,8 @@ class LoginControllerTest extends TestCase {
|
||||
->willReturnCallback(function ($text, $parameters = []) {
|
||||
return vsprintf($text, $parameters);
|
||||
});
|
||||
$this->csrfTokenManager = $this->createMock(CsrfTokenManager::class);
|
||||
$this->csrfValidator = new CsrfValidator($this->csrfTokenManager);
|
||||
|
||||
|
||||
$this->request->method('getRemoteAddress')
|
||||
@@ -130,6 +138,7 @@ class LoginControllerTest extends TestCase {
|
||||
$this->notificationManager,
|
||||
$this->l,
|
||||
$this->appManager,
|
||||
$this->csrfValidator,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -483,9 +492,16 @@ class LoginControllerTest extends TestCase {
|
||||
$loginChain = $this->createMock(LoginChain::class);
|
||||
$trustedDomainHelper = $this->createMock(ITrustedDomainHelper::class);
|
||||
$trustedDomainHelper->method('isTrustedUrl')->willReturn(true);
|
||||
$this->request
|
||||
->expects($this->once())
|
||||
->method('passesCSRFCheck')
|
||||
$this->request->expects($this->once())
|
||||
->method('passesStrictCookieCheck')
|
||||
->willReturn(true);
|
||||
$this->request->expects($this->once())
|
||||
->method('getParam')
|
||||
->with('requesttoken', '')
|
||||
->willReturn('foobar');
|
||||
$this->csrfTokenManager->expects($this->once())
|
||||
->method('isTokenValid')
|
||||
->with(new CsrfToken('foobar'))
|
||||
->willReturn(true);
|
||||
$loginData = new LoginData(
|
||||
$this->request,
|
||||
@@ -520,9 +536,16 @@ class LoginControllerTest extends TestCase {
|
||||
$loginChain = $this->createMock(LoginChain::class);
|
||||
$trustedDomainHelper = $this->createMock(ITrustedDomainHelper::class);
|
||||
$trustedDomainHelper->method('isTrustedUrl')->willReturn(true);
|
||||
$this->request
|
||||
->expects($this->once())
|
||||
->method('passesCSRFCheck')
|
||||
$this->request->expects($this->once())
|
||||
->method('passesStrictCookieCheck')
|
||||
->willReturn(true);
|
||||
$this->request->expects($this->once())
|
||||
->method('getParam')
|
||||
->with('requesttoken', '')
|
||||
->willReturn('foobar');
|
||||
$this->csrfTokenManager->expects($this->once())
|
||||
->method('isTokenValid')
|
||||
->with(new CsrfToken('foobar'))
|
||||
->willReturn(true);
|
||||
$loginData = new LoginData(
|
||||
$this->request,
|
||||
@@ -554,9 +577,16 @@ class LoginControllerTest extends TestCase {
|
||||
$loginChain = $this->createMock(LoginChain::class);
|
||||
$trustedDomainHelper = $this->createMock(ITrustedDomainHelper::class);
|
||||
$trustedDomainHelper->method('isTrustedUrl')->willReturn(true);
|
||||
$this->request
|
||||
->expects($this->once())
|
||||
->method('passesCSRFCheck')
|
||||
$this->request->expects($this->once())
|
||||
->method('passesStrictCookieCheck')
|
||||
->willReturn(true);
|
||||
$this->request->expects($this->once())
|
||||
->method('getParam')
|
||||
->with('requesttoken', '')
|
||||
->willReturn('foobar');
|
||||
$this->csrfTokenManager->expects($this->once())
|
||||
->method('isTokenValid')
|
||||
->with(new CsrfToken('foobar'))
|
||||
->willReturn(false);
|
||||
$this->userSession
|
||||
->method('isLoggedIn')
|
||||
@@ -586,9 +616,16 @@ class LoginControllerTest extends TestCase {
|
||||
$loginChain = $this->createMock(LoginChain::class);
|
||||
$trustedDomainHelper = $this->createMock(ITrustedDomainHelper::class);
|
||||
$trustedDomainHelper->method('isTrustedUrl')->willReturn(true);
|
||||
$this->request
|
||||
->expects($this->once())
|
||||
->method('passesCSRFCheck')
|
||||
$this->request->expects($this->once())
|
||||
->method('passesStrictCookieCheck')
|
||||
->willReturn(true);
|
||||
$this->request->expects($this->once())
|
||||
->method('getParam')
|
||||
->with('requesttoken', '')
|
||||
->willReturn('foobar');
|
||||
$this->csrfTokenManager->expects($this->once())
|
||||
->method('isTokenValid')
|
||||
->with(new CsrfToken('foobar'))
|
||||
->willReturn(false);
|
||||
$this->userSession
|
||||
->method('isLoggedIn')
|
||||
@@ -620,9 +657,16 @@ class LoginControllerTest extends TestCase {
|
||||
$loginChain = $this->createMock(LoginChain::class);
|
||||
$trustedDomainHelper = $this->createMock(ITrustedDomainHelper::class);
|
||||
$trustedDomainHelper->method('isTrustedUrl')->willReturn(true);
|
||||
$this->request
|
||||
->expects($this->once())
|
||||
->method('passesCSRFCheck')
|
||||
$this->request->expects($this->once())
|
||||
->method('passesStrictCookieCheck')
|
||||
->willReturn(true);
|
||||
$this->request->expects($this->once())
|
||||
->method('getParam')
|
||||
->with('requesttoken', '')
|
||||
->willReturn('foobar');
|
||||
$this->csrfTokenManager->expects($this->once())
|
||||
->method('isTokenValid')
|
||||
->with(new CsrfToken('foobar'))
|
||||
->willReturn(true);
|
||||
$loginData = new LoginData(
|
||||
$this->request,
|
||||
@@ -653,9 +697,16 @@ class LoginControllerTest extends TestCase {
|
||||
$loginChain = $this->createMock(LoginChain::class);
|
||||
$trustedDomainHelper = $this->createMock(ITrustedDomainHelper::class);
|
||||
$trustedDomainHelper->method('isTrustedUrl')->willReturn(true);
|
||||
$this->request
|
||||
->expects($this->once())
|
||||
->method('passesCSRFCheck')
|
||||
$this->request->expects($this->once())
|
||||
->method('passesStrictCookieCheck')
|
||||
->willReturn(true);
|
||||
$this->request->expects($this->once())
|
||||
->method('getParam')
|
||||
->with('requesttoken', '')
|
||||
->willReturn('foobar');
|
||||
$this->csrfTokenManager->expects($this->once())
|
||||
->method('isTokenValid')
|
||||
->with(new CsrfToken('foobar'))
|
||||
->willReturn(true);
|
||||
$loginPageUrl = '/login?redirect_url=/apps/files';
|
||||
$loginData = new LoginData(
|
||||
|
||||
@@ -12,6 +12,8 @@ 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\Security\CSRF\CsrfTokenManager;
|
||||
use OC\Security\CSRF\CsrfValidator;
|
||||
use OC\User\Session;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
@@ -30,6 +32,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;
|
||||
@@ -40,6 +44,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();
|
||||
@@ -104,7 +110,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();
|
||||
@@ -132,7 +138,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');
|
||||
@@ -156,7 +162,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);
|
||||
@@ -188,7 +194,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);
|
||||
@@ -227,7 +233,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);
|
||||
}
|
||||
@@ -258,7 +264,7 @@ class CORSMiddlewareTest extends \Test\TestCase {
|
||||
->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 = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler, $this->logger, $this->csrfValidator);
|
||||
|
||||
$middleware->beforeController($this->controller, $method);
|
||||
}
|
||||
@@ -289,7 +295,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);
|
||||
}
|
||||
@@ -303,7 +309,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);
|
||||
@@ -319,7 +325,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);
|
||||
@@ -338,7 +344,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'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,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;
|
||||
@@ -69,6 +72,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();
|
||||
@@ -88,6 +93,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);
|
||||
@@ -122,7 +129,8 @@ class SecurityMiddlewareTest extends \Test\TestCase {
|
||||
$this->l10n,
|
||||
$this->authorizedGroupMapper,
|
||||
$this->userSession,
|
||||
$remoteIpAddress
|
||||
$remoteIpAddress,
|
||||
$this->csrfValidator,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -323,12 +331,18 @@ class SecurityMiddlewareTest extends \Test\TestCase {
|
||||
public function testCsrfCheck(string $method): void {
|
||||
$this->expectException(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);
|
||||
}
|
||||
@@ -345,11 +359,20 @@ class SecurityMiddlewareTest extends \Test\TestCase {
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\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);
|
||||
@@ -360,12 +383,21 @@ class SecurityMiddlewareTest extends \Test\TestCase {
|
||||
public function testFailCsrfCheck(string $method): void {
|
||||
$this->expectException(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);
|
||||
@@ -380,6 +412,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);
|
||||
@@ -431,6 +469,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';
|
||||
}
|
||||
@@ -439,9 +480,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);
|
||||
|
||||
|
||||
@@ -10,11 +10,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);
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user