aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/AppFramework/Middleware
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/AppFramework/Middleware')
-rw-r--r--lib/private/AppFramework/Middleware/FlowV2EphemeralSessionsMiddleware.php64
-rw-r--r--lib/private/AppFramework/Middleware/NotModifiedMiddleware.php2
-rw-r--r--lib/private/AppFramework/Middleware/OCSMiddleware.php1
-rw-r--r--lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php1
-rw-r--r--lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php3
-rw-r--r--lib/private/AppFramework/Middleware/Security/CORSMiddleware.php12
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php1
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php1
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php5
-rw-r--r--lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php7
-rw-r--r--lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php9
-rw-r--r--lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php17
-rw-r--r--lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php10
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();
}