Skip to content

Commit

Permalink
feat: move csrf validation out of request
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Kesselberg <[email protected]>
  • Loading branch information
kesselb committed May 29, 2024
1 parent a5fd623 commit a0dd76d
Show file tree
Hide file tree
Showing 24 changed files with 371 additions and 71 deletions.
2 changes: 2 additions & 0 deletions apps/dav/appinfo/v1/caldav.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use OCA\DAV\Connector\Sabre\MaintenancePlugin;
use OCA\DAV\Connector\Sabre\Principal;
use OCP\Accounts\IAccountManager;
use OCP\Security\CSRF\ICsrfValidator;
use Psr\Log\LoggerInterface;

$authBackend = new Auth(
Expand All @@ -24,6 +25,7 @@
\OC::$server->getRequest(),
\OC::$server->getTwoFactorAuthManager(),
\OC::$server->getBruteForceThrottler(),
\OC::$server->get(ICsrfValidator::class),
'principals/'
);
$principalBackend = new Principal(
Expand Down
2 changes: 2 additions & 0 deletions apps/dav/appinfo/v1/carddav.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use OCA\DAV\Connector\Sabre\Principal;
use OCP\Accounts\IAccountManager;
use OCP\App\IAppManager;
use OCP\Security\CSRF\ICsrfValidator;
use Psr\Log\LoggerInterface;
use Sabre\CardDAV\Plugin;

Expand All @@ -27,6 +28,7 @@
\OC::$server->getRequest(),
\OC::$server->getTwoFactorAuthManager(),
\OC::$server->getBruteForceThrottler(),
\OC::$server->get(ICsrfValidator::class),
'principals/'
);
$principalBackend = new Principal(
Expand Down
3 changes: 3 additions & 0 deletions apps/dav/appinfo/v1/webdav.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/

use OCP\Security\CSRF\ICsrfValidator;
use Psr\Log\LoggerInterface;

// no php execution timeout for webdav
Expand Down Expand Up @@ -38,6 +40,7 @@
\OC::$server->getRequest(),
\OC::$server->getTwoFactorAuthManager(),
\OC::$server->getBruteForceThrottler(),
\OC::$server->get(ICsrfValidator::class),
'principals/'
);
$authPlugin = new \Sabre\DAV\Auth\Plugin($authBackend);
Expand Down
4 changes: 3 additions & 1 deletion apps/dav/lib/Connector/Sabre/Auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use OCP\ISession;
use OCP\Security\Bruteforce\IThrottler;
use OCP\Security\Bruteforce\MaxDelayReached;
use OCP\Security\CSRF\ICsrfValidator;
use Psr\Log\LoggerInterface;
use Sabre\DAV\Auth\Backend\AbstractBasic;
use Sabre\DAV\Exception\NotAuthenticated;
Expand All @@ -39,6 +40,7 @@ public function __construct(ISession $session,
IRequest $request,
Manager $twoFactorManager,
IThrottler $throttler,
private ICsrfValidator $csrfValidator,
string $principalPrefix = 'principals/users/') {
$this->session = $session;
$this->userSession = $userSession;
Expand Down Expand Up @@ -164,7 +166,7 @@ private function requiresCSRFCheck(): bool {
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') {
Expand Down
5 changes: 4 additions & 1 deletion apps/dav/lib/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
use OCP\IRequest;
use OCP\Profiler\IProfiler;
use OCP\SabrePluginEvent;
use OCP\Security\Bruteforce\IThrottler;
use OCP\Security\CSRF\ICsrfValidator;
use Psr\Log\LoggerInterface;
use Sabre\CardDAV\VCFExportPlugin;
use Sabre\DAV\Auth\Plugin;
Expand Down Expand Up @@ -95,7 +97,8 @@ public function __construct(IRequest $request, string $baseUri) {
\OC::$server->getUserSession(),
\OC::$server->getRequest(),
\OC::$server->getTwoFactorAuthManager(),
\OC::$server->getBruteForceThrottler()
\OC::$server->get(IThrottler::class),
\OC::$server->get(ICsrfValidator::class)
);

// Set URL explicitly due to reverse-proxy situations
Expand Down
31 changes: 18 additions & 13 deletions apps/dav/tests/unit/Connector/Sabre/AuthTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
namespace OCA\DAV\Tests\unit\Connector\Sabre;

use OC\Authentication\TwoFactorAuth\Manager;
use OC\Security\CSRF\CsrfValidator;
use OC\User\Session;
use OCP\IRequest;
use OCP\ISession;
use OCP\IUser;
use OCP\Security\Bruteforce\IThrottler;
use OCP\Security\CSRF\ICsrfValidator;
use Sabre\DAV\Server;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
Expand All @@ -37,6 +39,7 @@ class AuthTest extends TestCase {
private $twoFactorManager;
/** @var IThrottler */
private $throttler;
private ICsrfValidator $csrfValidator;

protected function setUp(): void {
parent::setUp();
Expand All @@ -52,12 +55,14 @@ protected function setUp(): void {
$this->throttler = $this->getMockBuilder(IThrottler::class)
->disableOriginalConstructor()
->getMock();
$this->csrfValidator = $this->createMock(ICsrfValidator::class);
$this->auth = new \OCA\DAV\Connector\Sabre\Auth(
$this->session,
$this->userSession,
$this->request,
$this->twoFactorManager,
$this->throttler
$this->throttler,
$this->csrfValidator,
);
}

Expand Down Expand Up @@ -248,9 +253,9 @@ public function testAuthenticateAlreadyLoggedInWithoutCsrfTokenForNonGet(): void
->expects($this->any())
->method('getUser')
->willReturn($user);
$this->request
$this->csrfValidator
->expects($this->once())
->method('passesCSRFCheck')
->method('validate')
->willReturn(false);

$expectedResponse = [
Expand Down Expand Up @@ -295,9 +300,9 @@ public function testAuthenticateAlreadyLoggedInWithoutCsrfTokenAndCorrectlyDavAu
->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);
}
Expand Down Expand Up @@ -340,9 +345,9 @@ public function testAuthenticateAlreadyLoggedInWithoutTwoFactorChallengePassed()
->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')
Expand Down Expand Up @@ -389,9 +394,9 @@ public function testAuthenticateAlreadyLoggedInWithoutCsrfTokenAndIncorrectlyDav
->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);
}
Expand Down Expand Up @@ -430,9 +435,9 @@ public function testAuthenticateAlreadyLoggedInWithoutCsrfTokenForNonGetAndDeskt
->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);
Expand Down Expand Up @@ -499,9 +504,9 @@ public function testAuthenticateAlreadyLoggedInWithCsrfTokenForGet(): void {
->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);
Expand Down
6 changes: 4 additions & 2 deletions core/Controller/LoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
use OCP\IUserManager;
use OCP\Notification\IManager;
use OCP\Security\Bruteforce\IThrottler;
use OCP\Security\CSRF\ICsrfValidator;
use OCP\Util;

