diff options
Diffstat (limited to 'lib/private/AppFramework/Middleware')
13 files changed, 106 insertions, 27 deletions
diff --git a/lib/private/AppFramework/Middleware/FlowV2EphemeralSessionsMiddleware.php b/lib/private/AppFramework/Middleware/FlowV2EphemeralSessionsMiddleware.php new file mode 100644 index 00000000000..b69b129f798 --- /dev/null +++ b/lib/private/AppFramework/Middleware/FlowV2EphemeralSessionsMiddleware.php @@ -0,0 +1,64 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OC\AppFramework\Middleware; + +use OC\AppFramework\Utility\ControllerMethodReflector; +use OC\Core\Controller\ClientFlowLoginV2Controller; +use OC\Core\Controller\TwoFactorChallengeController; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\Attribute\PublicPage; +use OCP\AppFramework\Middleware; +use OCP\ISession; +use OCP\IUserSession; +use Psr\Log\LoggerInterface; +use ReflectionMethod; + +// Will close the session if the user session is ephemeral. +// Happens when the user logs in via the login flow v2. +class FlowV2EphemeralSessionsMiddleware extends Middleware { + public function __construct( + private ISession $session, + private IUserSession $userSession, + private ControllerMethodReflector $reflector, + private LoggerInterface $logger, + ) { + } + + public function beforeController(Controller $controller, string $methodName) { + if (!$this->session->get(ClientFlowLoginV2Controller::EPHEMERAL_NAME)) { + return; + } + + if ( + $controller instanceof ClientFlowLoginV2Controller + && ($methodName === 'grantPage' || $methodName === 'generateAppPassword') + ) { + return; + } + + if ($controller instanceof TwoFactorChallengeController) { + return; + } + + $reflectionMethod = new ReflectionMethod($controller, $methodName); + if (!empty($reflectionMethod->getAttributes(PublicPage::class))) { + return; + } + + if ($this->reflector->hasAnnotation('PublicPage')) { + return; + } + + $this->logger->info('Closing user and PHP session for ephemeral session', [ + 'controller' => $controller::class, + 'method' => $methodName, + ]); + $this->userSession->logout(); + $this->session->close(); + } +} diff --git a/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php b/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php index 17b423164f6..08b30092155 100644 --- a/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php +++ b/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php @@ -29,7 +29,7 @@ class NotModifiedMiddleware extends Middleware { } $modifiedSinceHeader = $this->request->getHeader('IF_MODIFIED_SINCE'); - if ($modifiedSinceHeader !== '' && $response->getLastModified() !== null && trim($modifiedSinceHeader) === $response->getLastModified()->format(\DateTimeInterface::RFC2822)) { + if ($modifiedSinceHeader !== '' && $response->getLastModified() !== null && trim($modifiedSinceHeader) === $response->getLastModified()->format(\DateTimeInterface::RFC7231)) { $response->setStatus(Http::STATUS_NOT_MODIFIED); return $response; } diff --git a/lib/private/AppFramework/Middleware/OCSMiddleware.php b/lib/private/AppFramework/Middleware/OCSMiddleware.php index 46612bf0d29..64f4b0054de 100644 --- a/lib/private/AppFramework/Middleware/OCSMiddleware.php +++ b/lib/private/AppFramework/Middleware/OCSMiddleware.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php b/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php index c80d06c90ae..5df4009b094 100644 --- a/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php +++ b/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php b/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php index 2b3025fccff..83e799e3d3b 100644 --- a/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php +++ b/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -120,7 +121,7 @@ class PublicShareMiddleware extends Middleware { private function throttle($bruteforceProtectionAction, $token): void { $ip = $this->request->getRemoteAddress(); - $this->throttler->sleepDelay($ip, $bruteforceProtectionAction); + $this->throttler->sleepDelayOrThrowOnMax($ip, $bruteforceProtectionAction); $this->throttler->registerAttempt($bruteforceProtectionAction, $ip, ['token' => $token]); } } diff --git a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php index 40af67739d6..4453f5a7d4b 100644 --- a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php @@ -68,8 +68,8 @@ class CORSMiddleware extends Middleware { // ensure that @CORS annotated API routes are not used in conjunction // with session authentication since this enables CSRF attack vectors - if ($this->hasAnnotationOrAttribute($reflectionMethod, 'CORS', CORS::class) && - (!$this->hasAnnotationOrAttribute($reflectionMethod, 'PublicPage', PublicPage::class) || $this->session->isLoggedIn())) { + if ($this->hasAnnotationOrAttribute($reflectionMethod, 'CORS', CORS::class) + && (!$this->hasAnnotationOrAttribute($reflectionMethod, 'PublicPage', PublicPage::class) || $this->session->isLoggedIn())) { $user = array_key_exists('PHP_AUTH_USER', $this->request->server) ? $this->request->server['PHP_AUTH_USER'] : null; $pass = array_key_exists('PHP_AUTH_PW', $this->request->server) ? $this->request->server['PHP_AUTH_PW'] : null; @@ -134,10 +134,10 @@ class CORSMiddleware extends Middleware { // allow credentials headers must not be true or CSRF is possible // otherwise foreach ($response->getHeaders() as $header => $value) { - if (strtolower($header) === 'access-control-allow-credentials' && - strtolower(trim($value)) === 'true') { - $msg = 'Access-Control-Allow-Credentials must not be ' . - 'set to true in order to prevent CSRF'; + if (strtolower($header) === 'access-control-allow-credentials' + && strtolower(trim($value)) === 'true') { + $msg = 'Access-Control-Allow-Credentials must not be ' + . 'set to true in order to prevent CSRF'; throw new SecurityException($msg); } } diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php index 646a5240bfc..53fbaaf5ed2 100644 --- a/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php +++ b/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php index 91f1dba874d..0380c6781aa 100644 --- a/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php +++ b/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php index 7e950f2c976..ca30f736fbc 100644 --- a/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php +++ b/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -14,7 +15,7 @@ use OCP\AppFramework\Http; * @package OC\AppFramework\Middleware\Security\Exceptions */ class NotConfirmedException extends SecurityException { - public function __construct() { - parent::__construct('Password confirmation is required', Http::STATUS_FORBIDDEN); + public function __construct(string $message = 'Password confirmation is required') { + parent::__construct($message, Http::STATUS_FORBIDDEN); } } diff --git a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php index 2e3ed02ed34..0facbffe504 100644 --- a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -79,8 +80,12 @@ class PasswordConfirmationMiddleware extends Middleware { if ($this->isPasswordConfirmationStrict($reflectionMethod)) { $authHeader = $this->request->getHeader('Authorization'); + if (!str_starts_with(strtolower($authHeader), 'basic ')) { + throw new NotConfirmedException('Required authorization header missing'); + } [, $password] = explode(':', base64_decode(substr($authHeader, 6)), 2); - $loginResult = $this->userManager->checkPassword($user->getUid(), $password); + $loginName = $this->session->get('loginname'); + $loginResult = $this->userManager->checkPassword($loginName, $password); if ($loginResult === false) { throw new NotConfirmedException(); } diff --git a/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php b/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php index f4d120ebc30..2d19be97993 100644 --- a/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace OC\AppFramework\Middleware\Security; use OC\AppFramework\Utility\ControllerMethodReflector; +use OC\Security\Ip\BruteforceAllowList; use OC\Security\RateLimiting\Exception\RateLimitExceededException; use OC\Security\RateLimiting\Limiter; use OC\User\Session; @@ -20,6 +21,7 @@ use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Middleware; +use OCP\IAppConfig; use OCP\IRequest; use OCP\ISession; use OCP\IUserSession; @@ -53,6 +55,8 @@ class RateLimitingMiddleware extends Middleware { protected ControllerMethodReflector $reflector, protected Limiter $limiter, protected ISession $session, + protected IAppConfig $appConfig, + protected BruteforceAllowList $bruteForceAllowList, ) { } @@ -73,6 +77,11 @@ class RateLimitingMiddleware extends Middleware { $rateLimit = $this->readLimitFromAnnotationOrAttribute($controller, $methodName, 'UserRateThrottle', UserRateLimit::class); if ($rateLimit !== null) { + if ($this->appConfig->getValueBool('bruteforcesettings', 'apply_allowlist_to_ratelimit') + && $this->bruteForceAllowList->isBypassListed($this->request->getRemoteAddress())) { + return; + } + $this->limiter->registerUserRequest( $rateLimitIdentifier, $rateLimit->getLimit(), diff --git a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php index efe56e0b124..097ed1b2b8f 100644 --- a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -13,16 +14,10 @@ use OCP\AppFramework\Http\Response; use OCP\AppFramework\Middleware; class SameSiteCookieMiddleware extends Middleware { - /** @var Request */ - private $request; - - /** @var ControllerMethodReflector */ - private $reflector; - - public function __construct(Request $request, - ControllerMethodReflector $reflector) { - $this->request = $request; - $this->reflector = $reflector; + public function __construct( + private Request $request, + private ControllerMethodReflector $reflector, + ) { } public function beforeController($controller, $methodName) { @@ -58,7 +53,7 @@ class SameSiteCookieMiddleware extends Middleware { throw $exception; } - protected function setSameSiteCookie() { + protected function setSameSiteCookie(): void { $cookieParams = $this->request->getCookieParams(); $secureCookie = ($cookieParams['secure'] === true) ? 'secure; ' : ''; $policies = [ diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index 88987290244..e3a293e0fd9 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -156,7 +156,7 @@ class SecurityMiddleware extends Middleware { throw new NotAdminException($this->l10n->t('Logged in account must be an admin, a sub admin or gotten special right to access this setting')); } if (!$this->remoteAddress->allowsAdminActions()) { - throw new AdminIpNotAllowedException($this->l10n->t('Your current IP address doesn’t allow you to perform admin actions')); + throw new AdminIpNotAllowedException($this->l10n->t('Your current IP address doesn\'t allow you to perform admin actions')); } } if ($this->hasAnnotationOrAttribute($reflectionMethod, 'SubAdminRequired', SubAdminRequired::class) @@ -173,19 +173,19 @@ class SecurityMiddleware extends Middleware { } if ($this->hasAnnotationOrAttribute($reflectionMethod, 'SubAdminRequired', SubAdminRequired::class) && !$this->remoteAddress->allowsAdminActions()) { - throw new AdminIpNotAllowedException($this->l10n->t('Your current IP address doesn’t allow you to perform admin actions')); + throw new AdminIpNotAllowedException($this->l10n->t('Your current IP address doesn\'t allow you to perform admin actions')); } if (!$this->hasAnnotationOrAttribute($reflectionMethod, 'SubAdminRequired', SubAdminRequired::class) && !$this->hasAnnotationOrAttribute($reflectionMethod, 'NoAdminRequired', NoAdminRequired::class) && !$this->remoteAddress->allowsAdminActions()) { - throw new AdminIpNotAllowedException($this->l10n->t('Your current IP address doesn’t allow you to perform admin actions')); + throw new AdminIpNotAllowedException($this->l10n->t('Your current IP address doesn\'t allow you to perform admin actions')); } } // Check for strict cookie requirement - if ($this->hasAnnotationOrAttribute($reflectionMethod, 'StrictCookieRequired', StrictCookiesRequired::class) || - !$this->hasAnnotationOrAttribute($reflectionMethod, 'NoCSRFRequired', NoCSRFRequired::class)) { + if ($this->hasAnnotationOrAttribute($reflectionMethod, 'StrictCookieRequired', StrictCookiesRequired::class) + || !$this->hasAnnotationOrAttribute($reflectionMethod, 'NoCSRFRequired', NoCSRFRequired::class)) { if (!$this->request->passesStrictCookieCheck()) { throw new StrictCookieMissingException(); } |