aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/AppFramework
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/AppFramework')
-rw-r--r--lib/private/AppFramework/App.php120
-rw-r--r--lib/private/AppFramework/Bootstrap/ARegistration.php22
-rw-r--r--lib/private/AppFramework/Bootstrap/BootContext.php23
-rw-r--r--lib/private/AppFramework/Bootstrap/Coordinator.php142
-rw-r--r--lib/private/AppFramework/Bootstrap/EventListenerRegistration.php28
-rw-r--r--lib/private/AppFramework/Bootstrap/FunctionInjector.php22
-rw-r--r--lib/private/AppFramework/Bootstrap/MiddlewareRegistration.php29
-rw-r--r--lib/private/AppFramework/Bootstrap/ParameterRegistration.php26
-rw-r--r--lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php27
-rw-r--r--lib/private/AppFramework/Bootstrap/RegistrationContext.php453
-rw-r--r--lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php26
-rw-r--r--lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php28
-rw-r--r--lib/private/AppFramework/Bootstrap/ServiceRegistration.php21
-rw-r--r--lib/private/AppFramework/DependencyInjection/DIContainer.php365
-rw-r--r--lib/private/AppFramework/Http.php34
-rw-r--r--lib/private/AppFramework/Http/Dispatcher.php118
-rw-r--r--lib/private/AppFramework/Http/Output.php38
-rw-r--r--lib/private/AppFramework/Http/Request.php330
-rw-r--r--lib/private/AppFramework/Http/RequestId.php22
-rw-r--r--lib/private/AppFramework/Logger.php127
-rw-r--r--lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php62
-rw-r--r--lib/private/AppFramework/Middleware/CompressionMiddleware.php25
-rw-r--r--lib/private/AppFramework/Middleware/FlowV2EphemeralSessionsMiddleware.php64
-rw-r--r--lib/private/AppFramework/Middleware/MiddlewareDispatcher.php52
-rw-r--r--lib/private/AppFramework/Middleware/NotModifiedMiddleware.php23
-rw-r--r--lib/private/AppFramework/Middleware/OCSMiddleware.php26
-rw-r--r--lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php23
-rw-r--r--lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php64
-rw-r--r--lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php113
-rw-r--r--lib/private/AppFramework/Middleware/Security/CORSMiddleware.php125
-rw-r--r--lib/private/AppFramework/Middleware/Security/CSPMiddleware.php46
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/AdminIpNotAllowedException.php23
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php25
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/CrossSiteRequestForgeryException.php25
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/ExAppRequiredException.php18
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php22
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/NotAdminException.php26
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php26
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/NotLoggedInException.php25
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/ReloadExecutionException.php21
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php26
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/StrictCookieMissingException.php24
-rw-r--r--lib/private/AppFramework/Middleware/Security/FeaturePolicyMiddleware.php22
-rw-r--r--lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php151
-rw-r--r--lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php157
-rw-r--r--lib/private/AppFramework/Middleware/Security/ReloadExecutionMiddleware.php23
-rw-r--r--lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php48
-rw-r--r--lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php270
-rw-r--r--lib/private/AppFramework/Middleware/SessionMiddleware.php63
-rw-r--r--lib/private/AppFramework/OCS/BaseResponse.php94
-rw-r--r--lib/private/AppFramework/OCS/V1Response.php42
-rw-r--r--lib/private/AppFramework/OCS/V2Response.php36
-rw-r--r--lib/private/AppFramework/Routing/RouteActionHandler.php25
-rw-r--r--lib/private/AppFramework/Routing/RouteConfig.php295
-rw-r--r--lib/private/AppFramework/Routing/RouteParser.php33
-rw-r--r--lib/private/AppFramework/ScopedPsrLogger.php43
-rw-r--r--lib/private/AppFramework/Services/AppConfig.php339
-rw-r--r--lib/private/AppFramework/Services/InitialState.php21
-rw-r--r--lib/private/AppFramework/Utility/ControllerMethodReflector.php70
-rw-r--r--lib/private/AppFramework/Utility/QueryNotFoundException.php24
-rw-r--r--lib/private/AppFramework/Utility/SimpleContainer.php126
-rw-r--r--lib/private/AppFramework/Utility/TimeFactory.php57
62 files changed, 2228 insertions, 2596 deletions
diff --git a/lib/private/AppFramework/App.php b/lib/private/AppFramework/App.php
index feebb32d5bc..7bf32852209 100644
--- a/lib/private/AppFramework/App.php
+++ b/lib/private/AppFramework/App.php
@@ -1,50 +1,26 @@
<?php
declare(strict_types=1);
-
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Bernhard Posselt <dev@bernhard-posselt.com>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @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;
use OC\AppFramework\DependencyInjection\DIContainer;
use OC\AppFramework\Http\Dispatcher;
use OC\AppFramework\Http\Request;
-use OC\Diagnostics\EventLogger;
-use OCP\Profiler\IProfiler;
use OC\Profiler\RoutingDataCollector;
-use OCP\AppFramework\QueryException;
+use OCP\App\IAppManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\ICallbackResponse;
use OCP\AppFramework\Http\IOutput;
+use OCP\AppFramework\QueryException;
use OCP\Diagnostics\IEventLogger;
use OCP\HintException;
-use OCP\IConfig;
use OCP\IRequest;
+use OCP\Profiler\IProfiler;
/**
* Entry point for every request in your app. You can consider this as your
@@ -53,7 +29,6 @@ use OCP\IRequest;
* Handles all the dependency injection, controllers and output flow
*/
class App {
-
/** @var string[] */
private static $nameSpaceCache = [];
@@ -62,7 +37,7 @@ class App {
* namespace tag or uppercasing the appid's first letter
* @param string $appId the app id
* @param string $topNamespace the namespace which should be prepended to
- * the transformed app id, defaults to OCA\
+ * the transformed app id, defaults to OCA\
* @return string the starting namespace for the app
*/
public static function buildAppNamespace(string $appId, string $topNamespace = 'OCA\\'): string {
@@ -71,35 +46,24 @@ class App {
return $topNamespace . self::$nameSpaceCache[$appId];
}
- $appInfo = \OC_App::getAppInfo($appId);
+ $appInfo = \OCP\Server::get(IAppManager::class)->getAppInfo($appId);
if (isset($appInfo['namespace'])) {
self::$nameSpaceCache[$appId] = trim($appInfo['namespace']);
} else {
- if ($appId !== 'spreed') {
- // if the tag is not found, fall back to uppercasing the first letter
- self::$nameSpaceCache[$appId] = ucfirst($appId);
- } else {
- // For the Talk app (appid spreed) the above fallback doesn't work.
- // This leads to a problem when trying to install it freshly,
- // because the apps namespace is already registered before the
- // app is downloaded from the appstore, because of the hackish
- // global route index.php/call/{token} which is registered via
- // the core/routes.php so it does not have the app namespace.
- // @ref https://github.com/nextcloud/server/pull/19433
- self::$nameSpaceCache[$appId] = 'Talk';
- }
+ // if the tag is not found, fall back to uppercasing the first letter
+ self::$nameSpaceCache[$appId] = ucfirst($appId);
}
return $topNamespace . self::$nameSpaceCache[$appId];
}
public static function getAppIdForClass(string $className, string $topNamespace = 'OCA\\'): ?string {
- if (strpos($className, $topNamespace) !== 0) {
+ if (!str_starts_with($className, $topNamespace)) {
return null;
}
foreach (self::$nameSpaceCache as $appId => $namespace) {
- if (strpos($className, $topNamespace . $namespace . '\\') === 0) {
+ if (str_starts_with($className, $topNamespace . $namespace . '\\')) {
return $appId;
}
}
@@ -107,7 +71,6 @@ class App {
return null;
}
-
/**
* Shortcut for calling a controller method and printing the result
*
@@ -118,17 +81,24 @@ class App {
* @param array $urlParams list of URL parameters (optional)
* @throws HintException
*/
- public static function main(string $controllerName, string $methodName, DIContainer $container, array $urlParams = null) {
+ public static function main(
+ string $controllerName,
+ string $methodName,
+ DIContainer $container,
+ ?array $urlParams = null,
+ ): void {
/** @var IProfiler $profiler */
$profiler = $container->get(IProfiler::class);
- $config = $container->get(IConfig::class);
+ $eventLogger = $container->get(IEventLogger::class);
// Disable profiler on the profiler UI
$profiler->setEnabled($profiler->isEnabled() && !is_null($urlParams) && isset($urlParams['_route']) && !str_starts_with($urlParams['_route'], 'profiler.'));
if ($profiler->isEnabled()) {
\OC::$server->get(IEventLogger::class)->activate();
- $profiler->add(new RoutingDataCollector($container['AppName'], $controllerName, $methodName));
+ $profiler->add(new RoutingDataCollector($container['appName'], $controllerName, $methodName));
}
+ $eventLogger->start('app:controller:params', 'Gather controller parameters');
+
if (!is_null($urlParams)) {
/** @var Request $request */
$request = $container->get(IRequest::class);
@@ -138,13 +108,17 @@ class App {
$request = $container->get(IRequest::class);
$request->setUrlParameters($container['urlParams']);
}
- $appName = $container['AppName'];
+ $appName = $container['appName'];
+
+ $eventLogger->end('app:controller:params');
+
+ $eventLogger->start('app:controller:load', 'Load app controller');
// first try $controllerName then go for \OCA\AppName\Controller\$controllerName
try {
$controller = $container->get($controllerName);
} catch (QueryException $e) {
- if (strpos($controllerName, '\\Controller\\') !== false) {
+ if (str_contains($controllerName, '\\Controller\\')) {
// This is from a global registered app route that is not enabled.
[/*OC(A)*/, $app, /* Controller/Name*/] = explode('\\', $controllerName, 3);
throw new HintException('App ' . strtolower($app) . ' is not enabled');
@@ -159,9 +133,16 @@ class App {
$controller = $container->query($controllerName);
}
+ $eventLogger->end('app:controller:load');
+
+ $eventLogger->start('app:controller:dispatcher', 'Initialize dispatcher and pre-middleware');
+
// initialize the dispatcher and run all the middleware before the controller
- /** @var Dispatcher $dispatcher */
- $dispatcher = $container['Dispatcher'];
+ $dispatcher = $container->get(Dispatcher::class);
+
+ $eventLogger->end('app:controller:dispatcher');
+
+ $eventLogger->start('app:controller:run', 'Run app controller');
[
$httpHeaders,
@@ -171,11 +152,11 @@ class App {
$response
] = $dispatcher->dispatch($controller, $methodName);
+ $eventLogger->end('app:controller:run');
+
$io = $container[IOutput::class];
if ($profiler->isEnabled()) {
- /** @var EventLogger $eventLogger */
- $eventLogger = $container->get(IEventLogger::class);
$eventLogger->end('runtime');
$profile = $profiler->collect($container->get(IRequest::class), $response);
$profiler->saveProfile($profile);
@@ -233,27 +214,4 @@ class App {
}
}
}
-
- /**
- * Shortcut for calling a controller method and printing the result.
- * Similar to App:main except that no headers will be sent.
- * This should be used for example when registering sections via
- * \OC\AppFramework\Core\API::registerAdmin()
- *
- * @param string $controllerName the name of the controller under which it is
- * stored in the DI container
- * @param string $methodName the method that you want to call
- * @param array $urlParams an array with variables extracted from the routes
- * @param DIContainer $container an instance of a pimple container.
- */
- public static function part(string $controllerName, string $methodName, array $urlParams,
- DIContainer $container) {
- $container['urlParams'] = $urlParams;
- $controller = $container[$controllerName];
-
- $dispatcher = $container['Dispatcher'];
-
- [, , $output] = $dispatcher->dispatch($controller, $methodName);
- return $output;
- }
}
diff --git a/lib/private/AppFramework/Bootstrap/ARegistration.php b/lib/private/AppFramework/Bootstrap/ARegistration.php
index bb8fe0c8e92..37984667727 100644
--- a/lib/private/AppFramework/Bootstrap/ARegistration.php
+++ b/lib/private/AppFramework/Bootstrap/ARegistration.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Bootstrap;
@@ -29,7 +12,6 @@ namespace OC\AppFramework\Bootstrap;
* @psalm-immutable
*/
abstract class ARegistration {
-
/** @var string */
private $appId;
diff --git a/lib/private/AppFramework/Bootstrap/BootContext.php b/lib/private/AppFramework/Bootstrap/BootContext.php
index f21d7d00b1e..b3da08adb07 100644
--- a/lib/private/AppFramework/Bootstrap/BootContext.php
+++ b/lib/private/AppFramework/Bootstrap/BootContext.php
@@ -3,26 +3,8 @@
declare(strict_types=1);
/**
- * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Morris Jobke <hey@morrisjobke.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Bootstrap;
@@ -31,7 +13,6 @@ use OCP\AppFramework\IAppContainer;
use OCP\IServerContainer;
class BootContext implements IBootContext {
-
/** @var IAppContainer */
private $appContainer;
diff --git a/lib/private/AppFramework/Bootstrap/Coordinator.php b/lib/private/AppFramework/Bootstrap/Coordinator.php
index b6378830732..a31dd6a05e1 100644
--- a/lib/private/AppFramework/Bootstrap/Coordinator.php
+++ b/lib/private/AppFramework/Bootstrap/Coordinator.php
@@ -3,92 +3,56 @@
declare(strict_types=1);
/**
- * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Bootstrap;
-use OCP\Diagnostics\IEventLogger;
-use function class_exists;
-use function class_implements;
-use function in_array;
-use OC_App;
use OC\Support\CrashReport\Registry;
+use OC_App;
+use OCP\App\AppPathNotFoundException;
+use OCP\App\IAppManager;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\QueryException;
use OCP\Dashboard\IManager;
+use OCP\Diagnostics\IEventLogger;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IServerContainer;
+use Psr\Container\ContainerExceptionInterface;
use Psr\Log\LoggerInterface;
use Throwable;
+use function class_exists;
+use function class_implements;
+use function in_array;
class Coordinator {
-
- /** @var IServerContainer */
- private $serverContainer;
-
- /** @var Registry */
- private $registry;
-
- /** @var IManager */
- private $dashboardManager;
-
- /** @var IEventDispatcher */
- private $eventDispatcher;
-
- /** @var IEventLogger */
- private $eventLogger;
-
- /** @var LoggerInterface */
- private $logger;
-
/** @var RegistrationContext|null */
private $registrationContext;
- /** @var string[] */
- private $bootedApps = [];
+ /** @var array<string,true> */
+ private array $bootedApps = [];
public function __construct(
- IServerContainer $container,
- Registry $registry,
- IManager $dashboardManager,
- IEventDispatcher $eventListener,
- IEventLogger $eventLogger,
- LoggerInterface $logger
+ private IServerContainer $serverContainer,
+ private Registry $registry,
+ private IManager $dashboardManager,
+ private IEventDispatcher $eventDispatcher,
+ private IEventLogger $eventLogger,
+ private IAppManager $appManager,
+ private LoggerInterface $logger,
) {
- $this->serverContainer = $container;
- $this->registry = $registry;
- $this->dashboardManager = $dashboardManager;
- $this->eventDispatcher = $eventListener;
- $this->eventLogger = $eventLogger;
- $this->logger = $logger;
}
public function runInitialRegistration(): void {
- $this->registerApps(OC_App::getEnabledApps());
+ $apps = OC_App::getEnabledApps();
+ if (!empty($apps)) {
+ // make sure to also register the core app
+ $apps = ['core', ...$apps];
+ }
+
+ $this->registerApps($apps);
}
public function runLazyRegistration(string $appId): void {
@@ -99,62 +63,78 @@ class Coordinator {
* @param string[] $appIds
*/
private function registerApps(array $appIds): void {
+ $this->eventLogger->start('bootstrap:register_apps', '');
if ($this->registrationContext === null) {
$this->registrationContext = new RegistrationContext($this->logger);
}
$apps = [];
foreach ($appIds as $appId) {
+ $this->eventLogger->start("bootstrap:register_app:$appId", "Register $appId");
+ $this->eventLogger->start("bootstrap:register_app:$appId:autoloader", "Setup autoloader for $appId");
/*
* First, we have to enable the app's autoloader
- *
- * @todo use $this->appManager->getAppPath($appId) here
*/
- $path = OC_App::getAppPath($appId);
- if ($path === false) {
+ try {
+ $path = $this->appManager->getAppPath($appId);
+ OC_App::registerAutoloading($appId, $path);
+ } catch (AppPathNotFoundException) {
// Ignore
continue;
}
- OC_App::registerAutoloading($appId, $path);
+ $this->eventLogger->end("bootstrap:register_app:$appId:autoloader");
/*
- * Next we check if there is an application class and it implements
+ * Next we check if there is an application class, and it implements
* the \OCP\AppFramework\Bootstrap\IBootstrap interface
*/
- $appNameSpace = App::buildAppNamespace($appId);
+ if ($appId === 'core') {
+ $appNameSpace = 'OC\\Core';
+ } else {
+ $appNameSpace = App::buildAppNamespace($appId);
+ }
$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
+
try {
- if (class_exists($applicationClassName) && in_array(IBootstrap::class, class_implements($applicationClassName), true)) {
+ if (class_exists($applicationClassName) && is_a($applicationClassName, IBootstrap::class, true)) {
+ $this->eventLogger->start("bootstrap:register_app:$appId:application", "Load `Application` instance for $appId");
try {
- /** @var IBootstrap|App $application */
- $apps[$appId] = $application = $this->serverContainer->query($applicationClassName);
- } catch (QueryException $e) {
+ /** @var IBootstrap&App $application */
+ $application = $this->serverContainer->query($applicationClassName);
+ $apps[$appId] = $application;
+ } catch (ContainerExceptionInterface $e) {
// Weird, but ok
+ $this->eventLogger->end("bootstrap:register_app:$appId");
continue;
}
+ $this->eventLogger->end("bootstrap:register_app:$appId:application");
- $this->eventLogger->start('bootstrap:register_app_' . $appId, '');
+ $this->eventLogger->start("bootstrap:register_app:$appId:register", "`Application::register` for $appId");
$application->register($this->registrationContext->for($appId));
- $this->eventLogger->end('bootstrap:register_app_' . $appId);
+ $this->eventLogger->end("bootstrap:register_app:$appId:register");
}
} catch (Throwable $e) {
$this->logger->emergency('Error during app service registration: ' . $e->getMessage(), [
'exception' => $e,
'app' => $appId,
]);
+ $this->eventLogger->end("bootstrap:register_app:$appId");
continue;
}
+ $this->eventLogger->end("bootstrap:register_app:$appId");
}
+ $this->eventLogger->start('bootstrap:register_apps:apply', 'Apply all the registered service by apps');
/**
* Now that all register methods have been called, we can delegate the registrations
* to the actual services
*/
$this->registrationContext->delegateCapabilityRegistrations($apps);
$this->registrationContext->delegateCrashReporterRegistrations($apps, $this->registry);
- $this->registrationContext->delegateDashboardPanelRegistrations($apps, $this->dashboardManager);
+ $this->registrationContext->delegateDashboardPanelRegistrations($this->dashboardManager);
$this->registrationContext->delegateEventListenerRegistrations($this->eventDispatcher);
$this->registrationContext->delegateContainerRegistrations($apps);
- $this->registrationContext->delegateMiddlewareRegistrations($apps);
+ $this->eventLogger->end('bootstrap:register_apps:apply');
+ $this->eventLogger->end('bootstrap:register_apps');
}
public function getRegistrationContext(): ?RegistrationContext {
@@ -180,7 +160,7 @@ class Coordinator {
* the instance was already created for register, but any other
* (legacy) code will now do their magic via the constructor.
*/
- $this->eventLogger->start('bootstrap:boot_app_' . $appId, '');
+ $this->eventLogger->start('bootstrap:boot_app:' . $appId, "Call `Application::boot` for $appId");
try {
/** @var App $application */
$application = $this->serverContainer->query($applicationClassName);
@@ -198,13 +178,13 @@ class Coordinator {
'exception' => $e,
]);
}
- $this->eventLogger->end('bootstrap:boot_app_' . $appId);
+ $this->eventLogger->end('bootstrap:boot_app:' . $appId);
}
public function isBootable(string $appId) {
$appNameSpace = App::buildAppNamespace($appId);
$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
- return class_exists($applicationClassName) &&
- in_array(IBootstrap::class, class_implements($applicationClassName), true);
+ return class_exists($applicationClassName)
+ && in_array(IBootstrap::class, class_implements($applicationClassName), true);
}
}
diff --git a/lib/private/AppFramework/Bootstrap/EventListenerRegistration.php b/lib/private/AppFramework/Bootstrap/EventListenerRegistration.php
index be279cbfd52..92955fc4123 100644
--- a/lib/private/AppFramework/Bootstrap/EventListenerRegistration.php
+++ b/lib/private/AppFramework/Bootstrap/EventListenerRegistration.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Bootstrap;
@@ -30,7 +13,6 @@ namespace OC\AppFramework\Bootstrap;
* @template-extends ServiceRegistration<\OCP\EventDispatcher\IEventListener>
*/
class EventListenerRegistration extends ServiceRegistration {
-
/** @var string */
private $event;
@@ -38,9 +20,9 @@ class EventListenerRegistration extends ServiceRegistration {
private $priority;
public function __construct(string $appId,
- string $event,
- string $service,
- int $priority) {
+ string $event,
+ string $service,
+ int $priority) {
parent::__construct($appId, $service);
$this->event = $event;
$this->priority = $priority;
diff --git a/lib/private/AppFramework/Bootstrap/FunctionInjector.php b/lib/private/AppFramework/Bootstrap/FunctionInjector.php
index 13eb5d88969..973fc13aa2c 100644
--- a/lib/private/AppFramework/Bootstrap/FunctionInjector.php
+++ b/lib/private/AppFramework/Bootstrap/FunctionInjector.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Bootstrap;
@@ -33,7 +16,6 @@ use ReflectionParameter;
use function array_map;
class FunctionInjector {
-
/** @var ContainerInterface */
private $container;
diff --git a/lib/private/AppFramework/Bootstrap/MiddlewareRegistration.php b/lib/private/AppFramework/Bootstrap/MiddlewareRegistration.php
new file mode 100644
index 00000000000..d2ad6bbf0f6
--- /dev/null
+++ b/lib/private/AppFramework/Bootstrap/MiddlewareRegistration.php
@@ -0,0 +1,29 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\AppFramework\Bootstrap;
+
+use OCP\AppFramework\Middleware;
+
+/**
+ * @psalm-immutable
+ * @template-extends ServiceRegistration<Middleware>
+ */
+class MiddlewareRegistration extends ServiceRegistration {
+ private bool $global;
+
+ public function __construct(string $appId, string $service, bool $global) {
+ parent::__construct($appId, $service);
+ $this->global = $global;
+ }
+
+ public function isGlobal(): bool {
+ return $this->global;
+ }
+}
diff --git a/lib/private/AppFramework/Bootstrap/ParameterRegistration.php b/lib/private/AppFramework/Bootstrap/ParameterRegistration.php
index 5a683f41a9f..cc9a4875e9a 100644
--- a/lib/private/AppFramework/Bootstrap/ParameterRegistration.php
+++ b/lib/private/AppFramework/Bootstrap/ParameterRegistration.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Bootstrap;
@@ -29,7 +12,6 @@ namespace OC\AppFramework\Bootstrap;
* @psalm-immutable
*/
final class ParameterRegistration extends ARegistration {
-
/** @var string */
private $name;
@@ -37,8 +19,8 @@ final class ParameterRegistration extends ARegistration {
private $value;
public function __construct(string $appId,
- string $name,
- $value) {
+ string $name,
+ $value) {
parent::__construct($appId);
$this->name = $name;
$this->value = $value;
diff --git a/lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php b/lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php
index 47c25d39300..7ecc4aac7f2 100644
--- a/lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php
+++ b/lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php
@@ -2,25 +2,9 @@
declare(strict_types=1);
-/*
- * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
+/**
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Bootstrap;
@@ -30,13 +14,12 @@ namespace OC\AppFramework\Bootstrap;
* @template-extends ServiceRegistration<\OCP\Preview\IProviderV2>
*/
class PreviewProviderRegistration extends ServiceRegistration {
-
/** @var string */
private $mimeTypeRegex;
public function __construct(string $appId,
- string $service,
- string $mimeTypeRegex) {
+ string $service,
+ string $mimeTypeRegex) {
parent::__construct($appId, $service);
$this->mimeTypeRegex = $mimeTypeRegex;
}
diff --git a/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/lib/private/AppFramework/Bootstrap/RegistrationContext.php
index 7b4d24036e8..8bd1ff35610 100644
--- a/lib/private/AppFramework/Bootstrap/RegistrationContext.php
+++ b/lib/private/AppFramework/Bootstrap/RegistrationContext.php
@@ -3,38 +3,13 @@
declare(strict_types=1);
/**
- * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Bootstrap;
use Closure;
-use OCP\Calendar\Resource\IBackend as IResourceBackend;
-use OCP\Calendar\Room\IBackend as IRoomBackend;
-use OCP\Talk\ITalkBackend;
-use RuntimeException;
-use function array_shift;
use OC\Support\CrashReport\Registry;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
@@ -42,22 +17,36 @@ use OCP\AppFramework\Middleware;
use OCP\AppFramework\Services\InitialStateProvider;
use OCP\Authentication\IAlternativeLogin;
use OCP\Calendar\ICalendarProvider;
+use OCP\Calendar\Resource\IBackend as IResourceBackend;
+use OCP\Calendar\Room\IBackend as IRoomBackend;
use OCP\Capabilities\ICapability;
+use OCP\Collaboration\Reference\IReferenceProvider;
+use OCP\Config\Lexicon\ILexicon;
use OCP\Dashboard\IManager;
use OCP\Dashboard\IWidget;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Template\ICustomTemplateProvider;
use OCP\Http\WellKnown\IHandler;
+use OCP\Mail\Provider\IProvider as IMailProvider;
use OCP\Notification\INotifier;
use OCP\Profile\ILinkAction;
use OCP\Search\IProvider;
+use OCP\Settings\IDeclarativeSettingsForm;
+use OCP\SetupCheck\ISetupCheck;
+use OCP\Share\IPublicShareTemplateProvider;
+use OCP\SpeechToText\ISpeechToTextProvider;
use OCP\Support\CrashReport\IReporter;
+use OCP\Talk\ITalkBackend;
+use OCP\Teams\ITeamResourceProvider;
+use OCP\TextProcessing\IProvider as ITextProcessingProvider;
+use OCP\Translation\ITranslationProvider;
use OCP\UserMigration\IMigrator as IUserMigrator;
use Psr\Log\LoggerInterface;
+use RuntimeException;
use Throwable;
+use function array_shift;
class RegistrationContext {
-
/** @var ServiceRegistration<ICapability>[] */
private $capabilities = [];
@@ -94,7 +83,7 @@ class RegistrationContext {
/** @var EventListenerRegistration[] */
private $eventListeners = [];
- /** @var ServiceRegistration<Middleware>[] */
+ /** @var MiddlewareRegistration[] */
private $middlewares = [];
/** @var ServiceRegistration<IProvider>[] */
@@ -109,9 +98,18 @@ class RegistrationContext {
/** @var ServiceRegistration<IHandler>[] */
private $wellKnownHandlers = [];
+ /** @var ServiceRegistration<ISpeechToTextProvider>[] */
+ private $speechToTextProviders = [];
+
+ /** @var ServiceRegistration<ITextProcessingProvider>[] */
+ private $textProcessingProviders = [];
+
/** @var ServiceRegistration<ICustomTemplateProvider>[] */
private $templateProviders = [];
+ /** @var ServiceRegistration<ITranslationProvider>[] */
+ private $translationProviders = [];
+
/** @var ServiceRegistration<INotifier>[] */
private $notifierServices = [];
@@ -121,11 +119,46 @@ class RegistrationContext {
/** @var ServiceRegistration<ICalendarProvider>[] */
private $calendarProviders = [];
- /** @var LoggerInterface */
- private $logger;
+ /** @var ServiceRegistration<IReferenceProvider>[] */
+ private array $referenceProviders = [];
+
+ /** @var ServiceRegistration<\OCP\TextToImage\IProvider>[] */
+ private $textToImageProviders = [];
+
+ /** @var ParameterRegistration[] */
+ private $sensitiveMethods = [];
+
+ /** @var ServiceRegistration<IPublicShareTemplateProvider>[] */
+ private $publicShareTemplateProviders = [];
+
+ private LoggerInterface $logger;
+
+ /** @var ServiceRegistration<ISetupCheck>[] */
+ private array $setupChecks = [];
/** @var PreviewProviderRegistration[] */
- private $previewProviders = [];
+ private array $previewProviders = [];
+
+ /** @var ServiceRegistration<IDeclarativeSettingsForm>[] */
+ private array $declarativeSettings = [];
+
+ /** @var array<array-key, string> */
+ private array $configLexiconClasses = [];
+
+ /** @var ServiceRegistration<ITeamResourceProvider>[] */
+ private array $teamResourceProviders = [];
+
+ /** @var ServiceRegistration<\OCP\TaskProcessing\IProvider>[] */
+ private array $taskProcessingProviders = [];
+
+ /** @var ServiceRegistration<\OCP\TaskProcessing\ITaskType>[] */
+ private array $taskProcessingTaskTypes = [];
+
+ /** @var ServiceRegistration<\OCP\Files\Conversion\IConversionProvider>[] */
+ private array $fileConversionProviders = [];
+
+ /** @var ServiceRegistration<IMailProvider>[] */
+ private $mailProviders = [];
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
@@ -199,10 +232,11 @@ class RegistrationContext {
);
}
- public function registerMiddleware(string $class): void {
+ public function registerMiddleware(string $class, bool $global = false): void {
$this->context->registerMiddleware(
$this->appId,
- $class
+ $class,
+ $global,
);
}
@@ -234,6 +268,26 @@ class RegistrationContext {
);
}
+ public function registerSpeechToTextProvider(string $providerClass): void {
+ $this->context->registerSpeechToTextProvider(
+ $this->appId,
+ $providerClass
+ );
+ }
+ public function registerTextProcessingProvider(string $providerClass): void {
+ $this->context->registerTextProcessingProvider(
+ $this->appId,
+ $providerClass
+ );
+ }
+
+ public function registerTextToImageProvider(string $providerClass): void {
+ $this->context->registerTextToImageProvider(
+ $this->appId,
+ $providerClass
+ );
+ }
+
public function registerTemplateProvider(string $providerClass): void {
$this->context->registerTemplateProvider(
$this->appId,
@@ -241,6 +295,13 @@ class RegistrationContext {
);
}
+ public function registerTranslationProvider(string $providerClass): void {
+ $this->context->registerTranslationProvider(
+ $this->appId,
+ $providerClass
+ );
+ }
+
public function registerNotifierService(string $notifierClass): void {
$this->context->registerNotifierService(
$this->appId,
@@ -270,6 +331,13 @@ class RegistrationContext {
);
}
+ public function registerReferenceProvider(string $class): void {
+ $this->context->registerReferenceProvider(
+ $this->appId,
+ $class
+ );
+ }
+
public function registerProfileLinkAction(string $actionClass): void {
$this->context->registerProfileLinkAction(
$this->appId,
@@ -291,6 +359,13 @@ class RegistrationContext {
);
}
+ public function registerTeamResourceProvider(string $class) : void {
+ $this->context->registerTeamResourceProvider(
+ $this->appId,
+ $class
+ );
+ }
+
public function registerCalendarRoomBackend(string $class): void {
$this->context->registerCalendarRoomBackend(
$this->appId,
@@ -304,6 +379,70 @@ class RegistrationContext {
$migratorClass
);
}
+
+ public function registerSensitiveMethods(string $class, array $methods): void {
+ $this->context->registerSensitiveMethods(
+ $this->appId,
+ $class,
+ $methods
+ );
+ }
+
+ public function registerPublicShareTemplateProvider(string $class): void {
+ $this->context->registerPublicShareTemplateProvider(
+ $this->appId,
+ $class
+ );
+ }
+
+ public function registerSetupCheck(string $setupCheckClass): void {
+ $this->context->registerSetupCheck(
+ $this->appId,
+ $setupCheckClass
+ );
+ }
+
+ public function registerDeclarativeSettings(string $declarativeSettingsClass): void {
+ $this->context->registerDeclarativeSettings(
+ $this->appId,
+ $declarativeSettingsClass
+ );
+ }
+
+ public function registerTaskProcessingProvider(string $taskProcessingProviderClass): void {
+ $this->context->registerTaskProcessingProvider(
+ $this->appId,
+ $taskProcessingProviderClass
+ );
+ }
+
+ public function registerTaskProcessingTaskType(string $taskProcessingTaskTypeClass): void {
+ $this->context->registerTaskProcessingTaskType(
+ $this->appId,
+ $taskProcessingTaskTypeClass
+ );
+ }
+
+ public function registerFileConversionProvider(string $class): void {
+ $this->context->registerFileConversionProvider(
+ $this->appId,
+ $class
+ );
+ }
+
+ public function registerMailProvider(string $class): void {
+ $this->context->registerMailProvider(
+ $this->appId,
+ $class
+ );
+ }
+
+ public function registerConfigLexicon(string $configLexiconClass): void {
+ $this->context->registerConfigLexicon(
+ $this->appId,
+ $configLexiconClass
+ );
+ }
};
}
@@ -315,14 +454,14 @@ class RegistrationContext {
}
/**
- * @psalm-param class-string<IReporter> $capability
+ * @psalm-param class-string<IReporter> $reporterClass
*/
public function registerCrashReporter(string $appId, string $reporterClass): void {
$this->crashReporters[] = new ServiceRegistration($appId, $reporterClass);
}
/**
- * @psalm-param class-string<IWidget> $capability
+ * @psalm-param class-string<IWidget> $panelClass
*/
public function registerDashboardPanel(string $appId, string $panelClass): void {
$this->dashboardPanels[] = new ServiceRegistration($appId, $panelClass);
@@ -347,8 +486,8 @@ class RegistrationContext {
/**
* @psalm-param class-string<Middleware> $class
*/
- public function registerMiddleware(string $appId, string $class): void {
- $this->middlewares[] = new ServiceRegistration($appId, $class);
+ public function registerMiddleware(string $appId, string $class, bool $global): void {
+ $this->middlewares[] = new MiddlewareRegistration($appId, $class, $global);
}
public function registerSearchProvider(string $appId, string $class) {
@@ -367,10 +506,26 @@ class RegistrationContext {
$this->wellKnownHandlers[] = new ServiceRegistration($appId, $class);
}
+ public function registerSpeechToTextProvider(string $appId, string $class): void {
+ $this->speechToTextProviders[] = new ServiceRegistration($appId, $class);
+ }
+
+ public function registerTextProcessingProvider(string $appId, string $class): void {
+ $this->textProcessingProviders[] = new ServiceRegistration($appId, $class);
+ }
+
+ public function registerTextToImageProvider(string $appId, string $class): void {
+ $this->textToImageProviders[] = new ServiceRegistration($appId, $class);
+ }
+
public function registerTemplateProvider(string $appId, string $class): void {
$this->templateProviders[] = new ServiceRegistration($appId, $class);
}
+ public function registerTranslationProvider(string $appId, string $class): void {
+ $this->translationProviders[] = new ServiceRegistration($appId, $class);
+ }
+
public function registerNotifierService(string $appId, string $class): void {
$this->notifierServices[] = new ServiceRegistration($appId, $class);
}
@@ -387,6 +542,10 @@ class RegistrationContext {
$this->calendarProviders[] = new ServiceRegistration($appId, $class);
}
+ public function registerReferenceProvider(string $appId, string $class): void {
+ $this->referenceProviders[] = new ServiceRegistration($appId, $class);
+ }
+
/**
* @psalm-param class-string<ILinkAction> $actionClass
*/
@@ -400,10 +559,10 @@ class RegistrationContext {
public function registerTalkBackend(string $appId, string $backend) {
// Some safeguards for invalid registrations
if ($appId !== 'spreed') {
- throw new RuntimeException("Only the Talk app is allowed to register a Talk backend");
+ throw new RuntimeException('Only the Talk app is allowed to register a Talk backend');
}
if ($this->talkBackendRegistration !== null) {
- throw new RuntimeException("There can only be one Talk backend");
+ throw new RuntimeException('There can only be one Talk backend');
}
$this->talkBackendRegistration = new ServiceRegistration($appId, $backend);
@@ -424,12 +583,80 @@ class RegistrationContext {
}
/**
+ * @psalm-param class-string<ITeamResourceProvider> $class
+ */
+ public function registerTeamResourceProvider(string $appId, string $class) {
+ $this->teamResourceProviders[] = new ServiceRegistration(
+ $appId,
+ $class
+ );
+ }
+
+ /**
* @psalm-param class-string<IUserMigrator> $migratorClass
*/
public function registerUserMigrator(string $appId, string $migratorClass): void {
$this->userMigrators[] = new ServiceRegistration($appId, $migratorClass);
}
+ public function registerSensitiveMethods(string $appId, string $class, array $methods): void {
+ $methods = array_filter($methods, 'is_string');
+ $this->sensitiveMethods[] = new ParameterRegistration($appId, $class, $methods);
+ }
+
+ public function registerPublicShareTemplateProvider(string $appId, string $class): void {
+ $this->publicShareTemplateProviders[] = new ServiceRegistration($appId, $class);
+ }
+
+ /**
+ * @psalm-param class-string<ISetupCheck> $setupCheckClass
+ */
+ public function registerSetupCheck(string $appId, string $setupCheckClass): void {
+ $this->setupChecks[] = new ServiceRegistration($appId, $setupCheckClass);
+ }
+
+ /**
+ * @psalm-param class-string<IDeclarativeSettingsForm> $declarativeSettingsClass
+ */
+ public function registerDeclarativeSettings(string $appId, string $declarativeSettingsClass): void {
+ $this->declarativeSettings[] = new ServiceRegistration($appId, $declarativeSettingsClass);
+ }
+
+ /**
+ * @psalm-param class-string<\OCP\TaskProcessing\IProvider> $declarativeSettingsClass
+ */
+ public function registerTaskProcessingProvider(string $appId, string $taskProcessingProviderClass): void {
+ $this->taskProcessingProviders[] = new ServiceRegistration($appId, $taskProcessingProviderClass);
+ }
+
+ /**
+ * @psalm-param class-string<\OCP\TaskProcessing\ITaskType> $declarativeSettingsClass
+ */
+ public function registerTaskProcessingTaskType(string $appId, string $taskProcessingTaskTypeClass) {
+ $this->taskProcessingTaskTypes[] = new ServiceRegistration($appId, $taskProcessingTaskTypeClass);
+ }
+
+ /**
+ * @psalm-param class-string<\OCP\Files\Conversion\IConversionProvider> $class
+ */
+ public function registerFileConversionProvider(string $appId, string $class): void {
+ $this->fileConversionProviders[] = new ServiceRegistration($appId, $class);
+ }
+
+ /**
+ * @psalm-param class-string<IMailProvider> $migratorClass
+ */
+ public function registerMailProvider(string $appId, string $class): void {
+ $this->mailProviders[] = new ServiceRegistration($appId, $class);
+ }
+
+ /**
+ * @psalm-param class-string<ILexicon> $configLexiconClass
+ */
+ public function registerConfigLexicon(string $appId, string $configLexiconClass): void {
+ $this->configLexiconClasses[$appId] = $configLexiconClass;
+ }
+
/**
* @param App[] $apps
*/
@@ -472,13 +699,10 @@ class RegistrationContext {
}
}
- /**
- * @param App[] $apps
- */
- public function delegateDashboardPanelRegistrations(array $apps, IManager $dashboardManager): void {
+ public function delegateDashboardPanelRegistrations(IManager $dashboardManager): void {
while (($panel = array_shift($this->dashboardPanels)) !== null) {
try {
- $dashboardManager->lazyRegisterWidget($panel->getService());
+ $dashboardManager->lazyRegisterWidget($panel->getService(), $panel->getAppId());
} catch (Throwable $e) {
$appId = $panel->getAppId();
$this->logger->error("Error during dashboard registration of $appId: " . $e->getMessage(), [
@@ -587,29 +811,10 @@ class RegistrationContext {
}
/**
- * @param App[] $apps
+ * @return MiddlewareRegistration[]
*/
- public function delegateMiddlewareRegistrations(array $apps): void {
- while (($middleware = array_shift($this->middlewares)) !== null) {
- $appId = $middleware->getAppId();
- if (!isset($apps[$appId])) {
- // If we land here something really isn't right. But at least we caught the
- // notice that is otherwise emitted for the undefined index
- $this->logger->error("App $appId not loaded for the container middleware registration");
-
- continue;
- }
-
- try {
- $apps[$appId]
- ->getContainer()
- ->registerMiddleWare($middleware->getService());
- } catch (Throwable $e) {
- $this->logger->error("Error during capability registration of $appId: " . $e->getMessage(), [
- 'exception' => $e,
- ]);
- }
- }
+ public function getMiddlewareRegistrations(): array {
+ return $this->middlewares;
}
/**
@@ -641,6 +846,27 @@ class RegistrationContext {
}
/**
+ * @return ServiceRegistration<ISpeechToTextProvider>[]
+ */
+ public function getSpeechToTextProviders(): array {
+ return $this->speechToTextProviders;
+ }
+
+ /**
+ * @return ServiceRegistration<ITextProcessingProvider>[]
+ */
+ public function getTextProcessingProviders(): array {
+ return $this->textProcessingProviders;
+ }
+
+ /**
+ * @return ServiceRegistration<\OCP\TextToImage\IProvider>[]
+ */
+ public function getTextToImageProviders(): array {
+ return $this->textToImageProviders;
+ }
+
+ /**
* @return ServiceRegistration<ICustomTemplateProvider>[]
*/
public function getTemplateProviders(): array {
@@ -648,6 +874,13 @@ class RegistrationContext {
}
/**
+ * @return ServiceRegistration<ITranslationProvider>[]
+ */
+ public function getTranslationProviders(): array {
+ return $this->translationProviders;
+ }
+
+ /**
* @return ServiceRegistration<INotifier>[]
*/
public function getNotifierServices(): array {
@@ -676,6 +909,13 @@ class RegistrationContext {
}
/**
+ * @return ServiceRegistration<IReferenceProvider>[]
+ */
+ public function getReferenceProviders(): array {
+ return $this->referenceProviders;
+ }
+
+ /**
* @return ServiceRegistration<ILinkAction>[]
*/
public function getProfileLinkActions(): array {
@@ -712,4 +952,83 @@ class RegistrationContext {
public function getUserMigrators(): array {
return $this->userMigrators;
}
+
+ /**
+ * @return ParameterRegistration[]
+ */
+ public function getSensitiveMethods(): array {
+ return $this->sensitiveMethods;
+ }
+
+ /**
+ * @return ServiceRegistration<IPublicShareTemplateProvider>[]
+ */
+ public function getPublicShareTemplateProviders(): array {
+ return $this->publicShareTemplateProviders;
+ }
+
+ /**
+ * @return ServiceRegistration<ISetupCheck>[]
+ */
+ public function getSetupChecks(): array {
+ return $this->setupChecks;
+ }
+
+ /**
+ * @return ServiceRegistration<ITeamResourceProvider>[]
+ */
+ public function getTeamResourceProviders(): array {
+ return $this->teamResourceProviders;
+ }
+
+ /**
+ * @return ServiceRegistration<IDeclarativeSettingsForm>[]
+ */
+ public function getDeclarativeSettings(): array {
+ return $this->declarativeSettings;
+ }
+
+ /**
+ * @return ServiceRegistration<\OCP\TaskProcessing\IProvider>[]
+ */
+ public function getTaskProcessingProviders(): array {
+ return $this->taskProcessingProviders;
+ }
+
+ /**
+ * @return ServiceRegistration<\OCP\TaskProcessing\ITaskType>[]
+ */
+ public function getTaskProcessingTaskTypes(): array {
+ return $this->taskProcessingTaskTypes;
+ }
+
+ /**
+ * @return ServiceRegistration<\OCP\Files\Conversion\IConversionProvider>[]
+ */
+ public function getFileConversionProviders(): array {
+ return $this->fileConversionProviders;
+ }
+
+ /**
+ * @return ServiceRegistration<IMailProvider>[]
+ */
+ public function getMailProviders(): array {
+ return $this->mailProviders;
+ }
+
+ /**
+ * returns IConfigLexicon registered by the app.
+ * null if none registered.
+ *
+ * @param string $appId
+ *
+ * @return ILexicon|null
+ */
+ public function getConfigLexicon(string $appId): ?ILexicon {
+ if (!array_key_exists($appId, $this->configLexiconClasses)) {
+ return null;
+ }
+
+ return \OCP\Server::get($this->configLexiconClasses[$appId]);
+ }
}
diff --git a/lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php b/lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php
index a8b9ddd1295..aa3e38e4c46 100644
--- a/lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php
+++ b/lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Bootstrap;
@@ -29,7 +12,6 @@ namespace OC\AppFramework\Bootstrap;
* @psalm-immutable
*/
class ServiceAliasRegistration extends ARegistration {
-
/**
* @var string
* @psalm-var string|class-string
@@ -47,8 +29,8 @@ class ServiceAliasRegistration extends ARegistration {
* @paslm-param string|class-string $target
*/
public function __construct(string $appId,
- string $alias,
- string $target) {
+ string $alias,
+ string $target) {
parent::__construct($appId);
$this->alias = $alias;
$this->target = $target;
diff --git a/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php b/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php
index 21e6d37865a..63e73410b5a 100644
--- a/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php
+++ b/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Bootstrap;
@@ -29,7 +12,6 @@ namespace OC\AppFramework\Bootstrap;
* @psalm-immutable
*/
class ServiceFactoryRegistration extends ARegistration {
-
/**
* @var string
* @psalm-var string|class-string
@@ -46,9 +28,9 @@ class ServiceFactoryRegistration extends ARegistration {
private $shared;
public function __construct(string $appId,
- string $alias,
- callable $target,
- bool $shared) {
+ string $alias,
+ callable $target,
+ bool $shared) {
parent::__construct($appId);
$this->name = $alias;
$this->factory = $target;
diff --git a/lib/private/AppFramework/Bootstrap/ServiceRegistration.php b/lib/private/AppFramework/Bootstrap/ServiceRegistration.php
index 6c89cdf9d71..6eda5e0196f 100644
--- a/lib/private/AppFramework/Bootstrap/ServiceRegistration.php
+++ b/lib/private/AppFramework/Bootstrap/ServiceRegistration.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Bootstrap;
diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php
index e06d5226a28..76261fe6b92 100644
--- a/lib/private/AppFramework/DependencyInjection/DIContainer.php
+++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php
@@ -1,46 +1,33 @@
<?php
+
+declare(strict_types=1);
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Bernhard Posselt <dev@bernhard-posselt.com>
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin McCorkell <robin@mccorkell.me.uk>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Sebastian Wessalowski <sebastian@wessalowski.org>
- * @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\DependencyInjection;
-use OC;
use OC\AppFramework\Http;
use OC\AppFramework\Http\Dispatcher;
use OC\AppFramework\Http\Output;
+use OC\AppFramework\Middleware\AdditionalScriptsMiddleware;
+use OC\AppFramework\Middleware\CompressionMiddleware;
+use OC\AppFramework\Middleware\FlowV2EphemeralSessionsMiddleware;
use OC\AppFramework\Middleware\MiddlewareDispatcher;
+use OC\AppFramework\Middleware\NotModifiedMiddleware;
use OC\AppFramework\Middleware\OCSMiddleware;
+use OC\AppFramework\Middleware\PublicShare\PublicShareMiddleware;
+use OC\AppFramework\Middleware\Security\BruteForceMiddleware;
use OC\AppFramework\Middleware\Security\CORSMiddleware;
+use OC\AppFramework\Middleware\Security\CSPMiddleware;
+use OC\AppFramework\Middleware\Security\FeaturePolicyMiddleware;
+use OC\AppFramework\Middleware\Security\PasswordConfirmationMiddleware;
use OC\AppFramework\Middleware\Security\RateLimitingMiddleware;
+use OC\AppFramework\Middleware\Security\ReloadExecutionMiddleware;
+use OC\AppFramework\Middleware\Security\SameSiteCookieMiddleware;
use OC\AppFramework\Middleware\Security\SecurityMiddleware;
use OC\AppFramework\Middleware\SessionMiddleware;
use OC\AppFramework\ScopedPsrLogger;
@@ -51,57 +38,45 @@ use OC\Log\PsrLoggerAdapter;
use OC\ServerContainer;
use OC\Settings\AuthorizedGroupMapper;
use OCA\WorkflowEngine\Manager;
+use OCP\App\IAppManager;
use OCP\AppFramework\Http\IOutput;
use OCP\AppFramework\IAppContainer;
use OCP\AppFramework\QueryException;
use OCP\AppFramework\Services\IAppConfig;
use OCP\AppFramework\Services\IInitialState;
use OCP\AppFramework\Utility\IControllerMethodReflector;
-use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Files\AppData\IAppDataFactory;
use OCP\Files\Folder;
use OCP\Files\IAppData;
use OCP\Group\ISubAdmin;
use OCP\IConfig;
use OCP\IDBConnection;
-use OCP\IInitialStateService;
+use OCP\IGroupManager;
use OCP\IL10N;
-use OCP\ILogger;
use OCP\INavigationManager;
use OCP\IRequest;
use OCP\IServerContainer;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUserSession;
+use OCP\Security\Ip\IRemoteAddress;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
-/**
- * @deprecated 20.0.0
- */
class DIContainer extends SimpleContainer implements IAppContainer {
+ protected string $appName;
+ private array $middleWares = [];
+ private ServerContainer $server;
- /**
- * @var array
- */
- private $middleWares = [];
-
- /** @var ServerContainer */
- private $server;
-
- /**
- * Put your class dependencies in here
- * @param string $appName the name of the app
- * @param array $urlParams
- * @param ServerContainer|null $server
- */
- public function __construct($appName, $urlParams = [], ServerContainer $server = null) {
+ public function __construct(string $appName, array $urlParams = [], ?ServerContainer $server = null) {
parent::__construct();
- $this['AppName'] = $appName;
- $this['urlParams'] = $urlParams;
+ $this->appName = $appName;
+ $this->registerParameter('appName', $appName);
+ $this->registerParameter('urlParams', $urlParams);
- $this->registerAlias('Request', IRequest::class);
+ /** @deprecated 32.0.0 */
+ $this->registerDeprecatedAlias('Request', IRequest::class);
- /** @var \OC\ServerContainer $server */
if ($server === null) {
$server = \OC::$server;
}
@@ -109,84 +84,84 @@ class DIContainer extends SimpleContainer implements IAppContainer {
$this->server->registerAppContainer($appName, $this);
// aliases
- $this->registerAlias('appName', 'AppName');
- $this->registerAlias('webRoot', 'WebRoot');
- $this->registerAlias('userId', 'UserId');
+ /** @deprecated 26.0.0 inject $appName */
+ $this->registerDeprecatedAlias('AppName', 'appName');
+ /** @deprecated 26.0.0 inject $webRoot*/
+ $this->registerDeprecatedAlias('WebRoot', 'webRoot');
+ /** @deprecated 26.0.0 inject $userId */
+ $this->registerDeprecatedAlias('UserId', 'userId');
/**
* Core services
*/
- $this->registerService(IOutput::class, function () {
- return new Output($this->getServer()->getWebRoot());
- });
+ /* Cannot be an alias because Output is not in OCA */
+ $this->registerService(IOutput::class, fn (ContainerInterface $c): IOutput => new Output($c->get('webRoot')));
$this->registerService(Folder::class, function () {
return $this->getServer()->getUserFolder();
});
- $this->registerService(IAppData::class, function (ContainerInterface $c) {
- return $this->getServer()->getAppDataDir($c->get('AppName'));
+ $this->registerService(IAppData::class, function (ContainerInterface $c): IAppData {
+ return $c->get(IAppDataFactory::class)->get($c->get('appName'));
});
$this->registerService(IL10N::class, function (ContainerInterface $c) {
- return $this->getServer()->getL10N($c->get('AppName'));
+ return $this->getServer()->getL10N($c->get('appName'));
});
// Log wrappers
$this->registerService(LoggerInterface::class, function (ContainerInterface $c) {
+ /* Cannot be an alias because it uses LoggerInterface so it would infinite loop */
return new ScopedPsrLogger(
$c->get(PsrLoggerAdapter::class),
- $c->get('AppName')
+ $c->get('appName')
);
});
- $this->registerService(ILogger::class, function (ContainerInterface $c) {
- return new OC\AppFramework\Logger($this->server->query(ILogger::class), $c->get('AppName'));
- });
$this->registerService(IServerContainer::class, function () {
return $this->getServer();
});
- $this->registerAlias('ServerContainer', IServerContainer::class);
+ /** @deprecated 32.0.0 */
+ $this->registerDeprecatedAlias('ServerContainer', IServerContainer::class);
- $this->registerService(\OCP\WorkflowEngine\IManager::class, function (ContainerInterface $c) {
- return $c->get(Manager::class);
- });
+ $this->registerAlias(\OCP\WorkflowEngine\IManager::class, Manager::class);
- $this->registerService(ContainerInterface::class, function (ContainerInterface $c) {
- return $c;
- });
- $this->registerAlias(IAppContainer::class, ContainerInterface::class);
+ $this->registerService(ContainerInterface::class, fn (ContainerInterface $c) => $c);
+ $this->registerDeprecatedAlias(IAppContainer::class, ContainerInterface::class);
// commonly used attributes
- $this->registerService('UserId', function (ContainerInterface $c) {
- return $c->get(IUserSession::class)->getSession()->get('user_id');
+ $this->registerService('userId', function (ContainerInterface $c): ?string {
+ return $c->get(ISession::class)->get('user_id');
});
- $this->registerService('WebRoot', function (ContainerInterface $c) {
+ $this->registerService('webRoot', function (ContainerInterface $c): string {
return $c->get(IServerContainer::class)->getWebRoot();
});
- $this->registerService('OC_Defaults', function (ContainerInterface $c) {
- return $c->get(IServerContainer::class)->getThemingDefaults();
+ $this->registerService('OC_Defaults', function (ContainerInterface $c): object {
+ return $c->get(IServerContainer::class)->get('ThemingDefaults');
});
- $this->registerService('Protocol', function (ContainerInterface $c) {
- /** @var \OC\Server $server */
- $server = $c->get(IServerContainer::class);
- $protocol = $server->getRequest()->getHttpProtocol();
+ /** @deprecated 32.0.0 */
+ $this->registerDeprecatedAlias('Protocol', Http::class);
+ $this->registerService(Http::class, function (ContainerInterface $c) {
+ $protocol = $c->get(IRequest::class)->getHttpProtocol();
return new Http($_SERVER, $protocol);
});
- $this->registerService('Dispatcher', function (ContainerInterface $c) {
+ /** @deprecated 32.0.0 */
+ $this->registerDeprecatedAlias('Dispatcher', Dispatcher::class);
+ $this->registerService(Dispatcher::class, function (ContainerInterface $c) {
return new Dispatcher(
- $c->get('Protocol'),
+ $c->get(Http::class),
$c->get(MiddlewareDispatcher::class),
$c->get(IControllerMethodReflector::class),
$c->get(IRequest::class),
$c->get(IConfig::class),
$c->get(IDBConnection::class),
$c->get(LoggerInterface::class),
- $c->get(EventLogger::class)
+ $c->get(EventLogger::class),
+ $c,
);
});
@@ -200,139 +175,69 @@ class DIContainer extends SimpleContainer implements IAppContainer {
/**
* Middleware
*/
- $this->registerAlias('MiddlewareDispatcher', MiddlewareDispatcher::class);
+ /** @deprecated 32.0.0 */
+ $this->registerDeprecatedAlias('MiddlewareDispatcher', MiddlewareDispatcher::class);
$this->registerService(MiddlewareDispatcher::class, function (ContainerInterface $c) {
$server = $this->getServer();
$dispatcher = new MiddlewareDispatcher();
- $dispatcher->registerMiddleware(
- $c->get(OC\AppFramework\Middleware\CompressionMiddleware::class)
- );
-
- $dispatcher->registerMiddleware($c->get(OC\AppFramework\Middleware\NotModifiedMiddleware::class));
-
- $dispatcher->registerMiddleware(
- $c->get(OC\AppFramework\Middleware\Security\ReloadExecutionMiddleware::class)
- );
-
- $dispatcher->registerMiddleware(
- new OC\AppFramework\Middleware\Security\SameSiteCookieMiddleware(
- $c->get(IRequest::class),
- $c->get(IControllerMethodReflector::class)
- )
- );
- $dispatcher->registerMiddleware(
- new CORSMiddleware(
- $c->get(IRequest::class),
- $c->get(IControllerMethodReflector::class),
- $c->get(IUserSession::class),
- $c->get(OC\Security\Bruteforce\Throttler::class)
- )
- );
- $dispatcher->registerMiddleware(
- new OCSMiddleware(
- $c->get(IRequest::class)
- )
- );
-
+ $dispatcher->registerMiddleware($c->get(CompressionMiddleware::class));
+ $dispatcher->registerMiddleware($c->get(NotModifiedMiddleware::class));
+ $dispatcher->registerMiddleware($c->get(ReloadExecutionMiddleware::class));
+ $dispatcher->registerMiddleware($c->get(SameSiteCookieMiddleware::class));
+ $dispatcher->registerMiddleware($c->get(CORSMiddleware::class));
+ $dispatcher->registerMiddleware($c->get(OCSMiddleware::class));
+ $dispatcher->registerMiddleware($c->get(FlowV2EphemeralSessionsMiddleware::class));
$securityMiddleware = new SecurityMiddleware(
$c->get(IRequest::class),
$c->get(IControllerMethodReflector::class),
$c->get(INavigationManager::class),
$c->get(IURLGenerator::class),
- $server->get(LoggerInterface::class),
- $c->get('AppName'),
+ $c->get(LoggerInterface::class),
+ $c->get('appName'),
$server->getUserSession()->isLoggedIn(),
- $this->getUserId() !== null && $server->getGroupManager()->isAdmin($this->getUserId()),
- $server->getUserSession()->getUser() !== null && $server->query(ISubAdmin::class)->isSubAdmin($server->getUserSession()->getUser()),
- $server->getAppManager(),
+ $c->get(IGroupManager::class),
+ $c->get(ISubAdmin::class),
+ $c->get(IAppManager::class),
$server->getL10N('lib'),
$c->get(AuthorizedGroupMapper::class),
- $server->get(IUserSession::class)
+ $c->get(IUserSession::class),
+ $c->get(IRemoteAddress::class),
);
$dispatcher->registerMiddleware($securityMiddleware);
- $dispatcher->registerMiddleware(
- new OC\AppFramework\Middleware\Security\CSPMiddleware(
- $server->query(OC\Security\CSP\ContentSecurityPolicyManager::class),
- $server->query(OC\Security\CSP\ContentSecurityPolicyNonceManager::class),
- $server->query(OC\Security\CSRF\CsrfTokenManager::class)
- )
- );
- $dispatcher->registerMiddleware(
- $server->query(OC\AppFramework\Middleware\Security\FeaturePolicyMiddleware::class)
- );
- $dispatcher->registerMiddleware(
- new OC\AppFramework\Middleware\Security\PasswordConfirmationMiddleware(
- $c->get(IControllerMethodReflector::class),
- $c->get(ISession::class),
- $c->get(IUserSession::class),
- $c->get(ITimeFactory::class)
- )
- );
- $dispatcher->registerMiddleware(
- new TwoFactorMiddleware(
- $c->get(OC\Authentication\TwoFactorAuth\Manager::class),
- $c->get(IUserSession::class),
- $c->get(ISession::class),
- $c->get(IURLGenerator::class),
- $c->get(IControllerMethodReflector::class),
- $c->get(IRequest::class)
- )
- );
- $dispatcher->registerMiddleware(
- new OC\AppFramework\Middleware\Security\BruteForceMiddleware(
- $c->get(IControllerMethodReflector::class),
- $c->get(OC\Security\Bruteforce\Throttler::class),
- $c->get(IRequest::class)
- )
- );
- $dispatcher->registerMiddleware(
- new RateLimitingMiddleware(
- $c->get(IRequest::class),
- $c->get(IUserSession::class),
- $c->get(IControllerMethodReflector::class),
- $c->get(OC\Security\RateLimiting\Limiter::class)
- )
- );
- $dispatcher->registerMiddleware(
- new OC\AppFramework\Middleware\PublicShare\PublicShareMiddleware(
- $c->get(IRequest::class),
- $c->get(ISession::class),
- $c->get(\OCP\IConfig::class)
- )
- );
- $dispatcher->registerMiddleware(
- $c->get(\OC\AppFramework\Middleware\AdditionalScriptsMiddleware::class)
- );
-
+ $dispatcher->registerMiddleware($c->get(CSPMiddleware::class));
+ $dispatcher->registerMiddleware($c->get(FeaturePolicyMiddleware::class));
+ $dispatcher->registerMiddleware($c->get(PasswordConfirmationMiddleware::class));
+ $dispatcher->registerMiddleware($c->get(TwoFactorMiddleware::class));
+ $dispatcher->registerMiddleware($c->get(BruteForceMiddleware::class));
+ $dispatcher->registerMiddleware($c->get(RateLimitingMiddleware::class));
+ $dispatcher->registerMiddleware($c->get(PublicShareMiddleware::class));
+ $dispatcher->registerMiddleware($c->get(AdditionalScriptsMiddleware::class));
+
+ $coordinator = $c->get(\OC\AppFramework\Bootstrap\Coordinator::class);
+ $registrationContext = $coordinator->getRegistrationContext();
+ if ($registrationContext !== null) {
+ $appId = $this->get('appName');
+ foreach ($registrationContext->getMiddlewareRegistrations() as $middlewareRegistration) {
+ if ($middlewareRegistration->getAppId() === $appId
+ || $middlewareRegistration->isGlobal()) {
+ $dispatcher->registerMiddleware($c->get($middlewareRegistration->getService()));
+ }
+ }
+ }
foreach ($this->middleWares as $middleWare) {
$dispatcher->registerMiddleware($c->get($middleWare));
}
- $dispatcher->registerMiddleware(
- new SessionMiddleware(
- $c->get(IControllerMethodReflector::class),
- $c->get(ISession::class)
- )
- );
+ $dispatcher->registerMiddleware($c->get(SessionMiddleware::class));
return $dispatcher;
});
- $this->registerService(IAppConfig::class, function (ContainerInterface $c) {
- return new OC\AppFramework\Services\AppConfig(
- $c->get(IConfig::class),
- $c->get('AppName')
- );
- });
- $this->registerService(IInitialState::class, function (ContainerInterface $c) {
- return new OC\AppFramework\Services\InitialState(
- $c->get(IInitialStateService::class),
- $c->get('AppName')
- );
- });
+ $this->registerAlias(IAppConfig::class, \OC\AppFramework\Services\AppConfig::class);
+ $this->registerAlias(IInitialState::class, \OC\AppFramework\Services\InitialState::class);
}
/**
@@ -344,13 +249,13 @@ class DIContainer extends SimpleContainer implements IAppContainer {
/**
* @param string $middleWare
- * @return boolean|null
*/
- public function registerMiddleWare($middleWare) {
+ public function registerMiddleWare($middleWare): bool {
if (in_array($middleWare, $this->middleWares, true) !== false) {
return false;
}
$this->middleWares[] = $middleWare;
+ return true;
}
/**
@@ -358,11 +263,11 @@ class DIContainer extends SimpleContainer implements IAppContainer {
* @return string the name of your application
*/
public function getAppName() {
- return $this->query('AppName');
+ return $this->query('appName');
}
/**
- * @deprecated use IUserSession->isLoggedIn()
+ * @deprecated 12.0.0 use IUserSession->isLoggedIn()
* @return boolean
*/
public function isLoggedIn() {
@@ -370,7 +275,7 @@ class DIContainer extends SimpleContainer implements IAppContainer {
}
/**
- * @deprecated use IGroupManager->isAdmin($userId)
+ * @deprecated 12.0.0 use IGroupManager->isAdmin($userId)
* @return boolean
*/
public function isAdminUser() {
@@ -378,38 +283,11 @@ class DIContainer extends SimpleContainer implements IAppContainer {
return \OC_User::isAdminUser($uid);
}
- private function getUserId() {
+ private function getUserId(): string {
return $this->getServer()->getSession()->get('user_id');
}
/**
- * @deprecated use the ILogger instead
- * @param string $message
- * @param string $level
- * @return mixed
- */
- public function log($message, $level) {
- switch ($level) {
- case 'debug':
- $level = ILogger::DEBUG;
- break;
- case 'info':
- $level = ILogger::INFO;
- break;
- case 'warn':
- $level = ILogger::WARN;
- break;
- case 'fatal':
- $level = ILogger::FATAL;
- break;
- default:
- $level = ILogger::ERROR;
- break;
- }
- \OCP\Util::writeLog($this->getAppName(), $message, $level);
- }
-
- /**
* Register a capability
*
* @param string $serviceName e.g. 'OCA\Files\Capabilities'
@@ -433,6 +311,15 @@ class DIContainer extends SimpleContainer implements IAppContainer {
}
public function query(string $name, bool $autoload = true) {
+ if ($name === 'AppName' || $name === 'appName') {
+ return $this->appName;
+ }
+
+ $isServerClass = str_starts_with($name, 'OCP\\') || str_starts_with($name, 'OC\\');
+ if ($isServerClass && !$this->has($name)) {
+ return $this->getServer()->query($name, $autoload);
+ }
+
try {
return $this->queryNoFallback($name);
} catch (QueryException $firstException) {
@@ -457,15 +344,21 @@ class DIContainer extends SimpleContainer implements IAppContainer {
if ($this->offsetExists($name)) {
return parent::query($name);
- } elseif ($this['AppName'] === 'settings' && strpos($name, 'OC\\Settings\\') === 0) {
+ } elseif ($this->appName === 'settings' && str_starts_with($name, 'OC\\Settings\\')) {
+ return parent::query($name);
+ } elseif ($this->appName === 'core' && str_starts_with($name, 'OC\\Core\\')) {
return parent::query($name);
- } elseif ($this['AppName'] === 'core' && strpos($name, 'OC\\Core\\') === 0) {
+ } elseif (str_starts_with($name, \OC\AppFramework\App::buildAppNamespace($this->appName) . '\\')) {
return parent::query($name);
- } elseif (strpos($name, \OC\AppFramework\App::buildAppNamespace($this['AppName']) . '\\') === 0) {
+ } elseif (
+ str_starts_with($name, 'OC\\AppFramework\\Services\\')
+ || str_starts_with($name, 'OC\\AppFramework\\Middleware\\')
+ ) {
+ /* AppFramework services are scoped to the application */
return parent::query($name);
}
- throw new QueryException('Could not resolve ' . $name . '!' .
- ' Class can not be instantiated', 1);
+ throw new QueryException('Could not resolve ' . $name . '!'
+ . ' Class can not be instantiated', 1);
}
}
diff --git a/lib/private/AppFramework/Http.php b/lib/private/AppFramework/Http.php
index 251c231fe65..08d6259c2a2 100644
--- a/lib/private/AppFramework/Http.php
+++ b/lib/private/AppFramework/Http.php
@@ -1,31 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin McCorkell <robin@mccorkell.me.uk>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @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;
@@ -124,7 +102,7 @@ class Http extends BaseHttp {
$status = self::STATUS_FOUND;
}
- return $this->protocolVersion . ' ' . $status . ' ' .
- $this->headers[$status];
+ return $this->protocolVersion . ' ' . $status . ' '
+ . $this->headers[$status];
}
}
diff --git a/lib/private/AppFramework/Http/Dispatcher.php b/lib/private/AppFramework/Http/Dispatcher.php
index 21d61bc95aa..8d91ddf7502 100644
--- a/lib/private/AppFramework/Http/Dispatcher.php
+++ b/lib/private/AppFramework/Http/Dispatcher.php
@@ -1,34 +1,10 @@
<?php
declare(strict_types=1);
-
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Bernhard Posselt <dev@bernhard-posselt.com>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @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\Http;
@@ -38,17 +14,18 @@ use OC\AppFramework\Utility\ControllerMethodReflector;
use OC\DB\ConnectionAdapter;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\Http\ParameterOutOfRangeException;
use OCP\AppFramework\Http\Response;
use OCP\Diagnostics\IEventLogger;
use OCP\IConfig;
use OCP\IRequest;
+use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
/**
* Class to dispatch the request to the middleware dispatcher
*/
class Dispatcher {
-
/** @var MiddlewareDispatcher */
private $middlewareDispatcher;
@@ -73,26 +50,31 @@ class Dispatcher {
/** @var IEventLogger */
private $eventLogger;
+ private ContainerInterface $appContainer;
+
/**
* @param Http $protocol the http protocol with contains all status headers
* @param MiddlewareDispatcher $middlewareDispatcher the dispatcher which
- * runs the middleware
+ * runs the middleware
* @param ControllerMethodReflector $reflector the reflector that is used to inject
- * the arguments for the controller
+ * the arguments for the controller
* @param IRequest $request the incoming request
* @param IConfig $config
* @param ConnectionAdapter $connection
* @param LoggerInterface $logger
* @param IEventLogger $eventLogger
*/
- public function __construct(Http $protocol,
- MiddlewareDispatcher $middlewareDispatcher,
- ControllerMethodReflector $reflector,
- IRequest $request,
- IConfig $config,
- ConnectionAdapter $connection,
- LoggerInterface $logger,
- IEventLogger $eventLogger) {
+ public function __construct(
+ Http $protocol,
+ MiddlewareDispatcher $middlewareDispatcher,
+ ControllerMethodReflector $reflector,
+ IRequest $request,
+ IConfig $config,
+ ConnectionAdapter $connection,
+ LoggerInterface $logger,
+ IEventLogger $eventLogger,
+ ContainerInterface $appContainer,
+ ) {
$this->protocol = $protocol;
$this->middlewareDispatcher = $middlewareDispatcher;
$this->reflector = $reflector;
@@ -101,6 +83,7 @@ class Dispatcher {
$this->connection = $connection;
$this->logger = $logger;
$this->eventLogger = $eventLogger;
+ $this->appContainer = $appContainer;
}
@@ -108,17 +91,19 @@ class Dispatcher {
* Handles a request and calls the dispatcher on the controller
* @param Controller $controller the controller which will be called
* @param string $methodName the method name which will be called on
- * the controller
- * @return array $array[0] contains a string with the http main header,
- * $array[1] contains headers in the form: $key => value, $array[2] contains
- * the response output
+ * the controller
+ * @return array $array[0] contains the http status header as a string,
+ * $array[1] contains response headers as an array,
+ * $array[2] contains response cookies as an array,
+ * $array[3] contains the response output as a string,
+ * $array[4] contains the response object
* @throws \Exception
*/
public function dispatch(Controller $controller, string $methodName): array {
$out = [null, [], null];
try {
- // prefill reflector with everything thats needed for the
+ // prefill reflector with everything that's needed for the
// middlewares
$this->reflector->reflect($controller, $methodName);
@@ -156,7 +141,7 @@ class Dispatcher {
// if an exception appears, the middleware checks if it can handle the
// exception and creates a response. If no response is created, it is
- // assumed that theres no middleware who can handle it and the error is
+ // assumed that there's no middleware who can handle it and the error is
// thrown again
} catch (\Exception $exception) {
$response = $this->middlewareDispatcher->afterException(
@@ -164,7 +149,7 @@ class Dispatcher {
} catch (\Throwable $throwable) {
$exception = new \Exception($throwable->getMessage() . ' in file \'' . $throwable->getFile() . '\' line ' . $throwable->getLine(), $throwable->getCode(), $throwable);
$response = $this->middlewareDispatcher->afterException(
- $controller, $methodName, $exception);
+ $controller, $methodName, $exception);
}
$response = $this->middlewareDispatcher->afterController(
@@ -193,29 +178,23 @@ class Dispatcher {
private function executeController(Controller $controller, string $methodName): Response {
$arguments = [];
- // valid types that will be casted
- $types = ['int', 'integer', 'bool', 'boolean', 'float'];
+ // valid types that will be cast
+ $types = ['int', 'integer', 'bool', 'boolean', 'float', 'double'];
foreach ($this->reflector->getParameters() as $param => $default) {
-
// try to get the parameter from the request object and cast
// it to the type annotated in the @param annotation
$value = $this->request->getParam($param, $default);
$type = $this->reflector->getType($param);
- // if this is submitted using GET or a POST form, 'false' should be
- // converted to false
- if (($type === 'bool' || $type === 'boolean') &&
- $value === 'false' &&
- (
- $this->request->method === 'GET' ||
- strpos($this->request->getHeader('Content-Type'),
- 'application/x-www-form-urlencoded') !== false
- )
- ) {
+ // Converted the string `'false'` to false when the controller wants a boolean
+ if ($value === 'false' && ($type === 'bool' || $type === 'boolean')) {
$value = false;
} elseif ($value !== null && \in_array($type, $types, true)) {
settype($value, $type);
+ $this->ensureParameterValueSatisfiesRange($param, $value);
+ } elseif ($value === null && $type !== null && $this->appContainer->has($type)) {
+ $value = $this->appContainer->get($type);
}
$arguments[] = $value;
@@ -225,9 +204,12 @@ class Dispatcher {
$response = \call_user_func_array([$controller, $methodName], $arguments);
$this->eventLogger->end('controller:' . get_class($controller) . '::' . $methodName);
+ if (!($response instanceof Response)) {
+ $this->logger->debug($controller::class . '::' . $methodName . ' returned raw data. Please wrap it in a Response or one of it\'s inheritors.');
+ }
+
// format response
if ($response instanceof DataResponse || !($response instanceof Response)) {
-
// get format from the url format or request format parameter
$format = $this->request->getParam('format');
@@ -246,4 +228,22 @@ class Dispatcher {
return $response;
}
+
+ /**
+ * @psalm-param mixed $value
+ * @throws ParameterOutOfRangeException
+ */
+ private function ensureParameterValueSatisfiesRange(string $param, $value): void {
+ $rangeInfo = $this->reflector->getRange($param);
+ if ($rangeInfo) {
+ if ($value < $rangeInfo['min'] || $value > $rangeInfo['max']) {
+ throw new ParameterOutOfRangeException(
+ $param,
+ $value,
+ $rangeInfo['min'],
+ $rangeInfo['max'],
+ );
+ }
+ }
+ }
}
diff --git a/lib/private/AppFramework/Http/Output.php b/lib/private/AppFramework/Http/Output.php
index 963e01456e0..b4a8672fdc7 100644
--- a/lib/private/AppFramework/Http/Output.php
+++ b/lib/private/AppFramework/Http/Output.php
@@ -1,28 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Bernhard Posselt <dev@bernhard-posselt.com>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Stefan Weil <sw@weilnetz.de>
- *
- * @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\Http;
@@ -32,14 +13,9 @@ use OCP\AppFramework\Http\IOutput;
* Very thin wrapper class to make output testable
*/
class Output implements IOutput {
- /** @var string */
- private $webRoot;
-
- /**
- * @param $webRoot
- */
- public function __construct($webRoot) {
- $this->webRoot = $webRoot;
+ public function __construct(
+ private string $webRoot,
+ ) {
}
/**
diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php
index f896b825f2d..7cc7467675c 100644
--- a/lib/private/AppFramework/Http/Request.php
+++ b/lib/private/AppFramework/Http/Request.php
@@ -1,45 +1,10 @@
<?php
declare(strict_types=1);
-
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author b108@volgograd "b108@volgograd"
- * @author Bart Visscher <bartv@thisnet.nl>
- * @author Bernhard Posselt <dev@bernhard-posselt.com>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Georg Ehrke <oc.list@georgehrke.com>
- * @author J0WI <J0WI@users.noreply.github.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Juan Pablo Villafáñez <jvillafanez@solidgear.es>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Mitar <mitar.git@tnode.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Oliver Wegner <void1976@gmail.com>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Robin McCorkell <robin@mccorkell.me.uk>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Thomas Tanghus <thomas@tanghus.net>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @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\Http;
@@ -49,38 +14,41 @@ use OC\Security\TrustedDomainHelper;
use OCP\IConfig;
use OCP\IRequest;
use OCP\IRequestId;
-use OCP\Security\ICrypto;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\HttpFoundation\IpUtils;
/**
* Class for accessing variables in the request.
* This class provides an immutable object with request variables.
*
- * @property mixed[] cookies
- * @property mixed[] env
- * @property mixed[] files
- * @property string method
- * @property mixed[] parameters
- * @property mixed[] server
+ * @property mixed[] $cookies
+ * @property mixed[] $env
+ * @property mixed[] $files
+ * @property string $method
+ * @property mixed[] $parameters
+ * @property mixed[] $server
+ * @template-implements \ArrayAccess<string,mixed>
*/
class Request implements \ArrayAccess, \Countable, IRequest {
public const USER_AGENT_IE = '/(MSIE)|(Trident)/';
// Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
- public const USER_AGENT_MS_EDGE = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+ Edge\/[0-9.]+$/';
+ public const USER_AGENT_MS_EDGE = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+ Edge?\/[0-9.]+$/';
// Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference
public const USER_AGENT_FIREFOX = '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/[0-9.]+$/';
// Chrome User Agent from https://developer.chrome.com/multidevice/user-agent
public const USER_AGENT_CHROME = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)( Ubuntu Chromium\/[0-9.]+|) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+( (Vivaldi|Brave|OPR)\/[0-9.]+|)$/';
// Safari User Agent from http://www.useragentstring.com/pages/Safari/
public const USER_AGENT_SAFARI = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ Safari\/[0-9.A-Z]+$/';
+ public const USER_AGENT_SAFARI_MOBILE = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ (Mobile\/[0-9.A-Z]+) Safari\/[0-9.A-Z]+$/';
// Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
public const USER_AGENT_ANDROID_MOBILE_CHROME = '#Android.*Chrome/[.0-9]*#';
public const USER_AGENT_FREEBOX = '#^Mozilla/5\.0$#';
public const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost|\[::1\])$/';
- protected $inputStream;
- protected $content;
- protected $items = [];
- protected $allowedKeys = [
+ protected string $inputStream;
+ private bool $isPutStreamContentAlreadySent = false;
+ protected array $items = [];
+ protected array $allowedKeys = [
'get',
'post',
'files',
@@ -92,29 +60,24 @@ class Request implements \ArrayAccess, \Countable, IRequest {
'method',
'requesttoken',
];
- /** @var RequestId */
- protected $requestId;
- /** @var IConfig */
- protected $config;
- /** @var ICrypto */
- protected $crypto;
- /** @var CsrfTokenManager|null */
- protected $csrfTokenManager;
+ protected IRequestId $requestId;
+ protected IConfig $config;
+ protected ?CsrfTokenManager $csrfTokenManager;
- /** @var bool */
- protected $contentDecoded = false;
+ protected bool $contentDecoded = false;
+ private ?\JsonException $decodingException = null;
/**
* @param array $vars An associative array with the following optional values:
- * - array 'urlParams' the parameters which were matched from the URL
- * - array 'get' the $_GET array
- * - array|string 'post' the $_POST array or JSON string
- * - array 'files' the $_FILES array
- * - array 'server' the $_SERVER array
- * - array 'env' the $_ENV array
- * - array 'cookies' the $_COOKIE array
- * - string 'method' the request method (GET, POST etc)
- * - string|false 'requesttoken' the requesttoken or false when not available
+ * - array 'urlParams' the parameters which were matched from the URL
+ * - array 'get' the $_GET array
+ * - array|string 'post' the $_POST array or JSON string
+ * - array 'files' the $_FILES array
+ * - array 'server' the $_SERVER array
+ * - array 'env' the $_ENV array
+ * - array 'cookies' the $_COOKIE array
+ * - string 'method' the request method (GET, POST etc)
+ * - string|false 'requesttoken' the requesttoken or false when not available
* @param IRequestId $requestId
* @param IConfig $config
* @param CsrfTokenManager|null $csrfTokenManager
@@ -122,10 +85,10 @@ class Request implements \ArrayAccess, \Countable, IRequest {
* @see https://www.php.net/manual/en/reserved.variables.php
*/
public function __construct(array $vars,
- IRequestId $requestId,
- IConfig $config,
- CsrfTokenManager $csrfTokenManager = null,
- string $stream = 'php://input') {
+ IRequestId $requestId,
+ IConfig $config,
+ ?CsrfTokenManager $csrfTokenManager = null,
+ string $stream = 'php://input') {
$this->inputStream = $stream;
$this->items['params'] = [];
$this->requestId = $requestId;
@@ -137,9 +100,7 @@ class Request implements \ArrayAccess, \Countable, IRequest {
}
foreach ($this->allowedKeys as $name) {
- $this->items[$name] = isset($vars[$name])
- ? $vars[$name]
- : [];
+ $this->items[$name] = $vars[$name] ?? [];
}
$this->items['parameters'] = array_merge(
@@ -199,9 +160,7 @@ class Request implements \ArrayAccess, \Countable, IRequest {
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset) {
- return isset($this->items['parameters'][$offset])
- ? $this->items['parameters'][$offset]
- : null;
+ return $this->items['parameters'][$offset] ?? null;
}
/**
@@ -261,11 +220,12 @@ class Request implements \ArrayAccess, \Countable, IRequest {
case 'cookies':
case 'urlParams':
case 'method':
- return isset($this->items[$name])
- ? $this->items[$name]
- : null;
+ return $this->items[$name] ?? null;
case 'parameters':
case 'params':
+ if ($this->isPutStreamContent()) {
+ return $this->items['parameters'];
+ }
return $this->getContent();
default:
return isset($this[$name])
@@ -326,11 +286,11 @@ class Request implements \ArrayAccess, \Countable, IRequest {
* In case of json requests the encoded json body is accessed
*
* @param string $key the key which you want to access in the URL Parameter
- * placeholder, $_POST or $_GET array.
- * The priority how they're returned is the following:
- * 1. URL parameters
- * 2. POST parameters
- * 3. GET parameters
+ * placeholder, $_POST or $_GET array.
+ * The priority how they're returned is the following:
+ * 1. URL parameters
+ * 2. POST parameters
+ * 3. GET parameters
* @param mixed $default If the key is not found, this value will be returned
* @return mixed the content of the array
*/
@@ -342,7 +302,7 @@ class Request implements \ArrayAccess, \Countable, IRequest {
/**
* Returns all params that were received, be it from the request
- * (as GET or POST) or throuh the URL by the route
+ * (as GET or POST) or through the URL by the route
* @return array the array with all parameters
*/
public function getParams(): array {
@@ -397,19 +357,14 @@ class Request implements \ArrayAccess, \Countable, IRequest {
*/
protected function getContent() {
// If the content can't be parsed into an array then return a stream resource.
- if ($this->method === 'PUT'
- && $this->getHeader('Content-Length') !== '0'
- && $this->getHeader('Content-Length') !== ''
- && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false
- && strpos($this->getHeader('Content-Type'), 'application/json') === false
- ) {
- if ($this->content === false) {
+ if ($this->isPutStreamContent()) {
+ if ($this->isPutStreamContentAlreadySent) {
throw new \LogicException(
'"put" can only be accessed once if not '
. 'application/x-www-form-urlencoded or application/json.'
);
}
- $this->content = false;
+ $this->isPutStreamContentAlreadySent = true;
return fopen($this->inputStream, 'rb');
} else {
$this->decodeContent();
@@ -417,6 +372,14 @@ class Request implements \ArrayAccess, \Countable, IRequest {
}
}
+ private function isPutStreamContent(): bool {
+ return $this->method === 'PUT'
+ && $this->getHeader('Content-Length') !== '0'
+ && $this->getHeader('Content-Length') !== ''
+ && !str_contains($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded')
+ && !str_contains($this->getHeader('Content-Type'), 'application/json');
+ }
+
/**
* Attempt to decode the content and populate parameters
*/
@@ -426,21 +389,27 @@ class Request implements \ArrayAccess, \Countable, IRequest {
}
$params = [];
- // 'application/json' must be decoded manually.
- if (strpos($this->getHeader('Content-Type'), 'application/json') !== false) {
- $params = json_decode(file_get_contents($this->inputStream), true);
- if ($params !== null && \count($params) > 0) {
+ // 'application/json' and other JSON-related content types must be decoded manually.
+ if (preg_match(self::JSON_CONTENT_TYPE_REGEX, $this->getHeader('Content-Type')) === 1) {
+ $content = file_get_contents($this->inputStream);
+ if ($content !== '') {
+ try {
+ $params = json_decode($content, true, flags:JSON_THROW_ON_ERROR);
+ } catch (\JsonException $e) {
+ $this->decodingException = $e;
+ }
+ }
+ if (\is_array($params) && \count($params) > 0) {
$this->items['params'] = $params;
if ($this->method === 'POST') {
$this->items['post'] = $params;
}
}
-
// Handle application/x-www-form-urlencoded for methods other than GET
- // or post correctly
+ // or post correctly
} elseif ($this->method !== 'GET'
&& $this->method !== 'POST'
- && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') !== false) {
+ && str_contains($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded')) {
parse_str(file_get_contents($this->inputStream), $params);
if (\is_array($params)) {
$this->items['params'] = $params;
@@ -453,6 +422,12 @@ class Request implements \ArrayAccess, \Countable, IRequest {
$this->contentDecoded = true;
}
+ public function throwDecodingExceptionIfAny(): void {
+ if ($this->decodingException !== null) {
+ throw $this->decodingException;
+ }
+ }
+
/**
* Checks if the CSRF check was correct
@@ -467,6 +442,10 @@ class Request implements \ArrayAccess, \Countable, IRequest {
return false;
}
+ if ($this->getHeader('OCS-APIRequest') !== '') {
+ return true;
+ }
+
if (isset($this->items['get']['requesttoken'])) {
$token = $this->items['get']['requesttoken'];
} elseif (isset($this->items['post']['requesttoken'])) {
@@ -520,7 +499,7 @@ class Request implements \ArrayAccess, \Countable, IRequest {
$prefix = '__Host-';
}
- return $prefix.$name;
+ return $prefix . $name;
}
/**
@@ -573,41 +552,19 @@ class Request implements \ArrayAccess, \Countable, IRequest {
}
/**
- * Checks if given $remoteAddress matches given $trustedProxy.
- * If $trustedProxy is an IPv4 IP range given in CIDR notation, true will be returned if
- * $remoteAddress is an IPv4 address within that IP range.
- * Otherwise $remoteAddress will be compared to $trustedProxy literally and the result
- * will be returned.
- * @return boolean true if $remoteAddress matches $trustedProxy, false otherwise
- */
- protected function matchesTrustedProxy($trustedProxy, $remoteAddress) {
- $cidrre = '/^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\/([0-9]{1,2})$/';
-
- if (preg_match($cidrre, $trustedProxy, $match)) {
- $net = $match[1];
- $shiftbits = min(32, max(0, 32 - intval($match[2])));
- $netnum = ip2long($net) >> $shiftbits;
- $ipnum = ip2long($remoteAddress) >> $shiftbits;
-
- return $ipnum === $netnum;
- }
-
- return $trustedProxy === $remoteAddress;
- }
-
- /**
* Checks if given $remoteAddress matches any entry in the given array $trustedProxies.
* For details regarding what "match" means, refer to `matchesTrustedProxy`.
* @return boolean true if $remoteAddress matches any entry in $trustedProxies, false otherwise
*/
protected function isTrustedProxy($trustedProxies, $remoteAddress) {
- foreach ($trustedProxies as $tp) {
- if ($this->matchesTrustedProxy($tp, $remoteAddress)) {
- return true;
- }
+ try {
+ return IpUtils::checkIp($remoteAddress, $trustedProxies);
+ } catch (\Throwable) {
+ // We can not log to our log here as the logger is using `getRemoteAddress` which uses the function, so we would have a cyclic dependency
+ // Reaching this line means `trustedProxies` is in invalid format.
+ error_log('Nextcloud trustedProxies has malformed entries');
+ return false;
}
-
- return false;
}
/**
@@ -627,14 +584,25 @@ class Request implements \ArrayAccess, \Countable, IRequest {
// only have one default, so we cannot ship an insecure product out of the box
]);
- foreach ($forwardedForHeaders as $header) {
+ // Read the x-forwarded-for headers and values in reverse order as per
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#selecting_an_ip_address
+ foreach (array_reverse($forwardedForHeaders) as $header) {
if (isset($this->server[$header])) {
- foreach (explode(',', $this->server[$header]) as $IP) {
+ foreach (array_reverse(explode(',', $this->server[$header])) as $IP) {
$IP = trim($IP);
+ $colons = substr_count($IP, ':');
+ if ($colons > 1) {
+ // Extract IP from string with brackets and optional port
+ if (preg_match('/^\[(.+?)\](?::\d+)?$/', $IP, $matches) && isset($matches[1])) {
+ $IP = $matches[1];
+ }
+ } elseif ($colons === 1) {
+ // IPv4 with port
+ $IP = substr($IP, 0, strpos($IP, ':'));
+ }
- // remove brackets from IPv6 addresses
- if (strpos($IP, '[') === 0 && substr($IP, -1) === ']') {
- $IP = substr($IP, 1, -1);
+ if ($this->isTrustedProxy($trustedProxies, $IP)) {
+ continue;
}
if (filter_var($IP, FILTER_VALIDATE_IP) !== false) {
@@ -650,48 +618,56 @@ class Request implements \ArrayAccess, \Countable, IRequest {
/**
* Check overwrite condition
- * @param string $type
* @return bool
*/
- private function isOverwriteCondition(string $type = ''): bool {
- $regex = '/' . $this->config->getSystemValue('overwritecondaddr', '') . '/';
+ private function isOverwriteCondition(): bool {
+ $regex = '/' . $this->config->getSystemValueString('overwritecondaddr', '') . '/';
$remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
- return $regex === '//' || preg_match($regex, $remoteAddr) === 1
- || $type !== 'protocol';
+ return $regex === '//' || preg_match($regex, $remoteAddr) === 1;
}
/**
* Returns the server protocol. It respects one or more reverse proxies servers
- * and load balancers
+ * and load balancers. Precedence:
+ * 1. `overwriteprotocol` config value
+ * 2. `X-Forwarded-Proto` header value
+ * 3. $_SERVER['HTTPS'] value
+ * If an invalid protocol is provided, defaults to http, continues, but logs as an error.
+ *
* @return string Server protocol (http or https)
*/
public function getServerProtocol(): string {
- if ($this->config->getSystemValue('overwriteprotocol') !== ''
- && $this->isOverwriteCondition('protocol')) {
- return $this->config->getSystemValue('overwriteprotocol');
- }
+ $proto = 'http';
- if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_PROTO'])) {
- if (strpos($this->server['HTTP_X_FORWARDED_PROTO'], ',') !== false) {
+ if ($this->config->getSystemValueString('overwriteprotocol') !== ''
+ && $this->isOverwriteCondition()
+ ) {
+ $proto = strtolower($this->config->getSystemValueString('overwriteprotocol'));
+ } elseif ($this->fromTrustedProxy()
+ && isset($this->server['HTTP_X_FORWARDED_PROTO'])
+ ) {
+ if (str_contains($this->server['HTTP_X_FORWARDED_PROTO'], ',')) {
$parts = explode(',', $this->server['HTTP_X_FORWARDED_PROTO']);
$proto = strtolower(trim($parts[0]));
} else {
$proto = strtolower($this->server['HTTP_X_FORWARDED_PROTO']);
}
-
- // Verify that the protocol is always HTTP or HTTPS
- // default to http if an invalid value is provided
- return $proto === 'https' ? 'https' : 'http';
+ } elseif (!empty($this->server['HTTPS'])
+ && $this->server['HTTPS'] !== 'off'
+ ) {
+ $proto = 'https';
}
- if (isset($this->server['HTTPS'])
- && $this->server['HTTPS'] !== null
- && $this->server['HTTPS'] !== 'off'
- && $this->server['HTTPS'] !== '') {
- return 'https';
+ if ($proto !== 'https' && $proto !== 'http') {
+ // log unrecognized value so admin has a chance to fix it
+ \OCP\Server::get(LoggerInterface::class)->critical(
+ 'Server protocol is malformed [falling back to http] (check overwriteprotocol and/or X-Forwarded-Proto to remedy): ' . $proto,
+ ['app' => 'core']
+ );
}
- return 'http';
+ // default to http if provided an invalid value
+ return $proto === 'https' ? 'https' : 'http';
}
/**
@@ -726,7 +702,7 @@ class Request implements \ArrayAccess, \Countable, IRequest {
*/
public function getRequestUri(): string {
$uri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
- if ($this->config->getSystemValue('overwritewebroot') !== '' && $this->isOverwriteCondition()) {
+ if ($this->config->getSystemValueString('overwritewebroot') !== '' && $this->isOverwriteCondition()) {
$uri = $this->getScriptName() . substr($uri, \strlen($this->server['SCRIPT_NAME']));
}
return $uri;
@@ -754,7 +730,7 @@ class Request implements \ArrayAccess, \Countable, IRequest {
// FIXME: Sabre does not really belong here
[$path, $name] = \Sabre\Uri\split($scriptName);
if (!empty($path)) {
- if ($path === $pathInfo || strpos($pathInfo, $path.'/') === 0) {
+ if ($path === $pathInfo || str_starts_with($pathInfo, $path . '/')) {
$pathInfo = substr($pathInfo, \strlen($path));
} else {
throw new \Exception("The requested uri($requestUri) cannot be processed by the script '$scriptName')");
@@ -764,10 +740,10 @@ class Request implements \ArrayAccess, \Countable, IRequest {
$name = '';
}
- if (strpos($pathInfo, '/'.$name) === 0) {
+ if (str_starts_with($pathInfo, '/' . $name)) {
$pathInfo = substr($pathInfo, \strlen($name) + 1);
}
- if ($name !== '' && strpos($pathInfo, $name) === 0) {
+ if ($name !== '' && str_starts_with($pathInfo, $name)) {
$pathInfo = substr($pathInfo, \strlen($name));
}
if ($pathInfo === false || $pathInfo === '/') {
@@ -778,23 +754,13 @@ class Request implements \ArrayAccess, \Countable, IRequest {
}
/**
- * Get PathInfo from request
+ * Get PathInfo from request (rawurldecoded)
* @throws \Exception
* @return string|false Path info or false when not found
*/
- public function getPathInfo() {
+ public function getPathInfo(): string|false {
$pathInfo = $this->getRawPathInfo();
- // following is taken from \Sabre\HTTP\URLUtil::decodePathSegment
- $pathInfo = rawurldecode($pathInfo);
- $encoding = mb_detect_encoding($pathInfo, ['UTF-8', 'ISO-8859-1']);
-
- switch ($encoding) {
- case 'ISO-8859-1':
- $pathInfo = utf8_encode($pathInfo);
- }
- // end copy
-
- return $pathInfo;
+ return \Sabre\HTTP\decodePath($pathInfo);
}
/**
@@ -804,7 +770,7 @@ class Request implements \ArrayAccess, \Countable, IRequest {
*/
public function getScriptName(): string {
$name = $this->server['SCRIPT_NAME'];
- $overwriteWebRoot = $this->config->getSystemValue('overwritewebroot');
+ $overwriteWebRoot = $this->config->getSystemValueString('overwritewebroot');
if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) {
// FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous
$serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -\strlen('lib/private/appframework/http/')));
@@ -843,7 +809,7 @@ class Request implements \ArrayAccess, \Countable, IRequest {
$host = 'localhost';
if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_HOST'])) {
- if (strpos($this->server['HTTP_X_FORWARDED_HOST'], ',') !== false) {
+ if (str_contains($this->server['HTTP_X_FORWARDED_HOST'], ',')) {
$parts = explode(',', $this->server['HTTP_X_FORWARDED_HOST']);
$host = trim(current($parts));
} else {
@@ -896,11 +862,11 @@ class Request implements \ArrayAccess, \Countable, IRequest {
* Returns the overwritehost setting from the config if set and
* if the overwrite condition is met
* @return string|null overwritehost value or null if not defined or the defined condition
- * isn't met
+ * isn't met
*/
private function getOverwriteHost() {
- if ($this->config->getSystemValue('overwritehost') !== '' && $this->isOverwriteCondition()) {
- return $this->config->getSystemValue('overwritehost');
+ if ($this->config->getSystemValueString('overwritehost') !== '' && $this->isOverwriteCondition()) {
+ return $this->config->getSystemValueString('overwritehost');
}
return null;
}
diff --git a/lib/private/AppFramework/Http/RequestId.php b/lib/private/AppFramework/Http/RequestId.php
index 70032873a75..c3a99c93591 100644
--- a/lib/private/AppFramework/Http/RequestId.php
+++ b/lib/private/AppFramework/Http/RequestId.php
@@ -2,24 +2,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2022, Joas Schilling <coding@schilljs.com>
- *
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @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: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\AppFramework\Http;
@@ -31,7 +15,7 @@ class RequestId implements IRequestId {
protected string $requestId;
public function __construct(string $uniqueId,
- ISecureRandom $secureRandom) {
+ ISecureRandom $secureRandom) {
$this->requestId = $uniqueId;
$this->secureRandom = $secureRandom;
}
diff --git a/lib/private/AppFramework/Logger.php b/lib/private/AppFramework/Logger.php
deleted file mode 100644
index 7fb30905df3..00000000000
--- a/lib/private/AppFramework/Logger.php
+++ /dev/null
@@ -1,127 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @copyright Copyright (c) 2018, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-namespace OC\AppFramework;
-
-use OCP\ILogger;
-
-/**
- * @deprecated
- */
-class Logger implements ILogger {
-
- /** @var ILogger */
- private $logger;
-
- /** @var string */
- private $appName;
-
- /**
- * @deprecated
- */
- public function __construct(ILogger $logger, string $appName) {
- $this->logger = $logger;
- $this->appName = $appName;
- }
-
- private function extendContext(array $context): array {
- if (!isset($context['app'])) {
- $context['app'] = $this->appName;
- }
-
- return $context;
- }
-
- /**
- * @deprecated
- */
- public function emergency(string $message, array $context = []) {
- $this->logger->emergency($message, $this->extendContext($context));
- }
-
- /**
- * @deprecated
- */
- public function alert(string $message, array $context = []) {
- $this->logger->alert($message, $this->extendContext($context));
- }
-
- /**
- * @deprecated
- */
- public function critical(string $message, array $context = []) {
- $this->logger->critical($message, $this->extendContext($context));
- }
-
- /**
- * @deprecated
- */
- public function error(string $message, array $context = []) {
- $this->logger->emergency($message, $this->extendContext($context));
- }
-
- /**
- * @deprecated
- */
- public function warning(string $message, array $context = []) {
- $this->logger->warning($message, $this->extendContext($context));
- }
-
- /**
- * @deprecated
- */
- public function notice(string $message, array $context = []) {
- $this->logger->notice($message, $this->extendContext($context));
- }
-
- /**
- * @deprecated
- */
- public function info(string $message, array $context = []) {
- $this->logger->info($message, $this->extendContext($context));
- }
-
- /**
- * @deprecated
- */
- public function debug(string $message, array $context = []) {
- $this->logger->debug($message, $this->extendContext($context));
- }
-
- /**
- * @deprecated
- */
- public function log(int $level, string $message, array $context = []) {
- $this->logger->log($level, $message, $this->extendContext($context));
- }
-
- /**
- * @deprecated
- */
- public function logException(\Throwable $exception, array $context = []) {
- $this->logger->logException($exception, $this->extendContext($context));
- }
-}
diff --git a/lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php b/lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php
index ec52dde0ecd..4f1c69b104f 100644
--- a/lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php
+++ b/lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php
@@ -3,74 +3,36 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Middleware;
+use OC\Core\Controller\LoginController;
+use OCP\AppFramework\Http\Events\BeforeLoginTemplateRenderedEvent;
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\StandaloneTemplateResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Middleware;
-use OCP\AppFramework\PublicShareController;
-use OCP\EventDispatcher\GenericEvent;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IUserSession;
-use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class AdditionalScriptsMiddleware extends Middleware {
- /** @var EventDispatcherInterface */
- private $legacyDispatcher;
- /** @var IUserSession */
- private $userSession;
- /** @var IEventDispatcher */
- private $dispatcher;
-
- public function __construct(EventDispatcherInterface $legacyDispatcher, IUserSession $userSession, IEventDispatcher $dispatcher) {
- $this->legacyDispatcher = $legacyDispatcher;
- $this->userSession = $userSession;
- $this->dispatcher = $dispatcher;
+ public function __construct(
+ private IUserSession $userSession,
+ private IEventDispatcher $dispatcher,
+ ) {
}
public function afterController($controller, $methodName, Response $response): Response {
if ($response instanceof TemplateResponse) {
- if (!$controller instanceof PublicShareController) {
- /*
- * The old event was not dispatched on the public share controller as there was
- * OCA\Files_Sharing::loadAdditionalScripts for that. This is kept for compatibility reasons
- * only for the old event as this is now also included in BeforeTemplateRenderedEvent
- */
- $this->legacyDispatcher->dispatch(TemplateResponse::EVENT_LOAD_ADDITIONAL_SCRIPTS, new GenericEvent());
- }
-
- if (!($response instanceof StandaloneTemplateResponse) && $this->userSession->isLoggedIn()) {
- $this->legacyDispatcher->dispatch(TemplateResponse::EVENT_LOAD_ADDITIONAL_SCRIPTS_LOGGEDIN, new GenericEvent());
- $isLoggedIn = true;
+ if ($controller instanceof LoginController) {
+ $this->dispatcher->dispatchTyped(new BeforeLoginTemplateRenderedEvent($response));
} else {
- $isLoggedIn = false;
+ $isLoggedIn = !($response instanceof StandaloneTemplateResponse) && $this->userSession->isLoggedIn();
+ $this->dispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($isLoggedIn, $response));
}
-
- $this->dispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($isLoggedIn, $response));
}
return $response;
diff --git a/lib/private/AppFramework/Middleware/CompressionMiddleware.php b/lib/private/AppFramework/Middleware/CompressionMiddleware.php
index 5128afa8cad..8bc56beb62e 100644
--- a/lib/private/AppFramework/Middleware/CompressionMiddleware.php
+++ b/lib/private/AppFramework/Middleware/CompressionMiddleware.php
@@ -3,26 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Middleware;
@@ -35,7 +17,6 @@ use OCP\AppFramework\Middleware;
use OCP\IRequest;
class CompressionMiddleware extends Middleware {
-
/** @var bool */
private $useGZip;
@@ -58,7 +39,7 @@ class CompressionMiddleware extends Middleware {
// Check if we are even asked for gzip
$header = $this->request->getHeader('Accept-Encoding');
- if (strpos($header, 'gzip') === false) {
+ if (!str_contains($header, 'gzip')) {
return $response;
}
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/MiddlewareDispatcher.php b/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php
index 950ef8a13a3..c9b51f26f34 100644
--- a/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php
+++ b/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php
@@ -1,33 +1,10 @@
<?php
declare(strict_types=1);
-
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @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;
@@ -39,17 +16,16 @@ use OCP\AppFramework\Middleware;
* This class is used to store and run all the middleware in correct order
*/
class MiddlewareDispatcher {
-
/**
- * @var array array containing all the middlewares
+ * @var Middleware[] array containing all the middlewares
*/
- private $middlewares;
+ private array $middlewares;
/**
- * @var int counter which tells us what middlware was executed once an
- * exception occurs
+ * @var int counter which tells us what middleware was executed once an
+ * exception occurs
*/
- private $middlewareCounter;
+ private int $middlewareCounter;
/**
@@ -65,14 +41,14 @@ class MiddlewareDispatcher {
* Adds a new middleware
* @param Middleware $middleWare the middleware which will be added
*/
- public function registerMiddleware(Middleware $middleWare) {
+ public function registerMiddleware(Middleware $middleWare): void {
$this->middlewares[] = $middleWare;
}
/**
* returns an array with all middleware elements
- * @return array the middlewares
+ * @return Middleware[] the middlewares
*/
public function getMiddlewares(): array {
return $this->middlewares;
@@ -87,7 +63,7 @@ class MiddlewareDispatcher {
* @param string $methodName the name of the method that will be called on
* the controller
*/
- public function beforeController(Controller $controller, string $methodName) {
+ public function beforeController(Controller $controller, string $methodName): void {
// we need to count so that we know which middlewares we have to ask in
// case there is an exception
$middlewareCount = \count($this->middlewares);
@@ -108,10 +84,10 @@ class MiddlewareDispatcher {
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
- * the controller
+ * the controller
* @param \Exception $exception the thrown exception
* @return Response a Response object if the middleware can handle the
- * exception
+ * exception
* @throws \Exception the passed in exception if it can't handle it
*/
public function afterException(Controller $controller, string $methodName, \Exception $exception): Response {
@@ -133,7 +109,7 @@ class MiddlewareDispatcher {
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
- * the controller
+ * the controller
* @param Response $response the generated response from the controller
* @return Response a Response object
*/
diff --git a/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php b/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php
index 4ebdf10f403..08b30092155 100644
--- a/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php
+++ b/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Middleware;
@@ -46,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 4c2dbd0f828..64f4b0054de 100644
--- a/lib/private/AppFramework/Middleware/OCSMiddleware.php
+++ b/lib/private/AppFramework/Middleware/OCSMiddleware.php
@@ -1,27 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Middleware;
@@ -39,7 +20,6 @@ use OCP\AppFramework\OCSController;
use OCP\IRequest;
class OCSMiddleware extends Middleware {
-
/** @var IRequest */
private $request;
diff --git a/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php b/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php
index 8b568f06dd4..5df4009b094 100644
--- a/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php
+++ b/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php
@@ -1,25 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Middleware\PublicShare\Exceptions;
diff --git a/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php b/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php
index d3beb4fd3a8..83e799e3d3b 100644
--- a/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php
+++ b/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php
@@ -1,52 +1,32 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Middleware\PublicShare;
use OC\AppFramework\Middleware\PublicShare\Exceptions\NeedAuthenticationException;
+use OCA\Files_Sharing\AppInfo\Application;
use OCP\AppFramework\AuthPublicShareController;
-use OCP\AppFramework\Http\NotFoundResponse;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\PublicShareController;
use OCP\Files\NotFoundException;
use OCP\IConfig;
use OCP\IRequest;
use OCP\ISession;
+use OCP\Security\Bruteforce\IThrottler;
class PublicShareMiddleware extends Middleware {
- /** @var IRequest */
- private $request;
-
- /** @var ISession */
- private $session;
- /** @var IConfig */
- private $config;
-
- public function __construct(IRequest $request, ISession $session, IConfig $config) {
- $this->request = $request;
- $this->session = $session;
- $this->config = $config;
+ public function __construct(
+ private IRequest $request,
+ private ISession $session,
+ private IConfig $config,
+ private IThrottler $throttler,
+ ) {
}
public function beforeController($controller, $methodName) {
@@ -54,6 +34,11 @@ class PublicShareMiddleware extends Middleware {
return;
}
+ $controllerClassPath = explode('\\', get_class($controller));
+ $controllerShortClass = end($controllerClassPath);
+ $bruteforceProtectionAction = $controllerShortClass . '::' . $methodName;
+ $this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), $bruteforceProtectionAction);
+
if (!$this->isLinkSharingEnabled()) {
throw new NotFoundException('Link sharing is disabled');
}
@@ -68,6 +53,8 @@ class PublicShareMiddleware extends Middleware {
$controller->setToken($token);
if (!$controller->isValidToken()) {
+ $this->throttle($bruteforceProtectionAction, $token);
+
$controller->shareNotFound();
throw new NotFoundException();
}
@@ -88,6 +75,7 @@ class PublicShareMiddleware extends Middleware {
throw new NeedAuthenticationException();
}
+ $this->throttle($bruteforceProtectionAction, $token);
throw new NotFoundException();
}
@@ -97,7 +85,9 @@ class PublicShareMiddleware extends Middleware {
}
if ($exception instanceof NotFoundException) {
- return new NotFoundResponse();
+ return new TemplateResponse(Application::APP_ID, 'sharenotfound', [
+ 'message' => $exception->getMessage(),
+ ], 'guest', Http::STATUS_NOT_FOUND);
}
if ($controller instanceof AuthPublicShareController && $exception instanceof NeedAuthenticationException) {
@@ -128,4 +118,10 @@ class PublicShareMiddleware extends Middleware {
return true;
}
+
+ private function throttle($bruteforceProtectionAction, $token): void {
+ $ip = $this->request->getRemoteAddress();
+ $this->throttler->sleepDelayOrThrowOnMax($ip, $bruteforceProtectionAction);
+ $this->throttler->registerAttempt($bruteforceProtectionAction, $ip, ['token' => $token]);
+ }
}
diff --git a/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php b/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php
index 26f4b9ef46f..4b4425517e0 100644
--- a/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php
@@ -3,41 +3,25 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Middleware\Security;
use OC\AppFramework\Utility\ControllerMethodReflector;
-use OC\Security\Bruteforce\Throttler;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\BruteForceProtection;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TooManyRequestsResponse;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
+use OCP\Security\Bruteforce\IThrottler;
use OCP\Security\Bruteforce\MaxDelayReached;
+use Psr\Log\LoggerInterface;
+use ReflectionMethod;
/**
* Class BruteForceMiddleware performs the bruteforce protection for controllers
@@ -47,24 +31,14 @@ use OCP\Security\Bruteforce\MaxDelayReached;
* @package OC\AppFramework\Middleware\Security
*/
class BruteForceMiddleware extends Middleware {
- /** @var ControllerMethodReflector */
- private $reflector;
- /** @var Throttler */
- private $throttler;
- /** @var IRequest */
- private $request;
+ private int $delaySlept = 0;
- /**
- * @param ControllerMethodReflector $controllerMethodReflector
- * @param Throttler $throttler
- * @param IRequest $request
- */
- public function __construct(ControllerMethodReflector $controllerMethodReflector,
- Throttler $throttler,
- IRequest $request) {
- $this->reflector = $controllerMethodReflector;
- $this->throttler = $throttler;
- $this->request = $request;
+ public function __construct(
+ protected ControllerMethodReflector $reflector,
+ protected IThrottler $throttler,
+ protected IRequest $request,
+ protected LoggerInterface $logger,
+ ) {
}
/**
@@ -75,7 +49,21 @@ class BruteForceMiddleware extends Middleware {
if ($this->reflector->hasAnnotation('BruteForceProtection')) {
$action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
- $this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), $action);
+ $this->delaySlept += $this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), $action);
+ } else {
+ $reflectionMethod = new ReflectionMethod($controller, $methodName);
+ $attributes = $reflectionMethod->getAttributes(BruteForceProtection::class);
+
+ if (!empty($attributes)) {
+ $remoteAddress = $this->request->getRemoteAddress();
+
+ foreach ($attributes as $attribute) {
+ /** @var BruteForceProtection $protection */
+ $protection = $attribute->newInstance();
+ $action = $protection->getAction();
+ $this->delaySlept += $this->throttler->sleepDelayOrThrowOnMax($remoteAddress, $action);
+ }
+ }
}
}
@@ -83,11 +71,46 @@ class BruteForceMiddleware extends Middleware {
* {@inheritDoc}
*/
public function afterController($controller, $methodName, Response $response) {
- if ($this->reflector->hasAnnotation('BruteForceProtection') && $response->isThrottled()) {
- $action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
- $ip = $this->request->getRemoteAddress();
- $this->throttler->sleepDelay($ip, $action);
- $this->throttler->registerAttempt($action, $ip, $response->getThrottleMetadata());
+ if ($response->isThrottled()) {
+ try {
+ if ($this->reflector->hasAnnotation('BruteForceProtection')) {
+ $action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
+ $ip = $this->request->getRemoteAddress();
+ $this->throttler->registerAttempt($action, $ip, $response->getThrottleMetadata());
+ $this->delaySlept += $this->throttler->sleepDelayOrThrowOnMax($ip, $action);
+ } else {
+ $reflectionMethod = new ReflectionMethod($controller, $methodName);
+ $attributes = $reflectionMethod->getAttributes(BruteForceProtection::class);
+
+ if (!empty($attributes)) {
+ $ip = $this->request->getRemoteAddress();
+ $metaData = $response->getThrottleMetadata();
+
+ foreach ($attributes as $attribute) {
+ /** @var BruteForceProtection $protection */
+ $protection = $attribute->newInstance();
+ $action = $protection->getAction();
+
+ if (!isset($metaData['action']) || $metaData['action'] === $action) {
+ $this->throttler->registerAttempt($action, $ip, $metaData);
+ $this->delaySlept += $this->throttler->sleepDelayOrThrowOnMax($ip, $action);
+ }
+ }
+ } else {
+ $this->logger->debug('Response for ' . get_class($controller) . '::' . $methodName . ' got bruteforce throttled but has no annotation nor attribute defined.');
+ }
+ }
+ } catch (MaxDelayReached $e) {
+ if ($controller instanceof OCSController) {
+ throw new OCSException($e->getMessage(), Http::STATUS_TOO_MANY_REQUESTS);
+ }
+
+ return new TooManyRequestsResponse();
+ }
+ }
+
+ if ($this->delaySlept) {
+ $response->addHeader('X-Nextcloud-Bruteforce-Throttled', $this->delaySlept . 'ms');
}
return parent::afterController($controller, $methodName, $response);
diff --git a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
index 1490b69f534..4453f5a7d4b 100644
--- a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
@@ -1,42 +1,28 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Bernhard Posselt <dev@bernhard-posselt.com>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author korelstar <korelstar@users.noreply.github.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Stefan Weil <sw@weilnetz.de>
- *
- * @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\SecurityException;
use OC\AppFramework\Utility\ControllerMethodReflector;
use OC\Authentication\Exceptions\PasswordLoginForbiddenException;
-use OC\Security\Bruteforce\Throttler;
use OC\User\Session;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\CORS;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
use OCP\IRequest;
+use OCP\ISession;
+use OCP\Security\Bruteforce\IThrottler;
+use Psr\Log\LoggerInterface;
+use ReflectionMethod;
/**
* This middleware sets the correct CORS headers on a response if the
@@ -45,25 +31,22 @@ use OCP\IRequest;
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
*/
class CORSMiddleware extends Middleware {
- /** @var IRequest */
+ /** @var IRequest */
private $request;
/** @var ControllerMethodReflector */
private $reflector;
/** @var Session */
private $session;
- /** @var Throttler */
+ /** @var IThrottler */
private $throttler;
- /**
- * @param IRequest $request
- * @param ControllerMethodReflector $reflector
- * @param Session $session
- * @param Throttler $throttler
- */
- public function __construct(IRequest $request,
- ControllerMethodReflector $reflector,
- Session $session,
- Throttler $throttler) {
+ public function __construct(
+ IRequest $request,
+ ControllerMethodReflector $reflector,
+ Session $session,
+ IThrottler $throttler,
+ private readonly LoggerInterface $logger,
+ ) {
$this->request = $request;
$this->reflector = $reflector;
$this->session = $session;
@@ -81,12 +64,23 @@ class CORSMiddleware extends Middleware {
* @since 6.0.0
*/
public function beforeController($controller, $methodName) {
+ $reflectionMethod = new ReflectionMethod($controller, $methodName);
+
// ensure that @CORS annotated API routes are not used in conjunction
// with session authentication since this enables CSRF attack vectors
- if ($this->reflector->hasAnnotation('CORS') && !$this->reflector->hasAnnotation('PublicPage')) {
+ 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;
+ // Allow to use the current session if a CSRF token is provided
+ if ($this->request->passesCSRFCheck()) {
+ return;
+ }
+ // Skip CORS check for requests with AppAPI auth.
+ if ($this->session->getSession() instanceof ISession && $this->session->getSession()->get('app_api') === true) {
+ return;
+ }
$this->session->logout();
try {
if ($user === null || $pass === null || !$this->session->logClientIn($user, $pass, $this->request, $this->throttler)) {
@@ -99,7 +93,29 @@ class CORSMiddleware extends Middleware {
}
/**
- * This is being run after a successful controllermethod call and allows
+ * @template T
+ *
+ * @param ReflectionMethod $reflectionMethod
+ * @param string $annotationName
+ * @param class-string<T> $attributeClass
+ * @return boolean
+ */
+ protected function hasAnnotationOrAttribute(ReflectionMethod $reflectionMethod, string $annotationName, string $attributeClass): bool {
+ if ($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;
+ }
+
+
+ if (!empty($reflectionMethod->getAttributes($attributeClass))) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * This is being run after a successful controller method call and allows
* the manipulation of a Response object. The middleware is run in reverse order
*
* @param Controller $controller the controller that is being called
@@ -110,24 +126,25 @@ class CORSMiddleware extends Middleware {
* @throws SecurityException
*/
public function afterController($controller, $methodName, Response $response) {
- // only react if its a CORS request and if the request sends origin and
-
- if (isset($this->request->server['HTTP_ORIGIN']) &&
- $this->reflector->hasAnnotation('CORS')) {
-
- // 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';
- throw new SecurityException($msg);
+ // only react if it's a CORS request and if the request sends origin and
+
+ if (isset($this->request->server['HTTP_ORIGIN'])) {
+ $reflectionMethod = new ReflectionMethod($controller, $methodName);
+ if ($this->hasAnnotationOrAttribute($reflectionMethod, 'CORS', CORS::class)) {
+ // 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';
+ throw new SecurityException($msg);
+ }
}
- }
- $origin = $this->request->server['HTTP_ORIGIN'];
- $response->addHeader('Access-Control-Allow-Origin', $origin);
+ $origin = $this->request->server['HTTP_ORIGIN'];
+ $response->addHeader('Access-Control-Allow-Origin', $origin);
+ }
}
return $response;
}
diff --git a/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php b/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php
index 1eea52d620c..e88c9563c00 100644
--- a/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php
@@ -3,32 +3,13 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Middleware\Security;
use OC\Security\CSP\ContentSecurityPolicyManager;
use OC\Security\CSP\ContentSecurityPolicyNonceManager;
-use OC\Security\CSRF\CsrfTokenManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
@@ -37,19 +18,10 @@ use OCP\AppFramework\Middleware;
class CSPMiddleware extends Middleware {
- /** @var ContentSecurityPolicyManager */
- private $contentSecurityPolicyManager;
- /** @var ContentSecurityPolicyNonceManager */
- private $cspNonceManager;
- /** @var CsrfTokenManager */
- private $csrfTokenManager;
-
- public function __construct(ContentSecurityPolicyManager $policyManager,
- ContentSecurityPolicyNonceManager $cspNonceManager,
- CsrfTokenManager $csrfTokenManager) {
- $this->contentSecurityPolicyManager = $policyManager;
- $this->cspNonceManager = $cspNonceManager;
- $this->csrfTokenManager = $csrfTokenManager;
+ public function __construct(
+ private ContentSecurityPolicyManager $policyManager,
+ private ContentSecurityPolicyNonceManager $cspNonceManager,
+ ) {
}
/**
@@ -68,11 +40,11 @@ class CSPMiddleware extends Middleware {
return $response;
}
- $defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy();
- $defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy);
+ $defaultPolicy = $this->policyManager->getDefaultPolicy();
+ $defaultPolicy = $this->policyManager->mergePolicies($defaultPolicy, $policy);
if ($this->cspNonceManager->browserSupportsCspV3()) {
- $defaultPolicy->useJsNonce($this->csrfTokenManager->getToken()->getEncryptedValue());
+ $defaultPolicy->useJsNonce($this->cspNonceManager->getNonce());
}
$response->setContentSecurityPolicy($defaultPolicy);
diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/AdminIpNotAllowedException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/AdminIpNotAllowedException.php
new file mode 100644
index 00000000000..36eb8f18928
--- /dev/null
+++ b/lib/private/AppFramework/Middleware/Security/Exceptions/AdminIpNotAllowedException.php
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OC\AppFramework\Middleware\Security\Exceptions;
+
+use OCP\AppFramework\Http;
+
+/**
+ * Class AdminIpNotAllowed is thrown when a resource has been requested by a
+ * an admin user connecting from an unauthorized IP address
+ * See configuration `allowed_admin_ranges`
+ *
+ * @package OC\AppFramework\Middleware\Security\Exceptions
+ */
+class AdminIpNotAllowedException extends SecurityException {
+ public function __construct(string $message) {
+ parent::__construct($message, Http::STATUS_FORBIDDEN);
+ }
+}
diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php
index 2b9c5a2280b..53fbaaf5ed2 100644
--- a/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php
+++ b/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php
@@ -1,26 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @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\Exceptions;
diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/CrossSiteRequestForgeryException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/CrossSiteRequestForgeryException.php
index b30ebe58abd..0c6a28134ca 100644
--- a/lib/private/AppFramework/Middleware/Security/Exceptions/CrossSiteRequestForgeryException.php
+++ b/lib/private/AppFramework/Middleware/Security/Exceptions/CrossSiteRequestForgeryException.php
@@ -1,26 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @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\Exceptions;
diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/ExAppRequiredException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/ExAppRequiredException.php
new file mode 100644
index 00000000000..77bc7efebac
--- /dev/null
+++ b/lib/private/AppFramework/Middleware/Security/Exceptions/ExAppRequiredException.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\AppFramework\Middleware\Security\Exceptions;
+
+use OCP\AppFramework\Http;
+
+/**
+ * Class ExAppRequiredException is thrown when an endpoint can only be called by an ExApp but the caller is not an ExApp.
+ */
+class ExAppRequiredException extends SecurityException {
+ public function __construct() {
+ parent::__construct('ExApp required', Http::STATUS_PRECONDITION_FAILED);
+ }
+}
diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php
index bbb8e746127..0380c6781aa 100644
--- a/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php
+++ b/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php
@@ -1,24 +1,8 @@
<?php
+
/**
- * @copyright 2017, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Middleware\Security\Exceptions;
diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/NotAdminException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/NotAdminException.php
index 1f1b9d6e501..6252c914ac1 100644
--- a/lib/private/AppFramework/Middleware/Security/Exceptions/NotAdminException.php
+++ b/lib/private/AppFramework/Middleware/Security/Exceptions/NotAdminException.php
@@ -1,30 +1,10 @@
<?php
declare(strict_types=1);
-
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @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\Exceptions;
diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php
index b466e9a4c7a..ca30f736fbc 100644
--- a/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php
+++ b/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php
@@ -1,24 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Middleware\Security\Exceptions;
@@ -31,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/Exceptions/NotLoggedInException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/NotLoggedInException.php
index a0287c20435..e5a7853a64b 100644
--- a/lib/private/AppFramework/Middleware/Security/Exceptions/NotLoggedInException.php
+++ b/lib/private/AppFramework/Middleware/Security/Exceptions/NotLoggedInException.php
@@ -1,26 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @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\Exceptions;
diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/ReloadExecutionException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/ReloadExecutionException.php
index 3c65d5f5a88..d12ee96292e 100644
--- a/lib/private/AppFramework/Middleware/Security/Exceptions/ReloadExecutionException.php
+++ b/lib/private/AppFramework/Middleware/Security/Exceptions/ReloadExecutionException.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Middleware\Security\Exceptions;
diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php
index 3232980b7e5..c8d70ad4f2b 100644
--- a/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php
+++ b/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php
@@ -1,26 +1,10 @@
<?php
+
+declare(strict_types=1);
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @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\Exceptions;
diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/StrictCookieMissingException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/StrictCookieMissingException.php
index eca03896953..8ae20ab4e70 100644
--- a/lib/private/AppFramework/Middleware/Security/Exceptions/StrictCookieMissingException.php
+++ b/lib/private/AppFramework/Middleware/Security/Exceptions/StrictCookieMissingException.php
@@ -1,24 +1,10 @@
<?php
+
+declare(strict_types=1);
+
/**
- * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
- *
- * @author Lukas Reschke <lukas@statuscode.ch>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Middleware\Security\Exceptions;
diff --git a/lib/private/AppFramework/Middleware/Security/FeaturePolicyMiddleware.php b/lib/private/AppFramework/Middleware/Security/FeaturePolicyMiddleware.php
index 534ff56cced..921630e6326 100644
--- a/lib/private/AppFramework/Middleware/Security/FeaturePolicyMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/FeaturePolicyMiddleware.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Middleware\Security;
@@ -33,7 +16,6 @@ use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
class FeaturePolicyMiddleware extends Middleware {
-
/** @var FeaturePolicyManager */
private $policyManager;
diff --git a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php
index 0ee9fdff881..0facbffe504 100644
--- a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php
@@ -1,92 +1,121 @@
<?php
+
/**
- * @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Middleware\Security;
use OC\AppFramework\Middleware\Security\Exceptions\NotConfirmedException;
use OC\AppFramework\Utility\ControllerMethodReflector;
+use OC\Authentication\Token\IProvider;
+use OC\User\Manager;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Authentication\Exceptions\ExpiredTokenException;
+use OCP\Authentication\Exceptions\InvalidTokenException;
+use OCP\Authentication\Exceptions\WipeTokenException;
+use OCP\Authentication\Token\IToken;
+use OCP\IRequest;
use OCP\ISession;
use OCP\IUserSession;
+use OCP\Session\Exceptions\SessionNotAvailableException;
use OCP\User\Backend\IPasswordConfirmationBackend;
+use Psr\Log\LoggerInterface;
+use ReflectionMethod;
class PasswordConfirmationMiddleware extends Middleware {
- /** @var ControllerMethodReflector */
- private $reflector;
- /** @var ISession */
- private $session;
- /** @var IUserSession */
- private $userSession;
- /** @var ITimeFactory */
- private $timeFactory;
- /** @var array */
- private $excludedUserBackEnds = ['user_saml' => true, 'user_globalsiteselector' => true];
+ private array $excludedUserBackEnds = ['user_saml' => true, 'user_globalsiteselector' => true];
- /**
- * PasswordConfirmationMiddleware constructor.
- *
- * @param ControllerMethodReflector $reflector
- * @param ISession $session
- * @param IUserSession $userSession
- * @param ITimeFactory $timeFactory
- */
- public function __construct(ControllerMethodReflector $reflector,
- ISession $session,
- IUserSession $userSession,
- ITimeFactory $timeFactory) {
- $this->reflector = $reflector;
- $this->session = $session;
- $this->userSession = $userSession;
- $this->timeFactory = $timeFactory;
+ public function __construct(
+ private ControllerMethodReflector $reflector,
+ private ISession $session,
+ private IUserSession $userSession,
+ private ITimeFactory $timeFactory,
+ private IProvider $tokenProvider,
+ private readonly LoggerInterface $logger,
+ private readonly IRequest $request,
+ private readonly Manager $userManager,
+ ) {
}
/**
- * @param Controller $controller
- * @param string $methodName
* @throws NotConfirmedException
*/
- public function beforeController($controller, $methodName) {
- if ($this->reflector->hasAnnotation('PasswordConfirmationRequired')) {
- $user = $this->userSession->getUser();
- $backendClassName = '';
- if ($user !== null) {
- $backend = $user->getBackend();
- if ($backend instanceof IPasswordConfirmationBackend) {
- if (!$backend->canConfirmPassword($user->getUID())) {
- return;
- }
+ public function beforeController(Controller $controller, string $methodName) {
+ $reflectionMethod = new ReflectionMethod($controller, $methodName);
+
+ if (!$this->needsPasswordConfirmation($reflectionMethod)) {
+ return;
+ }
+
+ $user = $this->userSession->getUser();
+ $backendClassName = '';
+ if ($user !== null) {
+ $backend = $user->getBackend();
+ if ($backend instanceof IPasswordConfirmationBackend) {
+ if (!$backend->canConfirmPassword($user->getUID())) {
+ return;
}
+ }
+
+ $backendClassName = $user->getBackendClassName();
+ }
- $backendClassName = $user->getBackendClassName();
+ try {
+ $sessionId = $this->session->getId();
+ $token = $this->tokenProvider->getToken($sessionId);
+ } catch (SessionNotAvailableException|InvalidTokenException|WipeTokenException|ExpiredTokenException) {
+ // States we do not deal with here.
+ return;
+ }
+
+ $scope = $token->getScopeAsArray();
+ if (isset($scope[IToken::SCOPE_SKIP_PASSWORD_VALIDATION]) && $scope[IToken::SCOPE_SKIP_PASSWORD_VALIDATION] === true) {
+ // Users logging in from SSO backends cannot confirm their password by design
+ return;
+ }
+
+ 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);
+ $loginName = $this->session->get('loginname');
+ $loginResult = $this->userManager->checkPassword($loginName, $password);
+ if ($loginResult === false) {
+ throw new NotConfirmedException();
}
- $lastConfirm = (int) $this->session->get('last-password-confirm');
- // we can't check the password against a SAML backend, so skip password confirmation in this case
+ $this->session->set('last-password-confirm', $this->timeFactory->getTime());
+ } else {
+ $lastConfirm = (int)$this->session->get('last-password-confirm');
+ // TODO: confirm excludedUserBackEnds can go away and remove it
if (!isset($this->excludedUserBackEnds[$backendClassName]) && $lastConfirm < ($this->timeFactory->getTime() - (30 * 60 + 15))) { // allow 15 seconds delay
throw new NotConfirmedException();
}
}
}
+
+ private function needsPasswordConfirmation(ReflectionMethod $reflectionMethod): bool {
+ $attributes = $reflectionMethod->getAttributes(PasswordConfirmationRequired::class);
+ if (!empty($attributes)) {
+ return true;
+ }
+
+ if ($this->reflector->hasAnnotation('PasswordConfirmationRequired')) {
+ $this->logger->debug($reflectionMethod->getDeclaringClass()->getName() . '::' . $reflectionMethod->getName() . ' uses the @' . 'PasswordConfirmationRequired' . ' annotation and should use the #[PasswordConfirmationRequired] attribute instead');
+ return true;
+ }
+
+ return false;
+ }
+
+ private function isPasswordConfirmationStrict(ReflectionMethod $reflectionMethod): bool {
+ $attributes = $reflectionMethod->getAttributes(PasswordConfirmationRequired::class);
+ return !empty($attributes) && ($attributes[0]->newInstance()->getStrict());
+ }
}
diff --git a/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php b/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php
index 5f683fa38ac..2d19be97993 100644
--- a/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php
@@ -1,37 +1,31 @@
<?php
+
+declare(strict_types=1);
+
/**
- * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
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;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Attribute\AnonRateLimit;
+use OCP\AppFramework\Http\Attribute\ARateLimit;
+use OCP\AppFramework\Http\Attribute\UserRateLimit;
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;
+use ReflectionMethod;
/**
* Class RateLimitingMiddleware is the middleware responsible for implementing the
@@ -42,7 +36,12 @@ use OCP\IUserSession;
* @UserRateThrottle(limit=5, period=100)
* @AnonRateThrottle(limit=1, period=100)
*
- * Those annotations above would mean that logged-in users can access the page 5
+ * Or attributes such as:
+ *
+ * #[UserRateLimit(limit: 5, period: 100)]
+ * #[AnonRateLimit(limit: 1, period: 100)]
+ *
+ * Both sets would mean that logged-in users can access the page 5
* times within 100 seconds, and anonymous users 1 time within 100 seconds. If
* only an AnonRateThrottle is specified that one will also be applied to logged-in
* users.
@@ -50,64 +49,98 @@ use OCP\IUserSession;
* @package OC\AppFramework\Middleware\Security
*/
class RateLimitingMiddleware extends Middleware {
- /** @var IRequest $request */
- private $request;
- /** @var IUserSession */
- private $userSession;
- /** @var ControllerMethodReflector */
- private $reflector;
- /** @var Limiter */
- private $limiter;
-
- /**
- * @param IRequest $request
- * @param IUserSession $userSession
- * @param ControllerMethodReflector $reflector
- * @param Limiter $limiter
- */
- public function __construct(IRequest $request,
- IUserSession $userSession,
- ControllerMethodReflector $reflector,
- Limiter $limiter) {
- $this->request = $request;
- $this->userSession = $userSession;
- $this->reflector = $reflector;
- $this->limiter = $limiter;
+ public function __construct(
+ protected IRequest $request,
+ protected IUserSession $userSession,
+ protected ControllerMethodReflector $reflector,
+ protected Limiter $limiter,
+ protected ISession $session,
+ protected IAppConfig $appConfig,
+ protected BruteforceAllowList $bruteForceAllowList,
+ ) {
}
/**
* {@inheritDoc}
* @throws RateLimitExceededException
*/
- public function beforeController($controller, $methodName) {
+ public function beforeController(Controller $controller, string $methodName): void {
parent::beforeController($controller, $methodName);
-
- $anonLimit = $this->reflector->getAnnotationParameter('AnonRateThrottle', 'limit');
- $anonPeriod = $this->reflector->getAnnotationParameter('AnonRateThrottle', 'period');
- $userLimit = $this->reflector->getAnnotationParameter('UserRateThrottle', 'limit');
- $userPeriod = $this->reflector->getAnnotationParameter('UserRateThrottle', 'period');
$rateLimitIdentifier = get_class($controller) . '::' . $methodName;
- if ($userLimit !== '' && $userPeriod !== '' && $this->userSession->isLoggedIn()) {
- $this->limiter->registerUserRequest(
- $rateLimitIdentifier,
- $userLimit,
- $userPeriod,
- $this->userSession->getUser()
- );
- } elseif ($anonLimit !== '' && $anonPeriod !== '') {
+
+ if ($this->userSession instanceof Session && $this->userSession->getSession()->get('app_api') === true && $this->userSession->getUser() === null) {
+ // if userId is not specified and the request is authenticated by AppAPI, we skip the rate limit
+ return;
+ }
+
+ if ($this->userSession->isLoggedIn()) {
+ $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(),
+ $rateLimit->getPeriod(),
+ $this->userSession->getUser()
+ );
+ return;
+ }
+
+ // If not user specific rate limit is found the Anon rate limit applies!
+ }
+
+ $rateLimit = $this->readLimitFromAnnotationOrAttribute($controller, $methodName, 'AnonRateThrottle', AnonRateLimit::class);
+
+ if ($rateLimit !== null) {
$this->limiter->registerAnonRequest(
$rateLimitIdentifier,
- $anonLimit,
- $anonPeriod,
+ $rateLimit->getLimit(),
+ $rateLimit->getPeriod(),
$this->request->getRemoteAddress()
);
}
}
/**
+ * @template T of ARateLimit
+ *
+ * @param Controller $controller
+ * @param string $methodName
+ * @param string $annotationName
+ * @param class-string<T> $attributeClass
+ * @return ?ARateLimit
+ */
+ protected function readLimitFromAnnotationOrAttribute(Controller $controller, string $methodName, string $annotationName, string $attributeClass): ?ARateLimit {
+ $annotationLimit = $this->reflector->getAnnotationParameter($annotationName, 'limit');
+ $annotationPeriod = $this->reflector->getAnnotationParameter($annotationName, 'period');
+
+ if ($annotationLimit !== '' && $annotationPeriod !== '') {
+ return new $attributeClass(
+ (int)$annotationLimit,
+ (int)$annotationPeriod,
+ );
+ }
+
+ $reflectionMethod = new ReflectionMethod($controller, $methodName);
+ $attributes = $reflectionMethod->getAttributes($attributeClass);
+ $attribute = current($attributes);
+
+ if ($attribute !== false) {
+ return $attribute->newInstance();
+ }
+
+ return null;
+ }
+
+ /**
* {@inheritDoc}
*/
- public function afterException($controller, $methodName, \Exception $exception) {
+ public function afterException(Controller $controller, string $methodName, \Exception $exception): Response {
if ($exception instanceof RateLimitExceededException) {
if (stripos($this->request->getHeader('Accept'), 'html') === false) {
$response = new DataResponse([], $exception->getCode());
diff --git a/lib/private/AppFramework/Middleware/Security/ReloadExecutionMiddleware.php b/lib/private/AppFramework/Middleware/Security/ReloadExecutionMiddleware.php
index 5a635e3f284..e770fa4cbff 100644
--- a/lib/private/AppFramework/Middleware/Security/ReloadExecutionMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/ReloadExecutionMiddleware.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Middleware\Security;
@@ -58,7 +41,7 @@ class ReloadExecutionMiddleware extends Middleware {
return new RedirectResponse($this->urlGenerator->linkToRouteAbsolute(
'core.login.showLoginForm',
- ['clear' => true] // this param the the code in login.js may be removed when the "Clear-Site-Data" is working in the browsers
+ ['clear' => true] // this param the code in login.js may be removed when the "Clear-Site-Data" is working in the browsers
));
}
diff --git a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php
index 0ab5d061c44..097ed1b2b8f 100644
--- a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php
@@ -1,25 +1,8 @@
<?php
+
/**
- * @copyright 2017, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Middleware\Security;
@@ -31,17 +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) {
@@ -65,19 +41,19 @@ class SameSiteCookieMiddleware extends Middleware {
public function afterException($controller, $methodName, \Exception $exception) {
if ($exception instanceof LaxSameSiteCookieFailedException) {
- $respone = new Response();
- $respone->setStatus(Http::STATUS_FOUND);
- $respone->addHeader('Location', $this->request->getRequestUri());
+ $response = new Response();
+ $response->setStatus(Http::STATUS_FOUND);
+ $response->addHeader('Location', $this->request->getRequestUri());
$this->setSameSiteCookie();
- return $respone;
+ return $response;
}
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 e0f36231b68..e3a293e0fd9 100644
--- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
@@ -1,66 +1,52 @@
<?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;
+use OCP\AppFramework\Http\Attribute\StrictCookiesRequired;
+use OCP\AppFramework\Http\Attribute\SubAdminRequired;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\RedirectResponse;
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;
/**
* Used to do all the authentication and checking stuff for a controller method
@@ -69,60 +55,41 @@ use Psr\Log\LoggerInterface;
* 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;
}
/**
@@ -137,7 +104,6 @@ class SecurityMiddleware extends Middleware {
* @suppress PhanUndeclaredClassConstant
*/
public function beforeController($controller, $methodName) {
-
// this will set the current navigation entry of the app, use this only
// for normal HTML requests and not for AJAX requests
$this->navigationManager->setActiveEntry($this->appName);
@@ -146,22 +112,37 @@ class SecurityMiddleware extends Middleware {
$this->navigationManager->setActiveEntry('spreed');
}
+ $reflectionMethod = new ReflectionMethod($controller, $methodName);
+
// security checks
- $isPublicPage = $this->reflector->hasAnnotation('PublicPage');
- if (!$isPublicPage) {
- if (!$this->isLoggedIn) {
- throw new NotLoggedInException();
+ $isPublicPage = $this->hasAnnotationOrAttribute($reflectionMethod, 'PublicPage', PublicPage::class);
+
+ 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->reflector->hasAnnotation('AuthorizedAdminSetting')) {
- $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->reflector->hasAnnotation('SubAdminRequired')) {
- $authorized = $this->isSubAdmin;
+ if (!$authorized && $this->hasAnnotationOrAttribute($reflectionMethod, 'AuthorizedAdminSetting', AuthorizedAdminSetting::class)) {
+ $authorized = $this->isAdminUser();
+
+ if (!$authorized && $this->hasAnnotationOrAttribute($reflectionMethod, 'SubAdminRequired', SubAdminRequired::class)) {
+ $authorized = $this->isSubAdmin();
}
if (!$authorized) {
- $settingClasses = explode(';', $this->reflector->getAnnotationParameter('AuthorizedAdminSetting', 'settings'));
+ $settingClasses = $this->getAuthorizedAdminSettingClasses($reflectionMethod);
$authorizedClasses = $this->groupAuthorizationMapper->findAllClassesForUser($this->userSession->getUser());
foreach ($settingClasses as $settingClass) {
$authorized = in_array($settingClass, $authorizedClasses, true);
@@ -172,32 +153,46 @@ 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->reflector->hasAnnotation('SubAdminRequired')
- && !$this->isSubAdmin
- && !$this->isAdminUser
+ if ($this->hasAnnotationOrAttribute($reflectionMethod, 'SubAdminRequired', SubAdminRequired::class)
+ && !$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->reflector->hasAnnotation('SubAdminRequired')
- && !$this->reflector->hasAnnotation('NoAdminRequired')
- && !$this->isAdminUser
+ if (!$this->hasAnnotationOrAttribute($reflectionMethod, 'SubAdminRequired', SubAdminRequired::class)
+ && !$this->hasAnnotationOrAttribute($reflectionMethod, 'NoAdminRequired', NoAdminRequired::class)
+ && !$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->reflector->hasAnnotation('StrictCookieRequired') || !$this->reflector->hasAnnotation('NoCSRFRequired')) {
+ if ($this->hasAnnotationOrAttribute($reflectionMethod, 'StrictCookieRequired', StrictCookiesRequired::class)
+ || !$this->hasAnnotationOrAttribute($reflectionMethod, 'NoCSRFRequired', NoCSRFRequired::class)) {
if (!$this->request->passesStrictCookieCheck()) {
throw new StrictCookieMissingException();
}
}
// CSRF check - also registers the CSRF token since the session may be closed later
Util::callRegister();
- if (!$this->reflector->hasAnnotation('NoCSRFRequired')) {
+ if ($this->isInvalidCSRFRequired($reflectionMethod)) {
/*
* Only allow the CSRF check to fail on OCS Requests. This kind of
* hacks around that we have no full token auth in place yet and we
@@ -206,12 +201,7 @@ class SecurityMiddleware extends Middleware {
* Additionally we allow Bearer authenticated requests to pass on OCS routes.
* This allows oauth apps (e.g. moodle) to use the OCS endpoints
*/
- if (!$this->request->passesCSRFCheck() && !(
- $controller instanceof OCSController && (
- $this->request->getHeader('OCS-APIREQUEST') === 'true' ||
- strpos($this->request->getHeader('Authorization'), 'Bearer ') === 0
- )
- )) {
+ if (!$controller instanceof OCSController || !$this->isValidOCSRequest()) {
throw new CrossSiteRequestForgeryException();
}
}
@@ -233,6 +223,62 @@ class SecurityMiddleware extends Middleware {
}
}
+ private function isInvalidCSRFRequired(ReflectionMethod $reflectionMethod): bool {
+ if ($this->hasAnnotationOrAttribute($reflectionMethod, 'NoCSRFRequired', NoCSRFRequired::class)) {
+ return false;
+ }
+
+ return !$this->request->passesCSRFCheck();
+ }
+
+ private function isValidOCSRequest(): bool {
+ return $this->request->getHeader('OCS-APIREQUEST') === 'true'
+ || str_starts_with($this->request->getHeader('Authorization'), 'Bearer ');
+ }
+
+ /**
+ * @template T
+ *
+ * @param ReflectionMethod $reflectionMethod
+ * @param ?string $annotationName
+ * @param class-string<T> $attributeClass
+ * @return boolean
+ */
+ protected function hasAnnotationOrAttribute(ReflectionMethod $reflectionMethod, ?string $annotationName, string $attributeClass): bool {
+ if (!empty($reflectionMethod->getAttributes($attributeClass))) {
+ return true;
+ }
+
+ 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;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param ReflectionMethod $reflectionMethod
+ * @return string[]
+ */
+ protected function getAuthorizedAdminSettingClasses(ReflectionMethod $reflectionMethod): array {
+ $classes = [];
+ if ($this->reflector->hasAnnotation('AuthorizedAdminSetting')) {
+ $classes = explode(';', $this->reflector->getAnnotationParameter('AuthorizedAdminSetting', 'settings'));
+ }
+
+ $attributes = $reflectionMethod->getAttributes(AuthorizedAdminSetting::class);
+ if (!empty($attributes)) {
+ foreach ($attributes as $attribute) {
+ /** @var AuthorizedAdminSetting $setting */
+ $setting = $attribute->newInstance();
+ $classes[] = $setting->getSettings();
+ }
+ }
+
+ return $classes;
+ }
+
/**
* If an SecurityException is being caught, ajax requests return a JSON error
* response and non ajax requests redirect to the index
diff --git a/lib/private/AppFramework/Middleware/SessionMiddleware.php b/lib/private/AppFramework/Middleware/SessionMiddleware.php
index f3fd2c99173..b7b0fb118c2 100644
--- a/lib/private/AppFramework/Middleware/SessionMiddleware.php
+++ b/lib/private/AppFramework/Middleware/SessionMiddleware.php
@@ -1,38 +1,22 @@
<?php
+
+declare(strict_types=1);
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @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;
use OC\AppFramework\Utility\ControllerMethodReflector;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Attribute\UseSession;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
use OCP\ISession;
+use ReflectionMethod;
class SessionMiddleware extends Middleware {
-
/** @var ControllerMethodReflector */
private $reflector;
@@ -40,7 +24,7 @@ class SessionMiddleware extends Middleware {
private $session;
public function __construct(ControllerMethodReflector $reflector,
- ISession $session) {
+ ISession $session) {
$this->reflector = $reflector;
$this->session = $session;
}
@@ -50,9 +34,19 @@ class SessionMiddleware extends Middleware {
* @param string $methodName
*/
public function beforeController($controller, $methodName) {
- $useSession = $this->reflector->hasAnnotation('UseSession');
- if (!$useSession) {
- $this->session->close();
+ /**
+ * Annotation deprecated with Nextcloud 26
+ */
+ $hasAnnotation = $this->reflector->hasAnnotation('UseSession');
+ if ($hasAnnotation) {
+ $this->session->reopen();
+ return;
+ }
+
+ $reflectionMethod = new ReflectionMethod($controller, $methodName);
+ $hasAttribute = !empty($reflectionMethod->getAttributes(UseSession::class));
+ if ($hasAttribute) {
+ $this->session->reopen();
}
}
@@ -63,10 +57,21 @@ class SessionMiddleware extends Middleware {
* @return Response
*/
public function afterController($controller, $methodName, Response $response) {
- $useSession = $this->reflector->hasAnnotation('UseSession');
- if ($useSession) {
+ /**
+ * Annotation deprecated with Nextcloud 26
+ */
+ $hasAnnotation = $this->reflector->hasAnnotation('UseSession');
+ if ($hasAnnotation) {
$this->session->close();
+ return $response;
}
+
+ $reflectionMethod = new ReflectionMethod($controller, $methodName);
+ $hasAttribute = !empty($reflectionMethod->getAttributes(UseSession::class));
+ if ($hasAttribute) {
+ $this->session->close();
+ }
+
return $response;
}
}
diff --git a/lib/private/AppFramework/OCS/BaseResponse.php b/lib/private/AppFramework/OCS/BaseResponse.php
index dbff1b846c1..05ce133db24 100644
--- a/lib/private/AppFramework/OCS/BaseResponse.php
+++ b/lib/private/AppFramework/OCS/BaseResponse.php
@@ -1,28 +1,8 @@
<?php
+
/**
- * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\OCS;
@@ -30,6 +10,13 @@ use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\Response;
+/**
+ * @psalm-import-type DataResponseType from DataResponse
+ * @template S of Http::STATUS_*
+ * @template-covariant T of DataResponseType
+ * @template H of array<string, mixed>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
+ */
abstract class BaseResponse extends Response {
/** @var array */
protected $data;
@@ -37,29 +24,29 @@ abstract class BaseResponse extends Response {
/** @var string */
protected $format;
- /** @var string */
+ /** @var ?string */
protected $statusMessage;
- /** @var int */
+ /** @var ?int */
protected $itemsCount;
- /** @var int */
+ /** @var ?int */
protected $itemsPerPage;
/**
* BaseResponse constructor.
*
- * @param DataResponse $dataResponse
+ * @param DataResponse<S, T, H> $dataResponse
* @param string $format
* @param string|null $statusMessage
* @param int|null $itemsCount
* @param int|null $itemsPerPage
*/
public function __construct(DataResponse $dataResponse,
- $format = 'xml',
- $statusMessage = null,
- $itemsCount = null,
- $itemsPerPage = null) {
+ $format = 'xml',
+ $statusMessage = null,
+ $itemsCount = null,
+ $itemsPerPage = null) {
parent::__construct();
$this->format = $format;
@@ -92,14 +79,14 @@ abstract class BaseResponse extends Response {
}
/**
- * @param string[] $meta
+ * @param array<string,string|int> $meta
* @return string
*/
protected function renderResult(array $meta): string {
$status = $this->getStatus();
- if ($status === Http::STATUS_NO_CONTENT ||
- $status === Http::STATUS_NOT_MODIFIED ||
- ($status >= 100 && $status <= 199)) {
+ if ($status === Http::STATUS_NO_CONTENT
+ || $status === Http::STATUS_NOT_MODIFIED
+ || ($status >= 100 && $status <= 199)) {
// Those status codes are not supposed to have a body:
// https://stackoverflow.com/q/8628725
return '';
@@ -113,7 +100,7 @@ abstract class BaseResponse extends Response {
];
if ($this->format === 'json') {
- return json_encode($response, JSON_HEX_TAG);
+ return $this->toJson($response);
}
$writer = new \XMLWriter();
@@ -126,12 +113,23 @@ abstract class BaseResponse extends Response {
}
/**
- * @param array $array
- * @param \XMLWriter $writer
+ * @psalm-taint-escape has_quotes
+ * @psalm-taint-escape html
*/
- protected function toXML(array $array, \XMLWriter $writer) {
+ protected function toJson(array $array): string {
+ return \json_encode($array, \JSON_HEX_TAG);
+ }
+
+ protected function toXML(array $array, \XMLWriter $writer): void {
foreach ($array as $k => $v) {
- if (\is_string($k) && strpos($k, '@') === 0) {
+ if ($k === '@attributes' && is_array($v)) {
+ foreach ($v as $k2 => $v2) {
+ $writer->writeAttribute($k2, $v2);
+ }
+ continue;
+ }
+
+ if (\is_string($k) && str_starts_with($k, '@')) {
$writer->writeAttribute(substr($k, 1), $v);
continue;
}
@@ -140,12 +138,24 @@ abstract class BaseResponse extends Response {
$k = 'element';
}
- if (\is_array($v)) {
+ if ($v instanceof \stdClass) {
+ $v = [];
+ }
+
+ if ($k === '$comment') {
+ $writer->writeComment($v);
+ } elseif (\is_array($v)) {
$writer->startElement($k);
$this->toXML($v, $writer);
$writer->endElement();
+ } elseif ($v instanceof \JsonSerializable) {
+ $writer->startElement($k);
+ $this->toXML($v->jsonSerialize(), $writer);
+ $writer->endElement();
+ } elseif ($v === null) {
+ $writer->writeElement($k);
} else {
- $writer->writeElement($k, $v);
+ $writer->writeElement($k, (string)$v);
}
}
}
diff --git a/lib/private/AppFramework/OCS/V1Response.php b/lib/private/AppFramework/OCS/V1Response.php
index f4f19832fa8..1c2c25f5cb0 100644
--- a/lib/private/AppFramework/OCS/V1Response.php
+++ b/lib/private/AppFramework/OCS/V1Response.php
@@ -1,39 +1,28 @@
<?php
+
/**
- * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\OCS;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
+/**
+ * @psalm-import-type DataResponseType from DataResponse
+ * @template S of Http::STATUS_*
+ * @template-covariant T of DataResponseType
+ * @template H of array<string, mixed>
+ * @template-extends BaseResponse<Http::STATUS_*, DataResponseType, array<string, mixed>>
+ */
class V1Response extends BaseResponse {
-
/**
* The V1 endpoint has very limited http status codes basically everything
* is status 200 except 401
*
- * @return int
+ * @return Http::STATUS_*
*/
public function getStatus() {
$status = parent::getStatus();
@@ -69,12 +58,11 @@ class V1Response extends BaseResponse {
$meta = [
'status' => $this->getOCSStatus() === 100 ? 'ok' : 'failure',
'statuscode' => $this->getOCSStatus(),
- 'message' => $this->getOCSStatus() === 100 ? 'OK' : $this->statusMessage,
+ 'message' => $this->getOCSStatus() === 100 ? 'OK' : $this->statusMessage ?? '',
+ 'totalitems' => (string)($this->itemsCount ?? ''),
+ 'itemsperpage' => (string)($this->itemsPerPage ?? ''),
];
- $meta['totalitems'] = $this->itemsCount !== null ? (string)$this->itemsCount : '';
- $meta['itemsperpage'] = $this->itemsPerPage !== null ? (string)$this->itemsPerPage: '';
-
return $this->renderResult($meta);
}
}
diff --git a/lib/private/AppFramework/OCS/V2Response.php b/lib/private/AppFramework/OCS/V2Response.php
index 6c302698bc9..efc9348eb37 100644
--- a/lib/private/AppFramework/OCS/V2Response.php
+++ b/lib/private/AppFramework/OCS/V2Response.php
@@ -1,38 +1,28 @@
<?php
+
/**
- * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\OCS;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
+/**
+ * @psalm-import-type DataResponseType from DataResponse
+ * @template S of Http::STATUS_*
+ * @template-covariant T of DataResponseType
+ * @template H of array<string, mixed>
+ * @template-extends BaseResponse<Http::STATUS_*, DataResponseType, array<string, mixed>>
+ */
class V2Response extends BaseResponse {
-
/**
* The V2 endpoint just passes on status codes.
* Of course we have to map the OCS specific codes to proper HTTP status codes
*
- * @return int
+ * @return Http::STATUS_*
*/
public function getStatus() {
$status = parent::getStatus();
@@ -61,7 +51,7 @@ class V2Response extends BaseResponse {
$meta = [
'status' => $status >= 200 && $status < 300 ? 'ok' : 'failure',
'statuscode' => $this->getOCSStatus(),
- 'message' => $status >= 200 && $status < 300 ? 'OK' : $this->statusMessage,
+ 'message' => $status >= 200 && $status < 300 ? 'OK' : $this->statusMessage ?? '',
];
if ($this->itemsCount !== null) {
diff --git a/lib/private/AppFramework/Routing/RouteActionHandler.php b/lib/private/AppFramework/Routing/RouteActionHandler.php
index 4682d2e9e7e..ec38105a5a0 100644
--- a/lib/private/AppFramework/Routing/RouteActionHandler.php
+++ b/lib/private/AppFramework/Routing/RouteActionHandler.php
@@ -1,26 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @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\Routing;
diff --git a/lib/private/AppFramework/Routing/RouteConfig.php b/lib/private/AppFramework/Routing/RouteConfig.php
deleted file mode 100644
index 3986abd6d6e..00000000000
--- a/lib/private/AppFramework/Routing/RouteConfig.php
+++ /dev/null
@@ -1,295 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Robin McCorkell <robin@mccorkell.me.uk>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @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/>
- *
- */
-namespace OC\AppFramework\Routing;
-
-use OC\AppFramework\DependencyInjection\DIContainer;
-use OC\Route\Router;
-
-/**
- * Class RouteConfig
- * @package OC\AppFramework\routing
- */
-class RouteConfig {
- /** @var DIContainer */
- private $container;
-
- /** @var Router */
- private $router;
-
- /** @var array */
- private $routes;
-
- /** @var string */
- private $appName;
-
- /** @var string[] */
- private $controllerNameCache = [];
-
- protected $rootUrlApps = [
- 'cloud_federation_api',
- 'core',
- 'files_sharing',
- 'files',
- 'settings',
- 'spreed',
- ];
-
- /**
- * @param \OC\AppFramework\DependencyInjection\DIContainer $container
- * @param \OC\Route\Router $router
- * @param array $routes
- * @internal param $appName
- */
- public function __construct(DIContainer $container, Router $router, $routes) {
- $this->routes = $routes;
- $this->container = $container;
- $this->router = $router;
- $this->appName = $container['AppName'];
- }
-
- /**
- * The routes and resource will be registered to the \OCP\Route\IRouter
- */
- public function register() {
-
- // parse simple
- $this->processIndexRoutes($this->routes);
-
- // parse resources
- $this->processIndexResources($this->routes);
-
- /*
- * OCS routes go into a different collection
- */
- $oldCollection = $this->router->getCurrentCollection();
- $this->router->useCollection($oldCollection . '.ocs');
-
- // parse ocs simple routes
- $this->processOCS($this->routes);
-
- // parse ocs simple routes
- $this->processOCSResources($this->routes);
-
- $this->router->useCollection($oldCollection);
- }
-
- private function processOCS(array $routes): void {
- $ocsRoutes = $routes['ocs'] ?? [];
- foreach ($ocsRoutes as $ocsRoute) {
- $this->processRoute($ocsRoute, 'ocs.');
- }
- }
-
- /**
- * Creates one route base on the give configuration
- * @param array $routes
- * @throws \UnexpectedValueException
- */
- private function processIndexRoutes(array $routes): void {
- $simpleRoutes = $routes['routes'] ?? [];
- foreach ($simpleRoutes as $simpleRoute) {
- $this->processRoute($simpleRoute);
- }
- }
-
- protected function processRoute(array $route, string $routeNamePrefix = ''): void {
- $name = $route['name'];
- $postfix = $route['postfix'] ?? '';
- $root = $this->buildRootPrefix($route, $routeNamePrefix);
-
- $url = $root . '/' . ltrim($route['url'], '/');
- $verb = strtoupper($route['verb'] ?? 'GET');
-
- $split = explode('#', $name, 2);
- if (count($split) !== 2) {
- throw new \UnexpectedValueException('Invalid route name: use the format foo#bar to reference FooController::bar');
- }
- [$controller, $action] = $split;
-
- $controllerName = $this->buildControllerName($controller);
- $actionName = $this->buildActionName($action);
-
- $routeName = $routeNamePrefix . $this->appName . '.' . $controller . '.' . $action . $postfix;
-
- $router = $this->router->create($routeName, $url)
- ->method($verb);
-
- // optionally register requirements for route. This is used to
- // tell the route parser how url parameters should be matched
- if (array_key_exists('requirements', $route)) {
- $router->requirements($route['requirements']);
- }
-
- // optionally register defaults for route. This is used to
- // tell the route parser how url parameters should be default valued
- $defaults = [];
- if (array_key_exists('defaults', $route)) {
- $defaults = $route['defaults'];
- }
-
- $defaults['caller'] = [$this->appName, $controllerName, $actionName];
- $router->defaults($defaults);
- }
-
- /**
- * For a given name and url restful OCS routes are created:
- * - index
- * - show
- * - create
- * - update
- * - destroy
- *
- * @param array $routes
- */
- private function processOCSResources(array $routes): void {
- $this->processResources($routes['ocs-resources'] ?? [], 'ocs.');
- }
-
- /**
- * For a given name and url restful routes are created:
- * - index
- * - show
- * - create
- * - update
- * - destroy
- *
- * @param array $routes
- */
- private function processIndexResources(array $routes): void {
- $this->processResources($routes['resources'] ?? []);
- }
-
- /**
- * For a given name and url restful routes are created:
- * - index
- * - show
- * - create
- * - update
- * - destroy
- *
- * @param array $resources
- * @param string $routeNamePrefix
- */
- protected function processResources(array $resources, string $routeNamePrefix = ''): void {
- // declaration of all restful actions
- $actions = [
- ['name' => 'index', 'verb' => 'GET', 'on-collection' => true],
- ['name' => 'show', 'verb' => 'GET'],
- ['name' => 'create', 'verb' => 'POST', 'on-collection' => true],
- ['name' => 'update', 'verb' => 'PUT'],
- ['name' => 'destroy', 'verb' => 'DELETE'],
- ];
-
- foreach ($resources as $resource => $config) {
- $root = $this->buildRootPrefix($config, $routeNamePrefix);
-
- // the url parameter used as id to the resource
- foreach ($actions as $action) {
- $url = $root . '/' . ltrim($config['url'], '/');
- $method = $action['name'];
-
- $verb = strtoupper($action['verb'] ?? 'GET');
- $collectionAction = $action['on-collection'] ?? false;
- if (!$collectionAction) {
- $url .= '/{id}';
- }
- if (isset($action['url-postfix'])) {
- $url .= '/' . $action['url-postfix'];
- }
-
- $controller = $resource;
-
- $controllerName = $this->buildControllerName($controller);
- $actionName = $this->buildActionName($method);
-
- $routeName = $routeNamePrefix . $this->appName . '.' . strtolower($resource) . '.' . $method;
-
- $route = $this->router->create($routeName, $url)
- ->method($verb);
-
- $route->defaults(['caller' => [$this->appName, $controllerName, $actionName]]);
- }
- }
- }
-
- private function buildRootPrefix(array $route, string $routeNamePrefix): string {
- $defaultRoot = $this->appName === 'core' ? '' : '/apps/' . $this->appName;
- $root = $route['root'] ?? $defaultRoot;
-
- if ($routeNamePrefix !== '') {
- // In OCS all apps are whitelisted
- return $root;
- }
-
- if (!\in_array($this->appName, $this->rootUrlApps, true)) {
- // Only allow root URLS for some apps
- return $defaultRoot;
- }
-
- return $root;
- }
-
- /**
- * Based on a given route name the controller name is generated
- * @param string $controller
- * @return string
- */
- private function buildControllerName(string $controller): string {
- if (!isset($this->controllerNameCache[$controller])) {
- $this->controllerNameCache[$controller] = $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller';
- }
- return $this->controllerNameCache[$controller];
- }
-
- /**
- * Based on the action part of the route name the controller method name is generated
- * @param string $action
- * @return string
- */
- private function buildActionName(string $action): string {
- return $this->underScoreToCamelCase($action);
- }
-
- /**
- * Underscored strings are converted to camel case strings
- * @param string $str
- * @return string
- */
- private function underScoreToCamelCase(string $str): string {
- $pattern = '/_[a-z]?/';
- return preg_replace_callback(
- $pattern,
- function ($matches) {
- return strtoupper(ltrim($matches[0], '_'));
- },
- $str);
- }
-}
diff --git a/lib/private/AppFramework/Routing/RouteParser.php b/lib/private/AppFramework/Routing/RouteParser.php
index 1b3a6c1255a..55e58234673 100644
--- a/lib/private/AppFramework/Routing/RouteParser.php
+++ b/lib/private/AppFramework/Routing/RouteParser.php
@@ -1,26 +1,10 @@
<?php
declare(strict_types=1);
-
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @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\Routing;
@@ -36,6 +20,7 @@ class RouteParser {
'core',
'files_sharing',
'files',
+ 'profile',
'settings',
'spreed',
];
@@ -91,7 +76,7 @@ class RouteParser {
$url = $root . '/' . ltrim($route['url'], '/');
$verb = strtoupper($route['verb'] ?? 'GET');
- $split = explode('#', $name, 2);
+ $split = explode('#', $name, 3);
if (count($split) !== 2) {
throw new \UnexpectedValueException('Invalid route name: use the format foo#bar to reference FooController::bar');
}
@@ -100,7 +85,13 @@ class RouteParser {
$controllerName = $this->buildControllerName($controller);
$actionName = $this->buildActionName($action);
- $routeName = $routeNamePrefix . $appName . '.' . $controller . '.' . $action . $postfix;
+ /*
+ * The route name has to be lowercase, for symfony to match it correctly.
+ * This is required because symfony allows mixed casing for controller names in the routes.
+ * To avoid breaking all the existing route names, registering and matching will only use the lowercase names.
+ * This is also safe on the PHP side because class and method names collide regardless of the casing.
+ */
+ $routeName = strtolower($routeNamePrefix . $appName . '.' . $controller . '.' . $action . $postfix);
$routeObject = new Route($url);
$routeObject->method($verb);
diff --git a/lib/private/AppFramework/ScopedPsrLogger.php b/lib/private/AppFramework/ScopedPsrLogger.php
index 46832c6d38d..0a8e2b0d303 100644
--- a/lib/private/AppFramework/ScopedPsrLogger.php
+++ b/lib/private/AppFramework/ScopedPsrLogger.php
@@ -3,26 +3,8 @@
declare(strict_types=1);
/**
- * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Maxence Lange <maxence@artificial-owl.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework;
@@ -30,7 +12,6 @@ use Psr\Log\LoggerInterface;
use function array_merge;
class ScopedPsrLogger implements LoggerInterface {
-
/** @var LoggerInterface */
private $inner;
@@ -38,12 +19,12 @@ class ScopedPsrLogger implements LoggerInterface {
private $appId;
public function __construct(LoggerInterface $inner,
- string $appId) {
+ string $appId) {
$this->inner = $inner;
$this->appId = $appId;
}
- public function emergency($message, array $context = []) {
+ public function emergency($message, array $context = []): void {
$this->inner->emergency(
$message,
array_merge(
@@ -55,7 +36,7 @@ class ScopedPsrLogger implements LoggerInterface {
);
}
- public function alert($message, array $context = []) {
+ public function alert($message, array $context = []): void {
$this->inner->alert(
$message,
array_merge(
@@ -67,7 +48,7 @@ class ScopedPsrLogger implements LoggerInterface {
);
}
- public function critical($message, array $context = []) {
+ public function critical($message, array $context = []): void {
$this->inner->critical(
$message,
array_merge(
@@ -79,7 +60,7 @@ class ScopedPsrLogger implements LoggerInterface {
);
}
- public function error($message, array $context = []) {
+ public function error($message, array $context = []): void {
$this->inner->error(
$message,
array_merge(
@@ -91,7 +72,7 @@ class ScopedPsrLogger implements LoggerInterface {
);
}
- public function warning($message, array $context = []) {
+ public function warning($message, array $context = []): void {
$this->inner->warning(
$message,
array_merge(
@@ -103,7 +84,7 @@ class ScopedPsrLogger implements LoggerInterface {
);
}
- public function notice($message, array $context = []) {
+ public function notice($message, array $context = []): void {
$this->inner->notice(
$message,
array_merge(
@@ -115,7 +96,7 @@ class ScopedPsrLogger implements LoggerInterface {
);
}
- public function info($message, array $context = []) {
+ public function info($message, array $context = []): void {
$this->inner->info(
$message,
array_merge(
@@ -127,7 +108,7 @@ class ScopedPsrLogger implements LoggerInterface {
);
}
- public function debug($message, array $context = []) {
+ public function debug($message, array $context = []): void {
$this->inner->debug(
$message,
array_merge(
@@ -139,7 +120,7 @@ class ScopedPsrLogger implements LoggerInterface {
);
}
- public function log($level, $message, array $context = []) {
+ public function log($level, $message, array $context = []): void {
$this->inner->log(
$level,
$message,
diff --git a/lib/private/AppFramework/Services/AppConfig.php b/lib/private/AppFramework/Services/AppConfig.php
index 355a4123987..04d97738483 100644
--- a/lib/private/AppFramework/Services/AppConfig.php
+++ b/lib/private/AppFramework/Services/AppConfig.php
@@ -3,63 +3,327 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Services;
+use InvalidArgumentException;
+use JsonException;
use OCP\AppFramework\Services\IAppConfig;
+use OCP\Exceptions\AppConfigTypeConflictException;
+use OCP\Exceptions\AppConfigUnknownKeyException;
use OCP\IConfig;
class AppConfig implements IAppConfig {
+ public function __construct(
+ private IConfig $config,
+ /** @var \OC\AppConfig */
+ private \OCP\IAppConfig $appConfig,
+ private string $appName,
+ ) {
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @return string[] list of stored config keys
+ * @since 20.0.0
+ */
+ public function getAppKeys(): array {
+ return $this->appConfig->getKeys($this->appName);
+ }
- /** @var IConfig */
- private $config;
+ /**
+ * @inheritDoc
+ *
+ * @param string $key config key
+ * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
+ *
+ * @return bool TRUE if key exists
+ * @since 29.0.0
+ */
+ public function hasAppKey(string $key, ?bool $lazy = false): bool {
+ return $this->appConfig->hasKey($this->appName, $key, $lazy);
+ }
- /** @var string */
- private $appName;
+ /**
+ * @param string $key config key
+ * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
+ *
+ * @return bool
+ * @throws AppConfigUnknownKeyException if config key is not known
+ * @since 29.0.0
+ */
+ public function isSensitive(string $key, ?bool $lazy = false): bool {
+ return $this->appConfig->isSensitive($this->appName, $key, $lazy);
+ }
- public function __construct(IConfig $config, string $appName) {
- $this->config = $config;
- $this->appName = $appName;
+ /**
+ * @inheritDoc
+ *
+ * @param string $key config key
+ *
+ * @return bool TRUE if config is lazy loaded
+ * @throws AppConfigUnknownKeyException if config key is not known
+ * @see \OCP\IAppConfig for details about lazy loading
+ * @since 29.0.0
+ */
+ public function isLazy(string $key): bool {
+ return $this->appConfig->isLazy($this->appName, $key);
}
- public function getAppKeys(): array {
- return $this->config->getAppKeys($this->appName);
+ /**
+ * @inheritDoc
+ *
+ * @param string $key config keys prefix to search
+ * @param bool $filtered TRUE to hide sensitive config values. Value are replaced by {@see IConfig::SENSITIVE_VALUE}
+ *
+ * @return array<string, string|int|float|bool|array> [configKey => configValue]
+ * @since 29.0.0
+ */
+ public function getAllAppValues(string $key = '', bool $filtered = false): array {
+ return $this->appConfig->getAllValues($this->appName, $key, $filtered);
}
+ /**
+ * @inheritDoc
+ *
+ * @param string $key the key of the value, under which will be saved
+ * @param string $value the value that should be stored
+ * @since 20.0.0
+ * @deprecated 29.0.0 use {@see setAppValueString()}
+ */
public function setAppValue(string $key, string $value): void {
- $this->config->setAppValue($this->appName, $key, $value);
+ /** @psalm-suppress InternalMethod */
+ $this->appConfig->setValueMixed($this->appName, $key, $value);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $key config key
+ * @param string $value config value
+ * @param bool $lazy set config as lazy loaded
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function setAppValueString(
+ string $key,
+ string $value,
+ bool $lazy = false,
+ bool $sensitive = false,
+ ): bool {
+ return $this->appConfig->setValueString($this->appName, $key, $value, $lazy, $sensitive);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $key config key
+ * @param int $value config value
+ * @param bool $lazy set config as lazy loaded
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function setAppValueInt(
+ string $key,
+ int $value,
+ bool $lazy = false,
+ bool $sensitive = false,
+ ): bool {
+ return $this->appConfig->setValueInt($this->appName, $key, $value, $lazy, $sensitive);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $key config key
+ * @param float $value config value
+ * @param bool $lazy set config as lazy loaded
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function setAppValueFloat(
+ string $key,
+ float $value,
+ bool $lazy = false,
+ bool $sensitive = false,
+ ): bool {
+ return $this->appConfig->setValueFloat($this->appName, $key, $value, $lazy, $sensitive);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $key config key
+ * @param bool $value config value
+ * @param bool $lazy set config as lazy loaded
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function setAppValueBool(
+ string $key,
+ bool $value,
+ bool $lazy = false,
+ ): bool {
+ return $this->appConfig->setValueBool($this->appName, $key, $value, $lazy);
}
+ /**
+ * @inheritDoc
+ *
+ * @param string $key config key
+ * @param array $value config value
+ * @param bool $lazy set config as lazy loaded
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
+ * @throws JsonException
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function setAppValueArray(
+ string $key,
+ array $value,
+ bool $lazy = false,
+ bool $sensitive = false,
+ ): bool {
+ return $this->appConfig->setValueArray($this->appName, $key, $value, $lazy, $sensitive);
+ }
+
+ /**
+ * @param string $key
+ * @param string $default
+ *
+ * @since 20.0.0
+ * @deprecated 29.0.0 use {@see getAppValueString()}
+ * @return string
+ */
public function getAppValue(string $key, string $default = ''): string {
- return $this->config->getAppValue($this->appName, $key, $default);
+ /** @psalm-suppress InternalMethod */
+ /** @psalm-suppress UndefinedInterfaceMethod */
+ return $this->appConfig->getValueMixed($this->appName, $key, $default);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $key config key
+ * @param string $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return string stored config value or $default if not set in database
+ * @throws InvalidArgumentException if one of the argument format is invalid
+ * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function getAppValueString(string $key, string $default = '', bool $lazy = false): string {
+ return $this->appConfig->getValueString($this->appName, $key, $default, $lazy);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $key config key
+ * @param int $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return int stored config value or $default if not set in database
+ * @throws InvalidArgumentException if one of the argument format is invalid
+ * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function getAppValueInt(string $key, int $default = 0, bool $lazy = false): int {
+ return $this->appConfig->getValueInt($this->appName, $key, $default, $lazy);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $key config key
+ * @param float $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return float stored config value or $default if not set in database
+ * @throws InvalidArgumentException if one of the argument format is invalid
+ * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function getAppValueFloat(string $key, float $default = 0, bool $lazy = false): float {
+ return $this->appConfig->getValueFloat($this->appName, $key, $default, $lazy);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $key config key
+ * @param bool $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return bool stored config value or $default if not set in database
+ * @throws InvalidArgumentException if one of the argument format is invalid
+ * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function getAppValueBool(string $key, bool $default = false, bool $lazy = false): bool {
+ return $this->appConfig->getValueBool($this->appName, $key, $default, $lazy);
}
+ /**
+ * @inheritDoc
+ *
+ * @param string $key config key
+ * @param array $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return array stored config value or $default if not set in database
+ * @throws InvalidArgumentException if one of the argument format is invalid
+ * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function getAppValueArray(string $key, array $default = [], bool $lazy = false): array {
+ return $this->appConfig->getValueArray($this->appName, $key, $default, $lazy);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $key the key of the value, under which it was saved
+ * @since 20.0.0
+ */
public function deleteAppValue(string $key): void {
- $this->config->deleteAppValue($this->appName, $key);
+ $this->appConfig->deleteKey($this->appName, $key);
}
+ /**
+ * @inheritDoc
+ *
+ * @since 20.0.0
+ */
public function deleteAppValues(): void {
- $this->config->deleteAppValues($this->appName);
+ $this->appConfig->deleteApp($this->appName);
}
public function setUserValue(string $userId, string $key, string $value, ?string $preCondition = null): void {
@@ -73,4 +337,13 @@ class AppConfig implements IAppConfig {
public function deleteUserValue(string $userId, string $key): void {
$this->config->deleteUserValue($userId, $this->appName, $key);
}
+
+ /**
+ * Returns the installed versions of all apps
+ *
+ * @return array<string, string>
+ */
+ public function getAppInstalledVersions(bool $onlyEnabled = false): array {
+ return $this->appConfig->getAppInstalledVersions($onlyEnabled);
+ }
}
diff --git a/lib/private/AppFramework/Services/InitialState.php b/lib/private/AppFramework/Services/InitialState.php
index 9a44db606ea..da225b612cf 100644
--- a/lib/private/AppFramework/Services/InitialState.php
+++ b/lib/private/AppFramework/Services/InitialState.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\AppFramework\Services;
diff --git a/lib/private/AppFramework/Utility/ControllerMethodReflector.php b/lib/private/AppFramework/Utility/ControllerMethodReflector.php
index b76b3c33c42..679e1788004 100644
--- a/lib/private/AppFramework/Utility/ControllerMethodReflector.php
+++ b/lib/private/AppFramework/Utility/ControllerMethodReflector.php
@@ -1,35 +1,10 @@
<?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 Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Olivier Paroz <github@oparoz.com>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @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\Utility;
@@ -42,6 +17,7 @@ class ControllerMethodReflector implements IControllerMethodReflector {
public $annotations = [];
private $types = [];
private $parameters = [];
+ private array $ranges = [];
/**
* @param object $object an object or classname
@@ -54,26 +30,38 @@ class ControllerMethodReflector implements IControllerMethodReflector {
if ($docs !== false) {
// extract everything prefixed by @ and first letter uppercase
preg_match_all('/^\h+\*\h+@(?P<annotation>[A-Z]\w+)((?P<parameter>.*))?$/m', $docs, $matches);
- foreach ($matches['annotation'] as $key => $annontation) {
- $annontation = strtolower($annontation);
+ foreach ($matches['annotation'] as $key => $annotation) {
+ $annotation = strtolower($annotation);
$annotationValue = $matches['parameter'][$key];
- if (isset($annotationValue[0]) && $annotationValue[0] === '(' && $annotationValue[\strlen($annotationValue) - 1] === ')') {
+ if (str_starts_with($annotationValue, '(') && str_ends_with($annotationValue, ')')) {
$cutString = substr($annotationValue, 1, -1);
$cutString = str_replace(' ', '', $cutString);
- $splittedArray = explode(',', $cutString);
- foreach ($splittedArray as $annotationValues) {
+ $splitArray = explode(',', $cutString);
+ foreach ($splitArray as $annotationValues) {
[$key, $value] = explode('=', $annotationValues);
- $this->annotations[$annontation][$key] = $value;
+ $this->annotations[$annotation][$key] = $value;
}
continue;
}
- $this->annotations[$annontation] = [$annotationValue];
+ $this->annotations[$annotation] = [$annotationValue];
}
// extract type parameter information
preg_match_all('/@param\h+(?P<type>\w+)\h+\$(?P<var>\w+)/', $docs, $matches);
$this->types = array_combine($matches['var'], $matches['type']);
+ preg_match_all('/@psalm-param\h+(\?)?(?P<type>\w+)<(?P<rangeMin>(-?\d+|min)),\h*(?P<rangeMax>(-?\d+|max))>(\|null)?\h+\$(?P<var>\w+)/', $docs, $matches);
+ foreach ($matches['var'] as $index => $varName) {
+ if ($matches['type'][$index] !== 'int') {
+ // only int ranges are possible at the moment
+ // @see https://psalm.dev/docs/annotating_code/type_syntax/scalar_types
+ continue;
+ }
+ $this->ranges[$varName] = [
+ 'min' => $matches['rangeMin'][$index] === 'min' ? PHP_INT_MIN : (int)$matches['rangeMin'][$index],
+ 'max' => $matches['rangeMax'][$index] === 'max' ? PHP_INT_MAX : (int)$matches['rangeMax'][$index],
+ ];
+ }
}
foreach ($reflection->getParameters() as $param) {
@@ -94,9 +82,9 @@ class ControllerMethodReflector implements IControllerMethodReflector {
/**
* Inspects the PHPDoc parameters for types
* @param string $parameter the parameter whose type comments should be
- * parsed
+ * parsed
* @return string|null type in the type parameters (@param int $something)
- * would return int or null if not existing
+ * would return int or null if not existing
*/
public function getType(string $parameter) {
if (array_key_exists($parameter, $this->types)) {
@@ -106,6 +94,14 @@ class ControllerMethodReflector implements IControllerMethodReflector {
return null;
}
+ public function getRange(string $parameter): ?array {
+ if (array_key_exists($parameter, $this->ranges)) {
+ return $this->ranges[$parameter];
+ }
+
+ return null;
+ }
+
/**
* @return array the arguments of the method with key => default value
*/
diff --git a/lib/private/AppFramework/Utility/QueryNotFoundException.php b/lib/private/AppFramework/Utility/QueryNotFoundException.php
new file mode 100644
index 00000000000..4d2df14ebc8
--- /dev/null
+++ b/lib/private/AppFramework/Utility/QueryNotFoundException.php
@@ -0,0 +1,24 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\AppFramework\Utility;
+
+use OCP\AppFramework\QueryException;
+use Psr\Container\NotFoundExceptionInterface;
+
+/**
+ * Private implementation of the `Psr\Container\NotFoundExceptionInterface`
+ *
+ * QueryNotFoundException is a simple wrapper over the `QueryException`
+ * to fulfill the PSR Container interface.
+ *
+ * You should not catch this class directly but the `NotFoundExceptionInterface`.
+ */
+class QueryNotFoundException extends QueryException implements NotFoundExceptionInterface {
+}
diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php
index 429382aa223..0db3bfc1c77 100644
--- a/lib/private/AppFramework/Utility/SimpleContainer.php
+++ b/lib/private/AppFramework/Utility/SimpleContainer.php
@@ -1,31 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Bernhard Posselt <dev@bernhard-posselt.com>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Robin McCorkell <robin@mccorkell.me.uk>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @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\Utility;
@@ -34,26 +12,36 @@ use Closure;
use OCP\AppFramework\QueryException;
use OCP\IContainer;
use Pimple\Container;
+use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
+use Psr\Log\LoggerInterface;
use ReflectionClass;
use ReflectionException;
-use ReflectionParameter;
use ReflectionNamedType;
+use ReflectionParameter;
use function class_exists;
/**
* SimpleContainer is a simple implementation of a container on basis of Pimple
*/
class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
+ public static bool $useLazyObjects = false;
- /** @var Container */
- private $container;
+ private Container $container;
public function __construct() {
$this->container = new Container();
}
- public function get(string $id) {
+ /**
+ * @template T
+ * @param class-string<T>|string $id
+ * @return T|mixed
+ * @psalm-template S as class-string<T>|string
+ * @psalm-param S $id
+ * @psalm-return (S is class-string<T> ? T : mixed)
+ */
+ public function get(string $id): mixed {
return $this->query($id);
}
@@ -64,16 +52,29 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
/**
* @param ReflectionClass $class the class to instantiate
- * @return \stdClass the created class
+ * @return object the created class
* @suppress PhanUndeclaredClassInstanceof
*/
- private function buildClass(ReflectionClass $class) {
+ private function buildClass(ReflectionClass $class): object {
$constructor = $class->getConstructor();
if ($constructor === null) {
+ /* No constructor, return a instance directly */
return $class->newInstance();
}
+ if (PHP_VERSION_ID >= 80400 && self::$useLazyObjects) {
+ /* For PHP>=8.4, use a lazy ghost to delay constructor and dependency resolving */
+ /** @psalm-suppress UndefinedMethod */
+ return $class->newLazyGhost(function (object $object) use ($constructor): void {
+ /** @psalm-suppress DirectConstructorCall For lazy ghosts we have to call the constructor directly */
+ $object->__construct(...$this->buildClassConstructorParameters($constructor));
+ });
+ } else {
+ return $class->newInstanceArgs($this->buildClassConstructorParameters($constructor));
+ }
+ }
- return $class->newInstanceArgs(array_map(function (ReflectionParameter $parameter) {
+ private function buildClassConstructorParameters(\ReflectionMethod $constructor): array {
+ return array_map(function (ReflectionParameter $parameter) {
$parameterType = $parameter->getType();
$resolveName = $parameter->getName();
@@ -84,10 +85,10 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
}
try {
- $builtIn = $parameter->hasType() && ($parameter->getType() instanceof ReflectionNamedType)
- && $parameter->getType()->isBuiltin();
+ $builtIn = $parameterType !== null && ($parameterType instanceof ReflectionNamedType)
+ && $parameterType->isBuiltin();
return $this->query($resolveName, !$builtIn);
- } catch (QueryException $e) {
+ } catch (ContainerExceptionInterface $e) {
// Service not found, use the default value when available
if ($parameter->isDefaultValueAvailable()) {
return $parameter->getDefaultValue();
@@ -97,15 +98,20 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
$resolveName = $parameter->getName();
try {
return $this->query($resolveName);
- } catch (QueryException $e2) {
+ } catch (ContainerExceptionInterface $e2) {
+ // Pass null if typed and nullable
+ if ($parameter->allowsNull() && ($parameterType instanceof ReflectionNamedType)) {
+ return null;
+ }
+
// don't lose the error we got while trying to query by type
- throw new QueryException($e2->getMessage(), (int) $e2->getCode(), $e);
+ throw new QueryException($e->getMessage(), (int)$e->getCode(), $e);
}
}
throw $e;
}
- }, $constructor->getParameters()));
+ }, $constructor->getParameters());
}
public function resolve($name) {
@@ -115,11 +121,12 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
if ($class->isInstantiable()) {
return $this->buildClass($class);
} else {
- throw new QueryException($baseMsg .
- ' Class can not be instantiated');
+ throw new QueryException($baseMsg
+ . ' Class can not be instantiated');
}
} catch (ReflectionException $e) {
- throw new QueryException($baseMsg . ' ' . $e->getMessage());
+ // Class does not exist
+ throw new QueryNotFoundException($baseMsg . ' ' . $e->getMessage());
}
}
@@ -137,7 +144,7 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
return $object;
}
- throw new QueryException('Could not resolve ' . $name . '!');
+ throw new QueryNotFoundException('Could not resolve ' . $name . '!');
}
/**
@@ -162,13 +169,13 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
return $closure($this);
};
$name = $this->sanitizeName($name);
- if (isset($this[$name])) {
- unset($this[$name]);
+ if (isset($this->container[$name])) {
+ unset($this->container[$name]);
}
if ($shared) {
- $this[$name] = $wrapped;
+ $this->container[$name] = $wrapped;
} else {
- $this[$name] = $this->container->factory($wrapped);
+ $this->container[$name] = $this->container->factory($wrapped);
}
}
@@ -179,13 +186,28 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
* @param string $alias the alias that should be registered
* @param string $target the target that should be resolved instead
*/
- public function registerAlias($alias, $target) {
- $this->registerService($alias, function (ContainerInterface $container) use ($target) {
+ public function registerAlias($alias, $target): void {
+ $this->registerService($alias, function (ContainerInterface $container) use ($target): mixed {
return $container->get($target);
}, false);
}
- /*
+ protected function registerDeprecatedAlias(string $alias, string $target): void {
+ $this->registerService($alias, function (ContainerInterface $container) use ($target, $alias): mixed {
+ try {
+ $logger = $container->get(LoggerInterface::class);
+ $logger->debug('The requested alias "' . $alias . '" is deprecated. Please request "' . $target . '" directly. This alias will be removed in a future Nextcloud version.', [
+ 'app' => $this->appName ?? 'serverDI',
+ ]);
+ } catch (ContainerExceptionInterface $e) {
+ // Could not get logger. Continue
+ }
+
+ return $container->get($target);
+ }, false);
+ }
+
+ /**
* @param string $name
* @return string
*/
@@ -215,8 +237,8 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
/**
* @deprecated 20.0.0 use \OCP\IContainer::registerService
*/
- public function offsetSet($id, $service): void {
- $this->container->offsetSet($id, $service);
+ public function offsetSet($offset, $value): void {
+ $this->container->offsetSet($offset, $value);
}
/**
diff --git a/lib/private/AppFramework/Utility/TimeFactory.php b/lib/private/AppFramework/Utility/TimeFactory.php
index d4fe451a995..0584fd05ef9 100644
--- a/lib/private/AppFramework/Utility/TimeFactory.php
+++ b/lib/private/AppFramework/Utility/TimeFactory.php
@@ -1,42 +1,33 @@
<?php
declare(strict_types=1);
-
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Bernhard Posselt <dev@bernhard-posselt.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @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: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\AppFramework\Utility;
use OCP\AppFramework\Utility\ITimeFactory;
/**
- * Needed to mock calls to time()
+ * Use this to get a timestamp or DateTime object in code to remain testable
+ *
+ * @since 8.0.0
+ * @since 27.0.0 Implements the \Psr\Clock\ClockInterface interface
+ * @ref https://www.php-fig.org/psr/psr-20/#21-clockinterface
*/
class TimeFactory implements ITimeFactory {
+ protected \DateTimeZone $timezone;
+ public function __construct() {
+ $this->timezone = new \DateTimeZone('UTC');
+ }
/**
* @return int the result of a call to time()
+ * @since 8.0.0
+ * @deprecated 26.0.0 {@see ITimeFactory::now()}
*/
public function getTime(): int {
return time();
@@ -47,8 +38,26 @@ class TimeFactory implements ITimeFactory {
* @param \DateTimeZone $timezone
* @return \DateTime
* @since 15.0.0
+ * @deprecated 26.0.0 {@see ITimeFactory::now()}
*/
- public function getDateTime(string $time = 'now', \DateTimeZone $timezone = null): \DateTime {
+ public function getDateTime(string $time = 'now', ?\DateTimeZone $timezone = null): \DateTime {
return new \DateTime($time, $timezone);
}
+
+ public function now(): \DateTimeImmutable {
+ return new \DateTimeImmutable('now', $this->timezone);
+ }
+ public function withTimeZone(\DateTimeZone $timezone): static {
+ $clone = clone $this;
+ $clone->timezone = $timezone;
+
+ return $clone;
+ }
+
+ public function getTimeZone(?string $timezone = null): \DateTimeZone {
+ if ($timezone !== null) {
+ return new \DateTimeZone($timezone);
+ }
+ return $this->timezone;
+ }
}