aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php')
-rw-r--r--lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php191
1 files changed, 89 insertions, 102 deletions
diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
index a97876fd9ab..e3a293e0fd9 100644
--- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
@@ -1,54 +1,30 @@
<?php
declare(strict_types=1);
-
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Bernhard Posselt <dev@bernhard-posselt.com>
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Holger Hees <holger.hees@gmail.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Julien Veyssier <eneiluj@posteo.net>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Stefan Weil <sw@weilnetz.de>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Thomas Tanghus <thomas@tanghus.net>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
-
namespace OC\AppFramework\Middleware\Security;
+use OC\AppFramework\Middleware\Security\Exceptions\AdminIpNotAllowedException;
use OC\AppFramework\Middleware\Security\Exceptions\AppNotEnabledException;
use OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException;
+use OC\AppFramework\Middleware\Security\Exceptions\ExAppRequiredException;
use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException;
use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException;
use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException;
use OC\AppFramework\Utility\ControllerMethodReflector;
use OC\Settings\AuthorizedGroupMapper;
+use OC\User\Session;
use OCP\App\AppPathNotFoundException;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Attribute\AppApiAdminAccessWithoutUser;
use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
+use OCP\AppFramework\Http\Attribute\ExAppRequired;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
@@ -60,11 +36,14 @@ use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\OCSController;
+use OCP\Group\ISubAdmin;
+use OCP\IGroupManager;
use OCP\IL10N;
use OCP\INavigationManager;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
+use OCP\Security\Ip\IRemoteAddress;
use OCP\Util;
use Psr\Log\LoggerInterface;
use ReflectionMethod;
@@ -76,60 +55,41 @@ use ReflectionMethod;
* check fails
*/
class SecurityMiddleware extends Middleware {
- /** @var INavigationManager */
- private $navigationManager;
- /** @var IRequest */
- private $request;
- /** @var ControllerMethodReflector */
- private $reflector;
- /** @var string */
- private $appName;
- /** @var IURLGenerator */
- private $urlGenerator;
- /** @var LoggerInterface */
- private $logger;
- /** @var bool */
- private $isLoggedIn;
- /** @var bool */
- private $isAdminUser;
- /** @var bool */
- private $isSubAdmin;
- /** @var IAppManager */
- private $appManager;
- /** @var IL10N */
- private $l10n;
- /** @var AuthorizedGroupMapper */
- private $groupAuthorizationMapper;
- /** @var IUserSession */
- private $userSession;
-
- public function __construct(IRequest $request,
- ControllerMethodReflector $reflector,
- INavigationManager $navigationManager,
- IURLGenerator $urlGenerator,
- LoggerInterface $logger,
- string $appName,
- bool $isLoggedIn,
- bool $isAdminUser,
- bool $isSubAdmin,
- IAppManager $appManager,
- IL10N $l10n,
- AuthorizedGroupMapper $mapper,
- IUserSession $userSession
+ private ?bool $isAdminUser = null;
+ private ?bool $isSubAdmin = null;
+
+ public function __construct(
+ private IRequest $request,
+ private ControllerMethodReflector $reflector,
+ private INavigationManager $navigationManager,
+ private IURLGenerator $urlGenerator,
+ private LoggerInterface $logger,
+ private string $appName,
+ private bool $isLoggedIn,
+ private IGroupManager $groupManager,
+ private ISubAdmin $subAdminManager,
+ private IAppManager $appManager,
+ private IL10N $l10n,
+ private AuthorizedGroupMapper $groupAuthorizationMapper,
+ private IUserSession $userSession,
+ private IRemoteAddress $remoteAddress,
) {
- $this->navigationManager = $navigationManager;
- $this->request = $request;
- $this->reflector = $reflector;
- $this->appName = $appName;
- $this->urlGenerator = $urlGenerator;
- $this->logger = $logger;
- $this->isLoggedIn = $isLoggedIn;
- $this->isAdminUser = $isAdminUser;
- $this->isSubAdmin = $isSubAdmin;
- $this->appManager = $appManager;
- $this->l10n = $l10n;
- $this->groupAuthorizationMapper = $mapper;
- $this->userSession = $userSession;
+ }
+
+ private function isAdminUser(): bool {
+ if ($this->isAdminUser === null) {
+ $user = $this->userSession->getUser();
+ $this->isAdminUser = $user && $this->groupManager->isAdmin($user->getUID());
+ }
+ return $this->isAdminUser;
+ }
+
+ private function isSubAdmin(): bool {
+ if ($this->isSubAdmin === null) {
+ $user = $this->userSession->getUser();
+ $this->isSubAdmin = $user && $this->subAdminManager->isSubAdmin($user);
+ }
+ return $this->isSubAdmin;
}
/**
@@ -156,16 +116,29 @@ class SecurityMiddleware extends Middleware {
// security checks
$isPublicPage = $this->hasAnnotationOrAttribute($reflectionMethod, 'PublicPage', PublicPage::class);
- if (!$isPublicPage) {
- if (!$this->isLoggedIn) {
- throw new NotLoggedInException();
+
+ if ($this->hasAnnotationOrAttribute($reflectionMethod, 'ExAppRequired', ExAppRequired::class)) {
+ if (!$this->userSession instanceof Session || $this->userSession->getSession()->get('app_api') !== true) {
+ throw new ExAppRequiredException();
}
+ } elseif (!$isPublicPage) {
$authorized = false;
- if ($this->hasAnnotationOrAttribute($reflectionMethod, 'AuthorizedAdminSetting', AuthorizedAdminSetting::class)) {
- $authorized = $this->isAdminUser;
+ if ($this->hasAnnotationOrAttribute($reflectionMethod, null, AppApiAdminAccessWithoutUser::class)) {
+ // this attribute allows ExApp to access admin endpoints only if "userId" is "null"
+ if ($this->userSession instanceof Session && $this->userSession->getSession()->get('app_api') === true && $this->userSession->getUser() === null) {
+ $authorized = true;
+ }
+ }
+
+ if (!$authorized && !$this->isLoggedIn) {
+ throw new NotLoggedInException();
+ }
+
+ if (!$authorized && $this->hasAnnotationOrAttribute($reflectionMethod, 'AuthorizedAdminSetting', AuthorizedAdminSetting::class)) {
+ $authorized = $this->isAdminUser();
if (!$authorized && $this->hasAnnotationOrAttribute($reflectionMethod, 'SubAdminRequired', SubAdminRequired::class)) {
- $authorized = $this->isSubAdmin;
+ $authorized = $this->isSubAdmin();
}
if (!$authorized) {
@@ -180,26 +153,39 @@ class SecurityMiddleware extends Middleware {
}
}
if (!$authorized) {
- throw new NotAdminException($this->l10n->t('Logged in user must be an admin, a sub admin or gotten special right to access this setting'));
+ 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'));
}
}
if ($this->hasAnnotationOrAttribute($reflectionMethod, 'SubAdminRequired', SubAdminRequired::class)
- && !$this->isSubAdmin
- && !$this->isAdminUser
+ && !$this->isSubAdmin()
+ && !$this->isAdminUser()
&& !$authorized) {
- throw new NotAdminException($this->l10n->t('Logged in user must be an admin or sub admin'));
+ throw new NotAdminException($this->l10n->t('Logged in account must be an admin or sub admin'));
}
if (!$this->hasAnnotationOrAttribute($reflectionMethod, 'SubAdminRequired', SubAdminRequired::class)
&& !$this->hasAnnotationOrAttribute($reflectionMethod, 'NoAdminRequired', NoAdminRequired::class)
- && !$this->isAdminUser
+ && !$this->isAdminUser()
&& !$authorized) {
- throw new NotAdminException($this->l10n->t('Logged in user must be an admin'));
+ throw new NotAdminException($this->l10n->t('Logged in account must be an admin'));
}
+ 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'));
+ }
+ 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'));
+ }
+
}
// 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();
}
@@ -254,16 +240,17 @@ class SecurityMiddleware extends Middleware {
* @template T
*
* @param ReflectionMethod $reflectionMethod
- * @param string $annotationName
+ * @param ?string $annotationName
* @param class-string<T> $attributeClass
* @return boolean
*/
- protected function hasAnnotationOrAttribute(ReflectionMethod $reflectionMethod, string $annotationName, string $attributeClass): bool {
+ protected function hasAnnotationOrAttribute(ReflectionMethod $reflectionMethod, ?string $annotationName, string $attributeClass): bool {
if (!empty($reflectionMethod->getAttributes($attributeClass))) {
return true;
}
- if ($this->reflector->hasAnnotation($annotationName)) {
+ if ($annotationName && $this->reflector->hasAnnotation($annotationName)) {
+ $this->logger->debug($reflectionMethod->getDeclaringClass()->getName() . '::' . $reflectionMethod->getName() . ' uses the @' . $annotationName . ' annotation and should use the #[' . $attributeClass . '] attribute instead');
return true;
}