class LoginController extends Controller {
Expand All @@ -60,6 +61,7 @@ public function __construct(
private IManager $manager,
private IL10N $l10n,
private IAppManager $appManager,
private ICsrfValidator $csrfValidator,
) {
parent::__construct($appName, $request);
}
Expand Down Expand Up @@ -207,7 +209,7 @@ private function setPasswordResetInitialState(?string $username): void {
$this->canResetPassword($passwordLink, $user)
);
}

/**
* Sets the initial state of whether or not a user is allowed to login with their email
* initial state is passed in the array of 1 for email allowed and 0 for not allowed
Expand Down Expand Up @@ -284,7 +286,7 @@ public function tryLogin(Chain $loginChain,
?string $redirect_url = null,
string $timezone = '',
string $timezone_offset = ''): RedirectResponse {
if (!$this->request->passesCSRFCheck()) {
if (!$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
Expand Down
2 changes: 2 additions & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,7 @@
'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',
Expand Down Expand Up @@ -1775,6 +1776,7 @@
'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',
Expand Down
2 changes: 2 additions & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,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',
Expand Down Expand Up @@ -1816,6 +1817,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',
Expand Down
7 changes: 5 additions & 2 deletions lib/private/AppFramework/DependencyInjection/DIContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Security\Bruteforce\IThrottler;
use OCP\Security\CSRF\ICsrfValidator;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;

Expand Down Expand Up @@ -207,7 +208,8 @@ public function __construct(string $appName, array $urlParams = [], ?ServerConta
$c->get(IRequest::class),
$c->get(IControllerMethodReflector::class),
$c->get(IUserSession::class),
$c->get(IThrottler::class)
$c->get(IThrottler::class),
$c->get(ICsrfValidator::class),
)
);
$dispatcher->registerMiddleware(
Expand All @@ -231,7 +233,8 @@ public function __construct(string $appName, array $urlParams = [], ?ServerConta
$server->getAppManager(),
$server->getL10N('lib'),
$c->get(AuthorizedGroupMapper::class),
$server->get(IUserSession::class)
$server->get(IUserSession::class),
$c->get(ICsrfValidator::class),
);
$dispatcher->registerMiddleware($securityMiddleware);
$dispatcher->registerMiddleware(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use OCP\IRequest;
use OCP\ISession;
use OCP\Security\Bruteforce\IThrottler;
use OCP\Security\CSRF\ICsrfValidator;
use ReflectionMethod;

/**
Expand All @@ -42,7 +43,8 @@ class CORSMiddleware extends Middleware {
public function __construct(IRequest $request,
ControllerMethodReflector $reflector,
Session $session,
IThrottler $throttler) {
IThrottler $throttler,
private ICsrfValidator $csrfValidator) {
$this->request = $request;
$this->reflector = $reflector;
$this->session = $session;
Expand Down Expand Up @@ -70,7 +72,7 @@ public function beforeController($controller, $methodName) {
$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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Security\CSRF\ICsrfValidator;
use OCP\Util;
use Psr\Log\LoggerInterface;
use ReflectionMethod;
Expand Down Expand Up @@ -86,7 +87,8 @@ public function __construct(IRequest $request,
IAppManager $appManager,
IL10N $l10n,
AuthorizedGroupMapper $mapper,
IUserSession $userSession
IUserSession $userSession,
private ICsrfValidator $csrfValidator
) {
$this->navigationManager = $navigationManager;
$this->request = $request;
Expand Down Expand Up @@ -213,7 +215,7 @@ private function isInvalidCSRFRequired(ReflectionMethod $reflectionMethod): bool
return false;
}

return !$this->request->passesCSRFCheck();
return !$this->csrfValidator->validate($this->request);
}

private function isValidOCSRequest(): bool {
Expand Down
4 changes: 3 additions & 1 deletion lib/private/EventSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

use OCP\IEventSource;
use OCP\IRequest;
use OCP\Security\CSRF\ICsrfValidator;

class EventSource implements IEventSource {
private bool $fallback = false;
Expand All @@ -18,6 +19,7 @@ class EventSource implements IEventSource {

public function __construct(
private IRequest $request,
private ICsrfValidator $csrfValidator,
) {
}

Expand Down Expand Up @@ -54,7 +56,7 @@ protected function init(): void {
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();
Expand Down
7 changes: 6 additions & 1 deletion lib/private/EventSourceFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@
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,
);
}
}
Loading

0 comments on commit a0dd76d

Please sign in to comment.