aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private')
-rw-r--r--lib/private/App/AppManager.php326
-rw-r--r--lib/private/AppConfig.php4
-rw-r--r--lib/private/AppFramework/Bootstrap/RegistrationContext.php25
-rw-r--r--lib/private/AppFramework/DependencyInjection/DIContainer.php3
-rw-r--r--lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php67
-rw-r--r--lib/private/AppFramework/Utility/TimeFactory.php26
-rw-r--r--lib/private/Authentication/Token/Manager.php12
-rw-r--r--lib/private/Authentication/Token/PublicKeyTokenProvider.php2
-rw-r--r--lib/private/Collaboration/Reference/File/FileReferenceProvider.php42
-rw-r--r--lib/private/Comments/Manager.php24
-rw-r--r--lib/private/Config.php10
-rw-r--r--lib/private/DB/SQLiteMigrator.php6
-rw-r--r--lib/private/Files/Cache/Cache.php20
-rw-r--r--lib/private/Files/Config/UserMountCache.php31
-rw-r--r--lib/private/Files/Node/Folder.php15
-rw-r--r--lib/private/Files/Node/LazyUserFolder.php18
-rw-r--r--lib/private/Files/Node/Root.php2
-rw-r--r--lib/private/Files/ObjectStore/ObjectStoreStorage.php76
-rw-r--r--lib/private/Files/ObjectStore/S3.php60
-rw-r--r--lib/private/Files/View.php4
-rw-r--r--lib/private/Group/Group.php2
-rw-r--r--lib/private/Log/ExceptionSerializer.php10
-rw-r--r--lib/private/Memcache/Redis.php4
-rw-r--r--lib/private/Preview/Imaginary.php65
-rw-r--r--lib/private/Preview/Movie.php27
-rw-r--r--lib/private/Repair/NC21/ValidatePhoneNumber.php3
-rw-r--r--lib/private/Repair/RemoveLinkShares.php2
-rw-r--r--lib/private/Server.php8
-rw-r--r--lib/private/Setup/AbstractDatabase.php6
-rw-r--r--lib/private/Setup/MySQL.php17
-rw-r--r--lib/private/Setup/PostgreSQL.php80
-rw-r--r--lib/private/Share/Constants.php2
-rw-r--r--lib/private/Share/Share.php2
-rw-r--r--lib/private/Share20/Manager.php1
-rw-r--r--lib/private/Share20/ProviderFactory.php2
-rw-r--r--lib/private/SystemTag/SystemTagManager.php8
-rw-r--r--lib/private/SystemTag/SystemTagObjectMapper.php16
-rw-r--r--lib/private/Template/JSResourceLocator.php93
-rw-r--r--lib/private/Translation/TranslationManager.php120
-rw-r--r--lib/private/User/Database.php29
-rw-r--r--lib/private/User/Session.php26
-rw-r--r--lib/private/User/User.php1
-rw-r--r--lib/private/legacy/OC_App.php204
-rw-r--r--lib/private/legacy/OC_Helper.php11
-rw-r--r--lib/private/legacy/OC_Image.php4
-rw-r--r--lib/private/legacy/OC_User.php11
-rw-r--r--lib/private/legacy/template/functions.php16
47 files changed, 1063 insertions, 480 deletions
diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php
index d14f0a2644e..5f243a1250e 100644
--- a/lib/private/App/AppManager.php
+++ b/lib/private/App/AppManager.php
@@ -39,15 +39,25 @@
namespace OC\App;
use OC\AppConfig;
+use OC\AppFramework\Bootstrap\Coordinator;
+use OC\ServerNotAvailableException;
+use OCP\Activity\IManager as IActivityManager;
use OCP\App\AppPathNotFoundException;
+use OCP\App\Events\AppDisableEvent;
+use OCP\App\Events\AppEnableEvent;
use OCP\App\IAppManager;
use OCP\App\ManagerEvent;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Collaboration\AutoComplete\IManager as IAutoCompleteManager;
+use OCP\Collaboration\Collaborators\ISearch as ICollaboratorSearch;
+use OCP\Diagnostics\IEventLogger;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserSession;
+use OCP\Settings\IManager as ISettingsManager;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -64,57 +74,51 @@ class AppManager implements IAppManager {
'prevent_group_restriction',
];
- /** @var IUserSession */
- private $userSession;
-
- /** @var IConfig */
- private $config;
-
- /** @var AppConfig */
- private $appConfig;
-
- /** @var IGroupManager */
- private $groupManager;
-
- /** @var ICacheFactory */
- private $memCacheFactory;
-
- /** @var EventDispatcherInterface */
- private $dispatcher;
-
- /** @var LoggerInterface */
- private $logger;
+ private IUserSession $userSession;
+ private IConfig $config;
+ private AppConfig $appConfig;
+ private IGroupManager $groupManager;
+ private ICacheFactory $memCacheFactory;
+ private EventDispatcherInterface $legacyDispatcher;
+ private IEventDispatcher $dispatcher;
+ private LoggerInterface $logger;
/** @var string[] $appId => $enabled */
- private $installedAppsCache;
+ private array $installedAppsCache = [];
- /** @var string[] */
- private $shippedApps;
+ /** @var string[]|null */
+ private ?array $shippedApps = null;
private array $alwaysEnabled = [];
private array $defaultEnabled = [];
/** @var array */
- private $appInfos = [];
+ private array $appInfos = [];
/** @var array */
- private $appVersions = [];
+ private array $appVersions = [];
/** @var array */
- private $autoDisabledApps = [];
+ private array $autoDisabledApps = [];
+ private array $appTypes = [];
+
+ /** @var array<string, true> */
+ private array $loadedApps = [];
public function __construct(IUserSession $userSession,
IConfig $config,
AppConfig $appConfig,
IGroupManager $groupManager,
ICacheFactory $memCacheFactory,
- EventDispatcherInterface $dispatcher,
+ EventDispatcherInterface $legacyDispatcher,
+ IEventDispatcher $dispatcher,
LoggerInterface $logger) {
$this->userSession = $userSession;
$this->config = $config;
$this->appConfig = $appConfig;
$this->groupManager = $groupManager;
$this->memCacheFactory = $memCacheFactory;
+ $this->legacyDispatcher = $legacyDispatcher;
$this->dispatcher = $dispatcher;
$this->logger = $logger;
}
@@ -122,7 +126,7 @@ class AppManager implements IAppManager {
/**
* @return string[] $appId => $enabled
*/
- private function getInstalledAppsValues() {
+ private function getInstalledAppsValues(): array {
if (!$this->installedAppsCache) {
$values = $this->appConfig->getValues(false, 'enabled');
@@ -163,7 +167,7 @@ class AppManager implements IAppManager {
}
/**
- * @param \OCP\IGroup $group
+ * @param IGroup $group
* @return array
*/
public function getEnabledAppsForGroup(IGroup $group): array {
@@ -175,6 +179,91 @@ class AppManager implements IAppManager {
}
/**
+ * Loads all apps
+ *
+ * @param string[] $types
+ * @return bool
+ *
+ * This function walks through the Nextcloud directory and loads all apps
+ * it can find. A directory contains an app if the file /appinfo/info.xml
+ * exists.
+ *
+ * if $types is set to non-empty array, only apps of those types will be loaded
+ */
+ public function loadApps(array $types = []): bool {
+ if ($this->config->getSystemValueBool('maintenance', false)) {
+ return false;
+ }
+ // Load the enabled apps here
+ $apps = \OC_App::getEnabledApps();
+
+ // Add each apps' folder as allowed class path
+ foreach ($apps as $app) {
+ // If the app is already loaded then autoloading it makes no sense
+ if (!$this->isAppLoaded($app)) {
+ $path = \OC_App::getAppPath($app);
+ if ($path !== false) {
+ \OC_App::registerAutoloading($app, $path);
+ }
+ }
+ }
+
+ // prevent app.php from printing output
+ ob_start();
+ foreach ($apps as $app) {
+ if (!$this->isAppLoaded($app) && ($types === [] || $this->isType($app, $types))) {
+ try {
+ $this->loadApp($app);
+ } catch (\Throwable $e) {
+ $this->logger->emergency('Error during app loading: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'app' => $app,
+ ]);
+ }
+ }
+ }
+ ob_end_clean();
+
+ return true;
+ }
+
+ /**
+ * check if an app is of a specific type
+ *
+ * @param string $app
+ * @param array $types
+ * @return bool
+ */
+ public function isType(string $app, array $types): bool {
+ $appTypes = $this->getAppTypes($app);
+ foreach ($types as $type) {
+ if (in_array($type, $appTypes, true)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * get the types of an app
+ *
+ * @param string $app
+ * @return string[]
+ */
+ private function getAppTypes(string $app): array {
+ //load the cache
+ if (count($this->appTypes) === 0) {
+ $this->appTypes = $this->appConfig->getValues(false, 'types') ?: [];
+ }
+
+ if (isset($this->appTypes[$app])) {
+ return explode(',', $this->appTypes[$app]);
+ }
+
+ return [];
+ }
+
+ /**
* @return array
*/
public function getAutoDisabledApps(): array {
@@ -221,12 +310,7 @@ class AppManager implements IAppManager {
}
}
- /**
- * @param string $enabled
- * @param IUser $user
- * @return bool
- */
- private function checkAppForUser($enabled, $user) {
+ private function checkAppForUser(string $enabled, ?IUser $user): bool {
if ($enabled === 'yes') {
return true;
} elseif ($user === null) {
@@ -254,16 +338,9 @@ class AppManager implements IAppManager {
}
}
- /**
- * @param string $enabled
- * @param IGroup $group
- * @return bool
- */
private function checkAppForGroups(string $enabled, IGroup $group): bool {
if ($enabled === 'yes') {
return true;
- } elseif ($group === null) {
- return false;
} else {
if (empty($enabled)) {
return false;
@@ -287,7 +364,7 @@ class AppManager implements IAppManager {
* Notice: This actually checks if the app is enabled and not only if it is installed.
*
* @param string $appId
- * @param \OCP\IGroup[]|String[] $groups
+ * @param IGroup[]|String[] $groups
* @return bool
*/
public function isInstalled($appId) {
@@ -303,6 +380,151 @@ class AppManager implements IAppManager {
}
}
+ public function loadApp(string $app): void {
+ if (isset($this->loadedApps[$app])) {
+ return;
+ }
+ $this->loadedApps[$app] = true;
+ $appPath = \OC_App::getAppPath($app);
+ if ($appPath === false) {
+ return;
+ }
+ $eventLogger = \OC::$server->get(\OCP\Diagnostics\IEventLogger::class);
+ $eventLogger->start("bootstrap:load_app:$app", "Load $app");
+
+ // in case someone calls loadApp() directly
+ \OC_App::registerAutoloading($app, $appPath);
+
+ /** @var Coordinator $coordinator */
+ $coordinator = \OC::$server->get(Coordinator::class);
+ $isBootable = $coordinator->isBootable($app);
+
+ $hasAppPhpFile = is_file($appPath . '/appinfo/app.php');
+
+ $eventLogger = \OC::$server->get(IEventLogger::class);
+ $eventLogger->start('bootstrap:load_app_' . $app, 'Load app: ' . $app);
+ if ($isBootable && $hasAppPhpFile) {
+ $this->logger->error('/appinfo/app.php is not loaded when \OCP\AppFramework\Bootstrap\IBootstrap on the application class is used. Migrate everything from app.php to the Application class.', [
+ 'app' => $app,
+ ]);
+ } elseif ($hasAppPhpFile) {
+ $eventLogger->start("bootstrap:load_app:$app:app.php", "Load legacy app.php app $app");
+ $this->logger->debug('/appinfo/app.php is deprecated, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [
+ 'app' => $app,
+ ]);
+ try {
+ self::requireAppFile($appPath);
+ } catch (\Throwable $ex) {
+ if ($ex instanceof ServerNotAvailableException) {
+ throw $ex;
+ }
+ if (!$this->isShipped($app) && !$this->isType($app, ['authentication'])) {
+ $this->logger->error("App $app threw an error during app.php load and will be disabled: " . $ex->getMessage(), [
+ 'exception' => $ex,
+ ]);
+
+ // Only disable apps which are not shipped and that are not authentication apps
+ $this->disableApp($app, true);
+ } else {
+ $this->logger->error("App $app threw an error during app.php load: " . $ex->getMessage(), [
+ 'exception' => $ex,
+ ]);
+ }
+ }
+ $eventLogger->end("bootstrap:load_app:$app:app.php");
+ }
+
+ $coordinator->bootApp($app);
+
+ $eventLogger->start("bootstrap:load_app:$app:info", "Load info.xml for $app and register any services defined in it");
+ $info = $this->getAppInfo($app);
+ if (!empty($info['activity'])) {
+ $activityManager = \OC::$server->get(IActivityManager::class);
+ if (!empty($info['activity']['filters'])) {
+ foreach ($info['activity']['filters'] as $filter) {
+ $activityManager->registerFilter($filter);
+ }
+ }
+ if (!empty($info['activity']['settings'])) {
+ foreach ($info['activity']['settings'] as $setting) {
+ $activityManager->registerSetting($setting);
+ }
+ }
+ if (!empty($info['activity']['providers'])) {
+ foreach ($info['activity']['providers'] as $provider) {
+ $activityManager->registerProvider($provider);
+ }
+ }
+ }
+
+ if (!empty($info['settings'])) {
+ $settingsManager = \OC::$server->get(ISettingsManager::class);
+ if (!empty($info['settings']['admin'])) {
+ foreach ($info['settings']['admin'] as $setting) {
+ $settingsManager->registerSetting('admin', $setting);
+ }
+ }
+ if (!empty($info['settings']['admin-section'])) {
+ foreach ($info['settings']['admin-section'] as $section) {
+ $settingsManager->registerSection('admin', $section);
+ }
+ }
+ if (!empty($info['settings']['personal'])) {
+ foreach ($info['settings']['personal'] as $setting) {
+ $settingsManager->registerSetting('personal', $setting);
+ }
+ }
+ if (!empty($info['settings']['personal-section'])) {
+ foreach ($info['settings']['personal-section'] as $section) {
+ $settingsManager->registerSection('personal', $section);
+ }
+ }
+ }
+
+ if (!empty($info['collaboration']['plugins'])) {
+ // deal with one or many plugin entries
+ $plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
+ [$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
+ $collaboratorSearch = null;
+ $autoCompleteManager = null;
+ foreach ($plugins as $plugin) {
+ if ($plugin['@attributes']['type'] === 'collaborator-search') {
+ $pluginInfo = [
+ 'shareType' => $plugin['@attributes']['share-type'],
+ 'class' => $plugin['@value'],
+ ];
+ $collaboratorSearch ??= \OC::$server->get(ICollaboratorSearch::class);
+ $collaboratorSearch->registerPlugin($pluginInfo);
+ } elseif ($plugin['@attributes']['type'] === 'autocomplete-sort') {
+ $autoCompleteManager ??= \OC::$server->get(IAutoCompleteManager::class);
+ $autoCompleteManager->registerSorter($plugin['@value']);
+ }
+ }
+ }
+ $eventLogger->end("bootstrap:load_app:$app:info");
+
+ $eventLogger->end("bootstrap:load_app:$app");
+ }
+ /**
+ * Check if an app is loaded
+ * @param string $app app id
+ * @since 26.0.0
+ */
+ public function isAppLoaded(string $app): bool {
+ return isset($this->loadedApps[$app]);
+ }
+
+ /**
+ * Load app.php from the given app
+ *
+ * @param string $app app name
+ * @throws \Error
+ */
+ private static function requireAppFile(string $app): void {
+ // encapsulated here to avoid variable scope conflicts
+ require_once $app . '/appinfo/app.php';
+ }
+
/**
* Enable an app for every user
*
@@ -320,7 +542,8 @@ class AppManager implements IAppManager {
$this->installedAppsCache[$appId] = 'yes';
$this->appConfig->setValue($appId, 'enabled', 'yes');
- $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(
+ $this->dispatcher->dispatchTyped(new AppEnableEvent($appId));
+ $this->legacyDispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(
ManagerEvent::EVENT_APP_ENABLE, $appId
));
$this->clearAppsCache();
@@ -345,7 +568,7 @@ class AppManager implements IAppManager {
* Enable an app only for specific groups
*
* @param string $appId
- * @param \OCP\IGroup[] $groups
+ * @param IGroup[] $groups
* @param bool $forceEnable
* @throws \InvalidArgumentException if app can't be enabled for groups
* @throws AppPathNotFoundException
@@ -363,8 +586,9 @@ class AppManager implements IAppManager {
$this->ignoreNextcloudRequirementForApp($appId);
}
+ /** @var string[] $groupIds */
$groupIds = array_map(function ($group) {
- /** @var \OCP\IGroup $group */
+ /** @var IGroup $group */
return ($group instanceof IGroup)
? $group->getGID()
: $group;
@@ -372,7 +596,8 @@ class AppManager implements IAppManager {
$this->installedAppsCache[$appId] = json_encode($groupIds);
$this->appConfig->setValue($appId, 'enabled', json_encode($groupIds));
- $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(
+ $this->dispatcher->dispatchTyped(new AppEnableEvent($appId, $groupIds));
+ $this->legacyDispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(
ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups
));
$this->clearAppsCache();
@@ -407,7 +632,8 @@ class AppManager implements IAppManager {
\OC_App::executeRepairSteps($appId, $appData['repair-steps']['uninstall']);
}
- $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(
+ $this->dispatcher->dispatchTyped(new AppDisableEvent($appId));
+ $this->legacyDispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(
ManagerEvent::EVENT_APP_DISABLE, $appId
));
$this->clearAppsCache();
@@ -556,7 +782,7 @@ class AppManager implements IAppManager {
return in_array($appId, $this->shippedApps, true);
}
- private function isAlwaysEnabled($appId) {
+ private function isAlwaysEnabled(string $appId): bool {
$alwaysEnabled = $this->getAlwaysEnabledApps();
return in_array($appId, $alwaysEnabled, true);
}
@@ -565,7 +791,7 @@ class AppManager implements IAppManager {
* In case you change this method, also change \OC\App\CodeChecker\InfoChecker::loadShippedJson()
* @throws \Exception
*/
- private function loadShippedJson() {
+ private function loadShippedJson(): void {
if ($this->shippedApps === null) {
$shippedJson = \OC::$SERVERROOT . '/core/shipped.json';
if (!file_exists($shippedJson)) {
diff --git a/lib/private/AppConfig.php b/lib/private/AppConfig.php
index 04e76373466..96fa6bf7467 100644
--- a/lib/private/AppConfig.php
+++ b/lib/private/AppConfig.php
@@ -34,6 +34,7 @@ namespace OC;
use OC\DB\Connection;
use OC\DB\OracleConnection;
+use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IAppConfig;
use OCP\IConfig;
@@ -111,6 +112,7 @@ class AppConfig implements IAppConfig {
'spreed' => [
'/^bridge_bot_password$/',
'/^hosted-signaling-server-(.*)$/',
+ '/^recording_servers$/',
'/^signaling_servers$/',
'/^signaling_ticket_secret$/',
'/^signaling_token_privkey_(.*)$/',
@@ -298,7 +300,7 @@ class AppConfig implements IAppConfig {
$sql->andWhere(
$sql->expr()->orX(
$sql->expr()->isNull('configvalue'),
- $sql->expr()->neq('configvalue', $sql->createNamedParameter($value))
+ $sql->expr()->neq('configvalue', $sql->createNamedParameter($value), IQueryBuilder::PARAM_STR)
)
);
}
diff --git a/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/lib/private/AppFramework/Bootstrap/RegistrationContext.php
index a78a895d029..9a6c298419a 100644
--- a/lib/private/AppFramework/Bootstrap/RegistrationContext.php
+++ b/lib/private/AppFramework/Bootstrap/RegistrationContext.php
@@ -34,6 +34,7 @@ use OCP\Calendar\Resource\IBackend as IResourceBackend;
use OCP\Calendar\Room\IBackend as IRoomBackend;
use OCP\Collaboration\Reference\IReferenceProvider;
use OCP\Talk\ITalkBackend;
+use OCP\Translation\ITranslationProvider;
use RuntimeException;
use function array_shift;
use OC\Support\CrashReport\Registry;
@@ -113,6 +114,9 @@ class RegistrationContext {
/** @var ServiceRegistration<ICustomTemplateProvider>[] */
private $templateProviders = [];
+ /** @var ServiceRegistration<ITranslationProvider>[] */
+ private $translationProviders = [];
+
/** @var ServiceRegistration<INotifier>[] */
private $notifierServices = [];
@@ -125,6 +129,9 @@ class RegistrationContext {
/** @var ServiceRegistration<IReferenceProvider>[] */
private array $referenceProviders = [];
+
+
+
/** @var ParameterRegistration[] */
private $sensitiveMethods = [];
@@ -252,6 +259,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,
@@ -404,6 +418,10 @@ class RegistrationContext {
$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);
}
@@ -675,6 +693,13 @@ class RegistrationContext {
}
/**
+ * @return ServiceRegistration<ITranslationProvider>[]
+ */
+ public function getTranslationProviders(): array {
+ return $this->translationProviders;
+ }
+
+ /**
* @return ServiceRegistration<INotifier>[]
*/
public function getNotifierServices(): array {
diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php
index 9b202f07fbf..9a9740b7bcc 100644
--- a/lib/private/AppFramework/DependencyInjection/DIContainer.php
+++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php
@@ -292,7 +292,8 @@ class DIContainer extends SimpleContainer implements IAppContainer {
new OC\AppFramework\Middleware\Security\BruteForceMiddleware(
$c->get(IControllerMethodReflector::class),
$c->get(OC\Security\Bruteforce\Throttler::class),
- $c->get(IRequest::class)
+ $c->get(IRequest::class),
+ $c->get(LoggerInterface::class)
)
);
$dispatcher->registerMiddleware(
diff --git a/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php b/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php
index 069d04a9e75..ba8c7f45b49 100644
--- a/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php
@@ -3,6 +3,7 @@
declare(strict_types=1);
/**
+ * @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
@@ -31,6 +32,7 @@ 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;
@@ -38,6 +40,8 @@ use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
use OCP\Security\Bruteforce\MaxDelayReached;
+use Psr\Log\LoggerInterface;
+use ReflectionMethod;
/**
* Class BruteForceMiddleware performs the bruteforce protection for controllers
@@ -47,16 +51,12 @@ use OCP\Security\Bruteforce\MaxDelayReached;
* @package OC\AppFramework\Middleware\Security
*/
class BruteForceMiddleware extends Middleware {
- private ControllerMethodReflector $reflector;
- private Throttler $throttler;
- private 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 Throttler $throttler,
+ protected IRequest $request,
+ protected LoggerInterface $logger,
+ ) {
}
/**
@@ -68,6 +68,20 @@ class BruteForceMiddleware extends Middleware {
if ($this->reflector->hasAnnotation('BruteForceProtection')) {
$action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
$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->throttler->sleepDelayOrThrowOnMax($remoteAddress, $action);
+ }
+ }
}
}
@@ -75,11 +89,34 @@ 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()) {
+ if ($this->reflector->hasAnnotation('BruteForceProtection')) {
+ $action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
+ $ip = $this->request->getRemoteAddress();
+ $this->throttler->sleepDelay($ip, $action);
+ $this->throttler->registerAttempt($action, $ip, $response->getThrottleMetadata());
+ } 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->sleepDelay($ip, $action);
+ $this->throttler->registerAttempt($action, $ip, $metaData);
+ }
+ }
+ } else {
+ $this->logger->debug('Response for ' . get_class($controller) . '::' . $methodName . ' got bruteforce throttled but has no annotation nor attribute defined.');
+ }
+ }
}
return parent::afterController($controller, $methodName, $response);
diff --git a/lib/private/AppFramework/Utility/TimeFactory.php b/lib/private/AppFramework/Utility/TimeFactory.php
index 27117ed3cfc..1e4655dd1cd 100644
--- a/lib/private/AppFramework/Utility/TimeFactory.php
+++ b/lib/private/AppFramework/Utility/TimeFactory.php
@@ -3,6 +3,7 @@
declare(strict_types=1);
/**
+ * @copyright Copyright (c) 2022, Joas Schilling <coding@schilljs.com>
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bernhard Posselt <dev@bernhard-posselt.com>
@@ -30,11 +31,23 @@ 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 26.0.0 Extends 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();
@@ -45,8 +58,19 @@ 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 {
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;
+ }
}
diff --git a/lib/private/Authentication/Token/Manager.php b/lib/private/Authentication/Token/Manager.php
index 59c7ca714c6..761e799d298 100644
--- a/lib/private/Authentication/Token/Manager.php
+++ b/lib/private/Authentication/Token/Manager.php
@@ -32,8 +32,9 @@ use OC\Authentication\Exceptions\ExpiredTokenException;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OC\Authentication\Exceptions\WipeTokenException;
+use OCP\Authentication\Token\IProvider as OCPIProvider;
-class Manager implements IProvider {
+class Manager implements IProvider, OCPIProvider {
/** @var PublicKeyTokenProvider */
private $publicKeyTokenProvider;
@@ -239,4 +240,13 @@ class Manager implements IProvider {
public function updatePasswords(string $uid, string $password) {
$this->publicKeyTokenProvider->updatePasswords($uid, $password);
}
+
+ public function invalidateTokensOfUser(string $uid, ?string $clientName) {
+ $tokens = $this->getTokenByUser($uid);
+ foreach ($tokens as $token) {
+ if ($clientName === null || ($token->getName() === $clientName)) {
+ $this->invalidateTokenById($uid, $token->getId());
+ }
+ }
+ }
}
diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php
index 38bbef8fb61..824e2e056c8 100644
--- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php
+++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php
@@ -113,7 +113,7 @@ class PublicKeyTokenProvider implements IProvider {
// We need to check against one old token to see if there is a password
// hash that we can reuse for detecting outdated passwords
$randomOldToken = $this->mapper->getFirstTokenForUser($uid);
- $oldTokenMatches = $randomOldToken && $randomOldToken->getPasswordHash() && $this->hasher->verify(sha1($password) . $password, $randomOldToken->getPasswordHash());
+ $oldTokenMatches = $randomOldToken && $randomOldToken->getPasswordHash() && $password !== null && $this->hasher->verify(sha1($password) . $password, $randomOldToken->getPasswordHash());
$dbToken = $this->newToken($token, $uid, $loginName, $password, $name, $type, $remember);
diff --git a/lib/private/Collaboration/Reference/File/FileReferenceProvider.php b/lib/private/Collaboration/Reference/File/FileReferenceProvider.php
index 95e49cdf860..d423a830495 100644
--- a/lib/private/Collaboration/Reference/File/FileReferenceProvider.php
+++ b/lib/private/Collaboration/Reference/File/FileReferenceProvider.php
@@ -25,8 +25,8 @@ declare(strict_types=1);
namespace OC\Collaboration\Reference\File;
use OC\User\NoUserException;
+use OCP\Collaboration\Reference\ADiscoverableReferenceProvider;
use OCP\Collaboration\Reference\IReference;
-use OCP\Collaboration\Reference\IReferenceProvider;
use OCP\Collaboration\Reference\Reference;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\InvalidPathException;
@@ -34,27 +34,34 @@ use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
+use OCP\IL10N;
use OCP\IPreview;
use OCP\IURLGenerator;
use OCP\IUserSession;
+use OCP\L10N\IFactory;
-class FileReferenceProvider implements IReferenceProvider {
+class FileReferenceProvider extends ADiscoverableReferenceProvider {
private IURLGenerator $urlGenerator;
private IRootFolder $rootFolder;
private ?string $userId;
private IPreview $previewManager;
private IMimeTypeDetector $mimeTypeDetector;
-
- public function __construct(IURLGenerator $urlGenerator,
- IRootFolder $rootFolder,
- IUserSession $userSession,
- IMimeTypeDetector $mimeTypeDetector,
- IPreview $previewManager) {
+ private IL10N $l10n;
+
+ public function __construct(
+ IURLGenerator $urlGenerator,
+ IRootFolder $rootFolder,
+ IUserSession $userSession,
+ IMimeTypeDetector $mimeTypeDetector,
+ IPreview $previewManager,
+ IFactory $l10n
+ ) {
$this->urlGenerator = $urlGenerator;
$this->rootFolder = $rootFolder;
$this->userId = $userSession->getUser() ? $userSession->getUser()->getUID() : null;
$this->previewManager = $previewManager;
$this->mimeTypeDetector = $mimeTypeDetector;
+ $this->l10n = $l10n->get('files');
}
public function matchReference(string $referenceText): bool {
@@ -145,9 +152,10 @@ class FileReferenceProvider implements IReferenceProvider {
'id' => $file->getId(),
'name' => $file->getName(),
'size' => $file->getSize(),
- 'path' => $file->getPath(),
+ 'path' => $userFolder->getRelativePath($file->getPath()),
'link' => $reference->getUrl(),
'mimetype' => $file->getMimetype(),
+ 'mtime' => $file->getMTime(),
'preview-available' => $this->previewManager->isAvailable($file)
]);
} catch (InvalidPathException|NotFoundException|NotPermittedException|NoUserException $e) {
@@ -162,4 +170,20 @@ class FileReferenceProvider implements IReferenceProvider {
public function getCacheKey(string $referenceId): ?string {
return $this->userId ?? '';
}
+
+ public function getId(): string {
+ return 'files';
+ }
+
+ public function getTitle(): string {
+ return $this->l10n->t('Files');
+ }
+
+ public function getOrder(): int {
+ return 0;
+ }
+
+ public function getIconUrl(): string {
+ return $this->urlGenerator->imagePath('files', 'folder.svg');
+ }
}
diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php
index 00cf323bfbf..c5fb4ebfe34 100644
--- a/lib/private/Comments/Manager.php
+++ b/lib/private/Comments/Manager.php
@@ -1031,6 +1031,7 @@ class Manager implements ICommentsManager {
->select('message_id')
->from('reactions')
->where($qb->expr()->eq('parent_id', $qb->createNamedParameter($parentId)))
+ ->orderBy('message_id', 'DESC')
->executeQuery();
$commentIds = [];
@@ -1106,22 +1107,29 @@ class Manager implements ICommentsManager {
if (!$commentIds) {
return [];
}
- $query = $this->dbConn->getQueryBuilder();
+ $chunks = array_chunk($commentIds, 500);
+
+ $query = $this->dbConn->getQueryBuilder();
$query->select('*')
->from('comments')
- ->where($query->expr()->in('id', $query->createNamedParameter($commentIds, IQueryBuilder::PARAM_STR_ARRAY)))
+ ->where($query->expr()->in('id', $query->createParameter('ids')))
->orderBy('creation_timestamp', 'DESC')
->addOrderBy('id', 'DESC');
$comments = [];
- $result = $query->executeQuery();
- while ($data = $result->fetch()) {
- $comment = $this->getCommentFromData($data);
- $this->cache($comment);
- $comments[] = $comment;
+ foreach ($chunks as $ids) {
+ $query->setParameter('ids', $ids, IQueryBuilder::PARAM_STR_ARRAY);
+
+ $result = $query->executeQuery();
+ while ($data = $result->fetch()) {
+ $comment = $this->getCommentFromData($data);
+ $this->cache($comment);
+ $comments[] = $comment;
+ }
+ $result->closeCursor();
}
- $result->closeCursor();
+
return $comments;
}
diff --git a/lib/private/Config.php b/lib/private/Config.php
index a9ecaf2c825..3ea822101df 100644
--- a/lib/private/Config.php
+++ b/lib/private/Config.php
@@ -286,10 +286,12 @@ class Config {
}
// Never write file back if disk space should be too low
- $df = disk_free_space($this->configDir);
- $size = strlen($content) + 10240;
- if ($df !== false && $df < (float)$size) {
- throw new \Exception($this->configDir . " does not have enough space for writing the config file! Not writing it back!");
+ if (function_exists('disk_free_space')) {
+ $df = disk_free_space($this->configDir);
+ $size = strlen($content) + 10240;
+ if ($df !== false && $df < (float)$size) {
+ throw new \Exception($this->configDir . " does not have enough space for writing the config file! Not writing it back!");
+ }
}
// Try to acquire a file lock
diff --git a/lib/private/DB/SQLiteMigrator.php b/lib/private/DB/SQLiteMigrator.php
index 2be3591afdc..cbb39070a48 100644
--- a/lib/private/DB/SQLiteMigrator.php
+++ b/lib/private/DB/SQLiteMigrator.php
@@ -39,9 +39,13 @@ class SQLiteMigrator extends Migrator {
$platform->registerDoctrineTypeMapping('smallint unsigned', 'integer');
$platform->registerDoctrineTypeMapping('varchar ', 'string');
- // with sqlite autoincrement columns is of type integer
foreach ($targetSchema->getTables() as $table) {
foreach ($table->getColumns() as $column) {
+ // column comments are not supported on SQLite
+ if ($column->getComment() !== null) {
+ $column->setComment(null);
+ }
+ // with sqlite autoincrement columns is of type integer
if ($column->getType() instanceof BigIntType && $column->getAutoincrement()) {
$column->setType(Type::getType('integer'));
}
diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php
index 6440bf05a1d..9afeea7b573 100644
--- a/lib/private/Files/Cache/Cache.php
+++ b/lib/private/Files/Cache/Cache.php
@@ -575,7 +575,7 @@ class Cache implements ICache {
}
/**
- * Recursively remove all children of a folder
+ * Remove all children of a folder
*
* @param ICacheEntry $entry the cache entry of the folder to remove the children of
* @throws \OC\DatabaseException
@@ -583,6 +583,8 @@ class Cache implements ICache {
private function removeChildren(ICacheEntry $entry) {
$parentIds = [$entry->getId()];
$queue = [$entry->getId()];
+ $deletedIds = [];
+ $deletedPaths = [];
// we walk depth first through the file tree, removing all filecache_extended attributes while we walk
// and collecting all folder ids to later use to delete the filecache entries
@@ -591,6 +593,12 @@ class Cache implements ICache {
$childIds = array_map(function (ICacheEntry $cacheEntry) {
return $cacheEntry->getId();
}, $children);
+ $childPaths = array_map(function (ICacheEntry $cacheEntry) {
+ return $cacheEntry->getPath();
+ }, $children);
+
+ $deletedIds = array_merge($deletedIds, $childIds);
+ $deletedPaths = array_merge($deletedPaths, $childPaths);
$query = $this->getQueryBuilder();
$query->delete('filecache_extended')
@@ -619,6 +627,16 @@ class Cache implements ICache {
$query->setParameter('parentIds', $parentIdChunk, IQueryBuilder::PARAM_INT_ARRAY);
$query->execute();
}
+
+ foreach (array_combine($deletedIds, $deletedPaths) as $fileId => $filePath) {
+ $cacheEntryRemovedEvent = new CacheEntryRemovedEvent(
+ $this->storage,
+ $filePath,
+ $fileId,
+ $this->getNumericStorageId()
+ );
+ $this->eventDispatcher->dispatchTyped($cacheEntryRemovedEvent);
+ }
}
/**
diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php
index fe677c5ea52..a60b39823c5 100644
--- a/lib/private/Files/Config/UserMountCache.php
+++ b/lib/private/Files/Config/UserMountCache.php
@@ -130,18 +130,25 @@ class UserMountCache implements IUserMountCache {
$changedMounts = $this->findChangedMounts($newMounts, $cachedMounts);
- foreach ($addedMounts as $mount) {
- $this->addToCache($mount);
- /** @psalm-suppress InvalidArgument */
- $this->mountsForUsers[$user->getUID()][] = $mount;
- }
- foreach ($removedMounts as $mount) {
- $this->removeFromCache($mount);
- $index = array_search($mount, $this->mountsForUsers[$user->getUID()]);
- unset($this->mountsForUsers[$user->getUID()][$index]);
- }
- foreach ($changedMounts as $mount) {
- $this->updateCachedMount($mount);
+ $this->connection->beginTransaction();
+ try {
+ foreach ($addedMounts as $mount) {
+ $this->addToCache($mount);
+ /** @psalm-suppress InvalidArgument */
+ $this->mountsForUsers[$user->getUID()][] = $mount;
+ }
+ foreach ($removedMounts as $mount) {
+ $this->removeFromCache($mount);
+ $index = array_search($mount, $this->mountsForUsers[$user->getUID()]);
+ unset($this->mountsForUsers[$user->getUID()][$index]);
+ }
+ foreach ($changedMounts as $mount) {
+ $this->updateCachedMount($mount);
+ }
+ $this->connection->commit();
+ } catch (\Throwable $e) {
+ $this->connection->rollBack();
+ throw $e;
}
$this->eventLogger->end('fs:setup:user:register');
}
diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php
index 90aed642a2d..2c376fe5885 100644
--- a/lib/private/Files/Node/Folder.php
+++ b/lib/private/Files/Node/Folder.php
@@ -1,6 +1,7 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @copyright Copyright (c) 2022 Informatyka Boguslawski sp. z o.o. sp.k., http://www.ib.pl/
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
@@ -68,7 +69,7 @@ class Folder extends Node implements \OCP\Files\Folder {
public function getFullPath($path) {
$path = $this->normalizePath($path);
if (!$this->isValidPath($path)) {
- throw new NotPermittedException('Invalid path');
+ throw new NotPermittedException('Invalid path "' . $path . '"');
}
return $this->path . $path;
}
@@ -163,14 +164,14 @@ class Folder extends Node implements \OCP\Files\Folder {
$nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
if (!$this->view->mkdir($fullPath)) {
- throw new NotPermittedException('Could not create folder');
+ throw new NotPermittedException('Could not create folder "' . $fullPath . '"');
}
$parent = dirname($fullPath) === $this->getPath() ? $this : null;
$node = new Folder($this->root, $this->view, $fullPath, null, $parent);
$this->sendHooks(['postWrite', 'postCreate'], [$node]);
return $node;
} else {
- throw new NotPermittedException('No create permission for folder');
+ throw new NotPermittedException('No create permission for folder "' . $path . '"');
}
}
@@ -194,13 +195,13 @@ class Folder extends Node implements \OCP\Files\Folder {
$result = $this->view->touch($fullPath);
}
if ($result === false) {
- throw new NotPermittedException('Could not create path');
+ throw new NotPermittedException('Could not create path "' . $fullPath . '"');
}
$node = new File($this->root, $this->view, $fullPath, null, $this);
$this->sendHooks(['postWrite', 'postCreate'], [$node]);
return $node;
}
- throw new NotPermittedException('No create permission for path');
+ throw new NotPermittedException('No create permission for path "' . $path . '"');
}
private function queryFromOperator(ISearchOperator $operator, string $uid = null): ISearchQuery {
@@ -230,7 +231,7 @@ class Folder extends Node implements \OCP\Files\Folder {
$limitToHome = $query->limitToHome();
if ($limitToHome && count(explode('/', $this->path)) !== 3) {
- throw new \InvalidArgumentException('searching by owner is only allows on the users home folder');
+ throw new \InvalidArgumentException('searching by owner is only allowed in the users home folder');
}
$rootLength = strlen($this->path);
@@ -392,7 +393,7 @@ class Folder extends Node implements \OCP\Files\Folder {
$nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
$this->sendHooks(['postDelete'], [$nonExisting]);
} else {
- throw new NotPermittedException('No delete permission for path');
+ throw new NotPermittedException('No delete permission for path "' . $this->path . '"');
}
}
diff --git a/lib/private/Files/Node/LazyUserFolder.php b/lib/private/Files/Node/LazyUserFolder.php
index c85a356ddd3..81009532dbf 100644
--- a/lib/private/Files/Node/LazyUserFolder.php
+++ b/lib/private/Files/Node/LazyUserFolder.php
@@ -26,6 +26,7 @@ namespace OC\Files\Node;
use OCP\Files\FileInfo;
use OCP\Constants;
use OCP\Files\IRootFolder;
+use OCP\Files\Mount\IMountManager;
use OCP\Files\NotFoundException;
use OCP\IUser;
@@ -33,10 +34,12 @@ class LazyUserFolder extends LazyFolder {
private IRootFolder $root;
private IUser $user;
private string $path;
+ private IMountManager $mountManager;
- public function __construct(IRootFolder $rootFolder, IUser $user) {
+ public function __construct(IRootFolder $rootFolder, IUser $user, IMountManager $mountManager) {
$this->root = $rootFolder;
$this->user = $user;
+ $this->mountManager = $mountManager;
$this->path = '/' . $user->getUID() . '/files';
parent::__construct(function () use ($user) {
try {
@@ -61,9 +64,20 @@ class LazyUserFolder extends LazyFolder {
/**
* @param int $id
- * @return \OC\Files\Node\Node[]
+ * @return \OCP\Files\Node[]
*/
public function getById($id) {
return $this->root->getByIdInPath((int)$id, $this->getPath());
}
+
+ public function getMountPoint() {
+ if ($this->folder !== null) {
+ return $this->folder->getMountPoint();
+ }
+ $mountPoint = $this->mountManager->find('/' . $this->user->getUID());
+ if (is_null($mountPoint)) {
+ throw new \Exception("No mountpoint for user folder");
+ }
+ return $mountPoint;
+ }
}
diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php
index 29cdbb987c3..e9fb14e5364 100644
--- a/lib/private/Files/Node/Root.php
+++ b/lib/private/Files/Node/Root.php
@@ -395,7 +395,7 @@ class Root extends Folder implements IRootFolder {
$folder = $this->newFolder('/' . $userId . '/files');
}
} else {
- $folder = new LazyUserFolder($this, $userObject);
+ $folder = new LazyUserFolder($this, $userObject, $this->mountManager);
}
$this->userFolderCache->set($userId, $folder);
diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
index d0c5bd14b38..4ca00cf6a16 100644
--- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php
+++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
@@ -29,6 +29,8 @@
*/
namespace OC\Files\ObjectStore;
+use Aws\S3\Exception\S3Exception;
+use Aws\S3\Exception\S3MultipartUploadException;
use Icewind\Streams\CallbackWrapper;
use Icewind\Streams\CountWrapper;
use Icewind\Streams\IteratorDirectory;
@@ -37,11 +39,14 @@ use OC\Files\Cache\CacheEntry;
use OC\Files\Storage\PolyFill\CopyDirectory;
use OCP\Files\Cache\ICacheEntry;
use OCP\Files\FileInfo;
+use OCP\Files\GenericFileException;
use OCP\Files\NotFoundException;
use OCP\Files\ObjectStore\IObjectStore;
+use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload;
+use OCP\Files\Storage\IChunkedFileWrite;
use OCP\Files\Storage\IStorage;
-class ObjectStoreStorage extends \OC\Files\Storage\Common {
+class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFileWrite {
use CopyDirectory;
/**
@@ -91,7 +96,6 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common {
public function mkdir($path) {
$path = $this->normalizePath($path);
-
if ($this->file_exists($path)) {
return false;
}
@@ -627,4 +631,72 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common {
throw $e;
}
}
+
+ public function startChunkedWrite(string $targetPath): string {
+ if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) {
+ throw new GenericFileException('Object store does not support multipart upload');
+ }
+ $cacheEntry = $this->getCache()->get($targetPath);
+ $urn = $this->getURN($cacheEntry->getId());
+ return $this->objectStore->initiateMultipartUpload($urn);
+ }
+
+ /**
+ *
+ * @throws GenericFileException
+ */
+ public function putChunkedWritePart(string $targetPath, string $writeToken, string $chunkId, $data, $size = null): ?array {
+ if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) {
+ throw new GenericFileException('Object store does not support multipart upload');
+ }
+ $cacheEntry = $this->getCache()->get($targetPath);
+ $urn = $this->getURN($cacheEntry->getId());
+
+ $result = $this->objectStore->uploadMultipartPart($urn, $writeToken, (int)$chunkId, $data, $size);
+
+ $parts[$chunkId] = [
+ 'PartNumber' => $chunkId,
+ 'ETag' => trim($result->get('ETag'), '"')
+ ];
+ return $parts[$chunkId];
+ }
+
+ public function completeChunkedWrite(string $targetPath, string $writeToken): int {
+ if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) {
+ throw new GenericFileException('Object store does not support multipart upload');
+ }
+ $cacheEntry = $this->getCache()->get($targetPath);
+ $urn = $this->getURN($cacheEntry->getId());
+ $parts = $this->objectStore->getMultipartUploads($urn, $writeToken);
+ $sortedParts = array_values($parts);
+ sort($sortedParts);
+ try {
+ $size = $this->objectStore->completeMultipartUpload($urn, $writeToken, $sortedParts);
+ $stat = $this->stat($targetPath);
+ $mtime = time();
+ if (is_array($stat)) {
+ $stat['size'] = $size;
+ $stat['mtime'] = $mtime;
+ $stat['mimetype'] = $this->getMimeType($targetPath);
+ $this->getCache()->update($stat['fileid'], $stat);
+ }
+ } catch (S3MultipartUploadException | S3Exception $e) {
+ $this->objectStore->abortMultipartUpload($urn, $writeToken);
+ $this->logger->logException($e, [
+ 'app' => 'objectstore',
+ 'message' => 'Could not compete multipart upload ' . $urn. ' with uploadId ' . $writeToken
+ ]);
+ throw new GenericFileException('Could not write chunked file');
+ }
+ return $size;
+ }
+
+ public function cancelChunkedWrite(string $targetPath, string $writeToken): void {
+ if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) {
+ throw new GenericFileException('Object store does not support multipart upload');
+ }
+ $cacheEntry = $this->getCache()->get($targetPath);
+ $urn = $this->getURN($cacheEntry->getId());
+ $this->objectStore->abortMultipartUpload($urn, $writeToken);
+ }
}
diff --git a/lib/private/Files/ObjectStore/S3.php b/lib/private/Files/ObjectStore/S3.php
index 6492145fb63..ebc8886f12d 100644
--- a/lib/private/Files/ObjectStore/S3.php
+++ b/lib/private/Files/ObjectStore/S3.php
@@ -23,9 +23,12 @@
*/
namespace OC\Files\ObjectStore;
+use Aws\Result;
+use Exception;
use OCP\Files\ObjectStore\IObjectStore;
+use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload;
-class S3 implements IObjectStore {
+class S3 implements IObjectStore, IObjectStoreMultiPartUpload {
use S3ConnectionTrait;
use S3ObjectTrait;
@@ -41,4 +44,59 @@ class S3 implements IObjectStore {
public function getStorageId() {
return $this->id;
}
+
+ public function initiateMultipartUpload(string $urn): string {
+ $upload = $this->getConnection()->createMultipartUpload([
+ 'Bucket' => $this->bucket,
+ 'Key' => $urn,
+ ]);
+ $uploadId = $upload->get('UploadId');
+ if ($uploadId === null) {
+ throw new Exception('No upload id returned');
+ }
+ return (string)$uploadId;
+ }
+
+ public function uploadMultipartPart(string $urn, string $uploadId, int $partId, $stream, $size): Result {
+ return $this->getConnection()->uploadPart([
+ 'Body' => $stream,
+ 'Bucket' => $this->bucket,
+ 'Key' => $urn,
+ 'ContentLength' => $size,
+ 'PartNumber' => $partId,
+ 'UploadId' => $uploadId,
+ ]);
+ }
+
+ public function getMultipartUploads(string $urn, string $uploadId): array {
+ $parts = $this->getConnection()->listParts([
+ 'Bucket' => $this->bucket,
+ 'Key' => $urn,
+ 'UploadId' => $uploadId,
+ 'MaxParts' => 10000
+ ]);
+ return $parts->get('Parts') ?? [];
+ }
+
+ public function completeMultipartUpload(string $urn, string $uploadId, array $result): int {
+ $this->getConnection()->completeMultipartUpload([
+ 'Bucket' => $this->bucket,
+ 'Key' => $urn,
+ 'UploadId' => $uploadId,
+ 'MultipartUpload' => ['Parts' => $result],
+ ]);
+ $stat = $this->getConnection()->headObject([
+ 'Bucket' => $this->bucket,
+ 'Key' => $urn,
+ ]);
+ return (int)$stat->get('ContentLength');
+ }
+
+ public function abortMultipartUpload($urn, $uploadId): void {
+ $this->getConnection()->abortMultipartUpload([
+ 'Bucket' => $this->bucket,
+ 'Key' => $urn,
+ 'UploadId' => $uploadId
+ ]);
+ }
}
diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php
index 456f804ee56..1bd131303e3 100644
--- a/lib/private/Files/View.php
+++ b/lib/private/Files/View.php
@@ -164,7 +164,7 @@ class View {
* get path relative to the root of the view
*
* @param string $path
- * @return string
+ * @return ?string
*/
public function getRelativePath($path) {
$this->assertPathLength($path);
@@ -1241,7 +1241,7 @@ class View {
* get the path relative to the default root for hook usage
*
* @param string $path
- * @return string
+ * @return ?string
*/
private function getHookPath($path) {
if (!Filesystem::getView()) {
diff --git a/lib/private/Group/Group.php b/lib/private/Group/Group.php
index ae70a611e4e..cca179bfe19 100644
--- a/lib/private/Group/Group.php
+++ b/lib/private/Group/Group.php
@@ -38,6 +38,7 @@ use OCP\Group\Backend\IGetDisplayNameBackend;
use OCP\Group\Backend\IHideFromCollaborationBackend;
use OCP\Group\Backend\INamedBackend;
use OCP\Group\Backend\ISetDisplayNameBackend;
+use OCP\Group\Events\BeforeGroupChangedEvent;
use OCP\Group\Events\GroupChangedEvent;
use OCP\GroupInterface;
use OCP\IGroup;
@@ -109,6 +110,7 @@ class Group implements IGroup {
public function setDisplayName(string $displayName): bool {
$displayName = trim($displayName);
if ($displayName !== '') {
+ $this->dispatcher->dispatch(new BeforeGroupChangedEvent($this, 'displayName', $displayName, $this->displayName));
foreach ($this->backends as $backend) {
if (($backend instanceof ISetDisplayNameBackend)
&& $backend->setDisplayName($this->gid, $displayName)) {
diff --git a/lib/private/Log/ExceptionSerializer.php b/lib/private/Log/ExceptionSerializer.php
index 5f806be0ae5..78843de7206 100644
--- a/lib/private/Log/ExceptionSerializer.php
+++ b/lib/private/Log/ExceptionSerializer.php
@@ -100,6 +100,16 @@ class ExceptionSerializer {
// Preview providers, don't log big data strings
'imagecreatefromstring',
+
+ // text: PublicSessionController, SessionController and ApiService
+ 'create',
+ 'close',
+ 'push',
+ 'sync',
+ 'updateSession',
+ 'mention',
+ 'loginSessionUser',
+
];
/** @var SystemConfig */
diff --git a/lib/private/Memcache/Redis.php b/lib/private/Memcache/Redis.php
index f4094e0bef6..b6f96fffba4 100644
--- a/lib/private/Memcache/Redis.php
+++ b/lib/private/Memcache/Redis.php
@@ -74,7 +74,7 @@ class Redis extends Cache implements IMemcacheTTL {
}
public function remove($key) {
- if ($this->getCache()->del($this->getPrefix() . $key)) {
+ if ($this->getCache()->unlink($this->getPrefix() . $key)) {
return true;
} else {
return false;
@@ -170,7 +170,7 @@ class Redis extends Cache implements IMemcacheTTL {
$this->getCache()->watch($this->getPrefix() . $key);
if ($this->get($key) === $old) {
$result = $this->getCache()->multi()
- ->del($this->getPrefix() . $key)
+ ->unlink($this->getPrefix() . $key)
->exec();
return $result !== false;
}
diff --git a/lib/private/Preview/Imaginary.php b/lib/private/Preview/Imaginary.php
index 5d559b65f00..ca46383e58b 100644
--- a/lib/private/Preview/Imaginary.php
+++ b/lib/private/Preview/Imaginary.php
@@ -82,8 +82,14 @@ class Imaginary extends ProviderV2 {
$httpClient = $this->service->newClient();
$convert = false;
+ $autorotate = true;
switch ($file->getMimeType()) {
+ case 'image/heic':
+ // Autorotate seems to be broken for Heic so disable for that
+ $autorotate = false;
+ $mimeType = 'jpeg';
+ break;
case 'image/gif':
case 'image/png':
$mimeType = 'png';
@@ -92,50 +98,43 @@ class Imaginary extends ProviderV2 {
case 'application/pdf':
case 'application/illustrator':
$convert = true;
+ // Converted files do not need to be autorotated
+ $autorotate = false;
+ $mimeType = 'png';
break;
default:
$mimeType = 'jpeg';
}
+
+ $operations = [];
if ($convert) {
- $operations = [
- [
- 'operation' => 'convert',
- 'params' => [
- 'type' => 'png',
- ]
- ],
- [
- 'operation' => ($crop ? 'smartcrop' : 'fit'),
- 'params' => [
- 'width' => $maxX,
- 'height' => $maxY,
- 'type' => 'png',
- 'norotation' => 'true',
- ]
+ $operations[] = [
+ 'operation' => 'convert',
+ 'params' => [
+ 'type' => $mimeType,
]
];
- } else {
- $quality = $this->config->getAppValue('preview', 'jpeg_quality', '80');
-
- $operations = [
- [
- 'operation' => 'autorotate',
- ],
- [
- 'operation' => ($crop ? 'smartcrop' : 'fit'),
- 'params' => [
- 'width' => $maxX,
- 'height' => $maxY,
- 'stripmeta' => 'true',
- 'type' => $mimeType,
- 'norotation' => 'true',
- 'quality' => $quality,
- ]
- ]
+ } elseif ($autorotate) {
+ $operations[] = [
+ 'operation' => 'autorotate',
];
}
+ $quality = $this->config->getAppValue('preview', 'jpeg_quality', '80');
+
+ $operations[] = [
+ 'operation' => ($crop ? 'smartcrop' : 'fit'),
+ 'params' => [
+ 'width' => $maxX,
+ 'height' => $maxY,
+ 'stripmeta' => 'true',
+ 'type' => $mimeType,
+ 'norotation' => 'true',
+ 'quality' => $quality,
+ ]
+ ];
+
try {
$response = $httpClient->post(
$imaginaryUrl . '/pipeline', [
diff --git a/lib/private/Preview/Movie.php b/lib/private/Preview/Movie.php
index 486c301d987..13d868cd583 100644
--- a/lib/private/Preview/Movie.php
+++ b/lib/private/Preview/Movie.php
@@ -125,23 +125,30 @@ class Movie extends ProviderV2 {
$binaryType = substr(strrchr($this->binary, '/'), 1);
if ($binaryType === 'avconv') {
- $cmd = $this->binary . ' -y -ss ' . escapeshellarg((string)$second) .
- ' -i ' . escapeshellarg($absPath) .
- ' -an -f mjpeg -vframes 1 -vsync 1 ' . escapeshellarg($tmpPath) .
- ' 2>&1';
+ $cmd = [$this->binary, '-y', '-ss', (string)$second,
+ '-i', $absPath,
+ '-an', '-f', 'mjpeg', '-vframes', '1', '-vsync', '1',
+ $tmpPath];
} elseif ($binaryType === 'ffmpeg') {
- $cmd = $this->binary . ' -y -ss ' . escapeshellarg((string)$second) .
- ' -i ' . escapeshellarg($absPath) .
- ' -f mjpeg -vframes 1' .
- ' ' . escapeshellarg($tmpPath) .
- ' 2>&1';
+ $cmd = [$this->binary, '-y', '-ss', (string)$second,
+ '-i', $absPath,
+ '-f', 'mjpeg', '-vframes', '1',
+ $tmpPath];
} else {
// Not supported
unlink($tmpPath);
return null;
}
- exec($cmd, $output, $returnCode);
+ $proc = proc_open($cmd, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
+ $returnCode = -1;
+ $output = "";
+ if (is_resource($proc)) {
+ $stdout = trim(stream_get_contents($pipes[1]));
+ $stderr = trim(stream_get_contents($pipes[2]));
+ $returnCode = proc_close($proc);
+ $output = $stdout . $stderr;
+ }
if ($returnCode === 0) {
$image = new \OCP\Image();
diff --git a/lib/private/Repair/NC21/ValidatePhoneNumber.php b/lib/private/Repair/NC21/ValidatePhoneNumber.php
index f9c3c5952bf..b3534dbeae8 100644
--- a/lib/private/Repair/NC21/ValidatePhoneNumber.php
+++ b/lib/private/Repair/NC21/ValidatePhoneNumber.php
@@ -55,7 +55,8 @@ class ValidatePhoneNumber implements IRepairStep {
public function run(IOutput $output): void {
if ($this->config->getSystemValueString('default_phone_region', '') === '') {
- throw new \Exception('Can not validate phone numbers without `default_phone_region` being set in the config file');
+ $output->warning('Can not validate phone numbers without `default_phone_region` being set in the config file');
+ return;
}
$numUpdated = 0;
diff --git a/lib/private/Repair/RemoveLinkShares.php b/lib/private/Repair/RemoveLinkShares.php
index e1ce78cdbf3..71eead1053b 100644
--- a/lib/private/Repair/RemoveLinkShares.php
+++ b/lib/private/Repair/RemoveLinkShares.php
@@ -126,7 +126,7 @@ class RemoveLinkShares implements IRepairStep {
$query = $this->connection->getQueryBuilder();
$query->select($query->func()->count('*', 'total'))
->from('share')
- ->where($query->expr()->in('id', $query->createFunction('(' . $subQuery->getSQL() . ')')));
+ ->where($query->expr()->in('id', $query->createFunction($subQuery->getSQL())));
$result = $query->execute();
$data = $result->fetch();
diff --git a/lib/private/Server.php b/lib/private/Server.php
index 35f63686457..f1e96170886 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -152,6 +152,7 @@ use OC\SystemTag\ManagerFactory as SystemTagManagerFactory;
use OC\Tagging\TagMapper;
use OC\Talk\Broker;
use OC\Template\JSCombiner;
+use OC\Translation\TranslationManager;
use OC\User\DisplayNameCache;
use OC\User\Listeners\BeforeUserDeletedListener;
use OC\User\Listeners\UserChangedListener;
@@ -162,6 +163,7 @@ use OCA\Theming\Util;
use OCP\Accounts\IAccountManager;
use OCP\App\IAppManager;
use OCP\Authentication\LoginCredentials\IStore;
+use OCP\Authentication\Token\IProvider as OCPIProvider;
use OCP\BackgroundJob\IJobList;
use OCP\Collaboration\AutoComplete\IManager;
use OCP\Collaboration\Reference\IReferenceManager;
@@ -247,6 +249,7 @@ use OCP\Share\IShareHelper;
use OCP\SystemTag\ISystemTagManager;
use OCP\SystemTag\ISystemTagObjectMapper;
use OCP\Talk\IBroker;
+use OCP\Translation\ITranslationManager;
use OCP\User\Events\BeforePasswordUpdatedEvent;
use OCP\User\Events\BeforeUserDeletedEvent;
use OCP\User\Events\BeforeUserLoggedInEvent;
@@ -548,6 +551,7 @@ class Server extends ServerContainer implements IServerContainer {
});
$this->registerAlias(IStore::class, Store::class);
$this->registerAlias(IProvider::class, Authentication\Token\Manager::class);
+ $this->registerAlias(OCPIProvider::class, Authentication\Token\Manager::class);
$this->registerService(\OC\User\Session::class, function (Server $c) {
$manager = $c->get(IUserManager::class);
@@ -926,6 +930,7 @@ class Server extends ServerContainer implements IServerContainer {
$c->get(IGroupManager::class),
$c->get(ICacheFactory::class),
$c->get(SymfonyAdapter::class),
+ $c->get(IEventDispatcher::class),
$c->get(LoggerInterface::class)
);
});
@@ -1385,6 +1390,7 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerDeprecatedAlias('ControllerMethodReflector', \OCP\AppFramework\Utility\IControllerMethodReflector::class);
$this->registerAlias(\OCP\AppFramework\Utility\ITimeFactory::class, \OC\AppFramework\Utility\TimeFactory::class);
+ $this->registerAlias(\Psr\Clock\ClockInterface::class, \OCP\AppFramework\Utility\ITimeFactory::class);
/** @deprecated 19.0.0 */
$this->registerDeprecatedAlias('TimeFactory', \OCP\AppFramework\Utility\ITimeFactory::class);
@@ -1453,6 +1459,8 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerAlias(\OCP\Share\IPublicShareTemplateFactory::class, \OC\Share20\PublicShareTemplateFactory::class);
+ $this->registerAlias(ITranslationManager::class, TranslationManager::class);
+
$this->connectDispatcher();
}
diff --git a/lib/private/Setup/AbstractDatabase.php b/lib/private/Setup/AbstractDatabase.php
index 94719a742e2..9ec4137cdef 100644
--- a/lib/private/Setup/AbstractDatabase.php
+++ b/lib/private/Setup/AbstractDatabase.php
@@ -57,6 +57,8 @@ abstract class AbstractDatabase {
protected $logger;
/** @var ISecureRandom */
protected $random;
+ /** @var bool */
+ protected $tryCreateDbUser;
public function __construct(IL10N $trans, SystemConfig $config, LoggerInterface $logger, ISecureRandom $random) {
$this->trans = $trans;
@@ -88,6 +90,10 @@ abstract class AbstractDatabase {
$dbPort = !empty($config['dbport']) ? $config['dbport'] : '';
$dbTablePrefix = isset($config['dbtableprefix']) ? $config['dbtableprefix'] : 'oc_';
+ $createUserConfig = $this->config->getValue("setup_create_db_user", true);
+ // accept `false` both as bool and string, since setting config values from env will result in a string
+ $this->tryCreateDbUser = $createUserConfig !== false && $createUserConfig !== "false";
+
$this->config->setValues([
'dbname' => $dbName,
'dbhost' => $dbHost,
diff --git a/lib/private/Setup/MySQL.php b/lib/private/Setup/MySQL.php
index caa73edccec..50f566728a9 100644
--- a/lib/private/Setup/MySQL.php
+++ b/lib/private/Setup/MySQL.php
@@ -49,7 +49,14 @@ class MySQL extends AbstractDatabase {
$connection = $this->connect(['dbname' => null]);
}
- $this->createSpecificUser($username, new ConnectionAdapter($connection));
+ if ($this->tryCreateDbUser) {
+ $this->createSpecificUser($username, new ConnectionAdapter($connection));
+ }
+
+ $this->config->setValues([
+ 'dbuser' => $this->dbUser,
+ 'dbpassword' => $this->dbPassword,
+ ]);
//create the database
$this->createDatabase($connection);
@@ -147,8 +154,7 @@ class MySQL extends AbstractDatabase {
. $this->random->generate(2, ISecureRandom::CHAR_UPPER)
. $this->random->generate(2, ISecureRandom::CHAR_LOWER)
. $this->random->generate(2, ISecureRandom::CHAR_DIGITS)
- . $this->random->generate(2, $saveSymbols)
- ;
+ . $this->random->generate(2, $saveSymbols);
$this->dbPassword = str_shuffle($password);
try {
@@ -196,10 +202,5 @@ class MySQL extends AbstractDatabase {
$this->dbUser = $rootUser;
$this->dbPassword = $rootPassword;
}
-
- $this->config->setValues([
- 'dbuser' => $this->dbUser,
- 'dbpassword' => $this->dbPassword,
- ]);
}
}
diff --git a/lib/private/Setup/PostgreSQL.php b/lib/private/Setup/PostgreSQL.php
index af816c7ad04..490cbba69a9 100644
--- a/lib/private/Setup/PostgreSQL.php
+++ b/lib/private/Setup/PostgreSQL.php
@@ -45,42 +45,44 @@ class PostgreSQL extends AbstractDatabase {
$connection = $this->connect([
'dbname' => 'postgres'
]);
- //check for roles creation rights in postgresql
- $builder = $connection->getQueryBuilder();
- $builder->automaticTablePrefix(false);
- $query = $builder
- ->select('rolname')
- ->from('pg_roles')
- ->where($builder->expr()->eq('rolcreaterole', new Literal('TRUE')))
- ->andWhere($builder->expr()->eq('rolname', $builder->createNamedParameter($this->dbUser)));
-
- try {
- $result = $query->execute();
- $canCreateRoles = $result->rowCount() > 0;
- } catch (DatabaseException $e) {
- $canCreateRoles = false;
- }
-
- if ($canCreateRoles) {
- $connectionMainDatabase = $this->connect();
- //use the admin login data for the new database user
-
- //add prefix to the postgresql user name to prevent collisions
- $this->dbUser = 'oc_' . strtolower($username);
- //create a new password so we don't need to store the admin config in the config file
- $this->dbPassword = \OC::$server->getSecureRandom()->generate(30, ISecureRandom::CHAR_ALPHANUMERIC);
-
- $this->createDBUser($connection);
-
- // Go to the main database and grant create on the public schema
- // The code below is implemented to make installing possible with PostgreSQL version 15:
- // https://www.postgresql.org/docs/release/15.0/
- // From the release notes: For new databases having no need to defend against insider threats, granting CREATE permission will yield the behavior of prior releases
- // Therefore we assume that the database is only used by one user/service which is Nextcloud
- // Additional services should get installed in a separate database in order to stay secure
- // Also see https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PATTERNS
- $connectionMainDatabase->executeQuery('GRANT CREATE ON SCHEMA public TO ' . addslashes($this->dbUser));
- $connectionMainDatabase->close();
+ if ($this->tryCreateDbUser) {
+ //check for roles creation rights in postgresql
+ $builder = $connection->getQueryBuilder();
+ $builder->automaticTablePrefix(false);
+ $query = $builder
+ ->select('rolname')
+ ->from('pg_roles')
+ ->where($builder->expr()->eq('rolcreaterole', new Literal('TRUE')))
+ ->andWhere($builder->expr()->eq('rolname', $builder->createNamedParameter($this->dbUser)));
+
+ try {
+ $result = $query->execute();
+ $canCreateRoles = $result->rowCount() > 0;
+ } catch (DatabaseException $e) {
+ $canCreateRoles = false;
+ }
+
+ if ($canCreateRoles) {
+ $connectionMainDatabase = $this->connect();
+ //use the admin login data for the new database user
+
+ //add prefix to the postgresql user name to prevent collisions
+ $this->dbUser = 'oc_' . strtolower($username);
+ //create a new password so we don't need to store the admin config in the config file
+ $this->dbPassword = \OC::$server->getSecureRandom()->generate(30, ISecureRandom::CHAR_ALPHANUMERIC);
+
+ $this->createDBUser($connection);
+
+ // Go to the main database and grant create on the public schema
+ // The code below is implemented to make installing possible with PostgreSQL version 15:
+ // https://www.postgresql.org/docs/release/15.0/
+ // From the release notes: For new databases having no need to defend against insider threats, granting CREATE permission will yield the behavior of prior releases
+ // Therefore we assume that the database is only used by one user/service which is Nextcloud
+ // Additional services should get installed in a separate database in order to stay secure
+ // Also see https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PATTERNS
+ $connectionMainDatabase->executeQuery('GRANT CREATE ON SCHEMA public TO "' . addslashes($this->dbUser) . '"');
+ $connectionMainDatabase->close();
+ }
}
$this->config->setValues([
@@ -120,7 +122,7 @@ class PostgreSQL extends AbstractDatabase {
private function createDatabase(Connection $connection) {
if (!$this->databaseExists($connection)) {
//The database does not exists... let's create it
- $query = $connection->prepare("CREATE DATABASE " . addslashes($this->dbName) . " OWNER " . addslashes($this->dbUser));
+ $query = $connection->prepare("CREATE DATABASE " . addslashes($this->dbName) . " OWNER \"" . addslashes($this->dbUser) . '"');
try {
$query->execute();
} catch (DatabaseException $e) {
@@ -170,10 +172,10 @@ class PostgreSQL extends AbstractDatabase {
}
// create the user
- $query = $connection->prepare("CREATE USER " . addslashes($this->dbUser) . " CREATEDB PASSWORD '" . addslashes($this->dbPassword) . "'");
+ $query = $connection->prepare("CREATE USER \"" . addslashes($this->dbUser) . "\" CREATEDB PASSWORD '" . addslashes($this->dbPassword) . "'");
$query->execute();
if ($this->databaseExists($connection)) {
- $query = $connection->prepare('GRANT CONNECT ON DATABASE ' . addslashes($this->dbName) . ' TO '.addslashes($this->dbUser));
+ $query = $connection->prepare('GRANT CONNECT ON DATABASE ' . addslashes($this->dbName) . ' TO "' . addslashes($this->dbUser) . '"');
$query->execute();
}
} catch (DatabaseException $e) {
diff --git a/lib/private/Share/Constants.php b/lib/private/Share/Constants.php
index 03c4c2ba828..0c8fad17e07 100644
--- a/lib/private/Share/Constants.php
+++ b/lib/private/Share/Constants.php
@@ -74,6 +74,8 @@ class Constants {
public const SHARE_TYPE_DECK = 12;
// const SHARE_TYPE_DECK_USER = 13; // Internal type used by DeckShareProvider
+ // Note to developers: Do not add new share types here
+
public const FORMAT_NONE = -1;
public const FORMAT_STATUSES = -2;
public const FORMAT_SOURCES = -3; // ToDo Check if it is still in use otherwise remove it
diff --git a/lib/private/Share/Share.php b/lib/private/Share/Share.php
index 487625affc1..dec71f792fd 100644
--- a/lib/private/Share/Share.php
+++ b/lib/private/Share/Share.php
@@ -63,7 +63,7 @@ class Share extends Constants {
* Apps are required to handle permissions on their own, this class only
* stores and manages the permissions of shares
*
- * @see lib/public/constants.php
+ * @see lib/public/Constants.php
*/
/**
diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php
index 7fd99545668..f84ed1671ba 100644
--- a/lib/private/Share20/Manager.php
+++ b/lib/private/Share20/Manager.php
@@ -244,6 +244,7 @@ class Manager implements IManager {
}
} elseif ($share->getShareType() === IShare::TYPE_ROOM) {
} elseif ($share->getShareType() === IShare::TYPE_DECK) {
+ } elseif ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
} else {
// We cannot handle other types yet
throw new \InvalidArgumentException('unknown share type');
diff --git a/lib/private/Share20/ProviderFactory.php b/lib/private/Share20/ProviderFactory.php
index 16f9a17ee42..6abfb372a4d 100644
--- a/lib/private/Share20/ProviderFactory.php
+++ b/lib/private/Share20/ProviderFactory.php
@@ -340,6 +340,8 @@ class ProviderFactory implements IProviderFactory {
$provider = $this->getRoomShareProvider();
} elseif ($shareType === IShare::TYPE_DECK) {
$provider = $this->getProvider('deck');
+ } elseif ($shareType === IShare::TYPE_SCIENCEMESH) {
+ $provider = $this->getProvider('sciencemesh');
}
diff --git a/lib/private/SystemTag/SystemTagManager.php b/lib/private/SystemTag/SystemTagManager.php
index 4524aeaf7bc..79c5adcf450 100644
--- a/lib/private/SystemTag/SystemTagManager.php
+++ b/lib/private/SystemTag/SystemTagManager.php
@@ -193,10 +193,12 @@ class SystemTagManager implements ISystemTagManager {
* {@inheritdoc}
*/
public function createTag(string $tagName, bool $userVisible, bool $userAssignable): ISystemTag {
+ // Length of name column is 64
+ $truncatedTagName = substr($tagName, 0, 64);
$query = $this->connection->getQueryBuilder();
$query->insert(self::TAG_TABLE)
->values([
- 'name' => $query->createNamedParameter($tagName),
+ 'name' => $query->createNamedParameter($truncatedTagName),
'visibility' => $query->createNamedParameter($userVisible ? 1 : 0),
'editable' => $query->createNamedParameter($userAssignable ? 1 : 0),
]);
@@ -205,7 +207,7 @@ class SystemTagManager implements ISystemTagManager {
$query->execute();
} catch (UniqueConstraintViolationException $e) {
throw new TagAlreadyExistsException(
- 'Tag ("' . $tagName . '", '. $userVisible . ', ' . $userAssignable . ') already exists',
+ 'Tag ("' . $truncatedTagName . '", '. $userVisible . ', ' . $userAssignable . ') already exists',
0,
$e
);
@@ -215,7 +217,7 @@ class SystemTagManager implements ISystemTagManager {
$tag = new SystemTag(
(string)$tagId,
- $tagName,
+ $truncatedTagName,
$userVisible,
$userAssignable
);
diff --git a/lib/private/SystemTag/SystemTagObjectMapper.php b/lib/private/SystemTag/SystemTagObjectMapper.php
index 5a09a1754f2..b61a81a1fa7 100644
--- a/lib/private/SystemTag/SystemTagObjectMapper.php
+++ b/lib/private/SystemTag/SystemTagObjectMapper.php
@@ -77,23 +77,25 @@ class SystemTagObjectMapper implements ISystemTagObjectMapper {
->from(self::RELATION_TABLE)
->where($query->expr()->in('objectid', $query->createParameter('objectids')))
->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype')))
- ->setParameter('objectids', $objIds, IQueryBuilder::PARAM_STR_ARRAY)
->setParameter('objecttype', $objectType)
->addOrderBy('objectid', 'ASC')
->addOrderBy('systemtagid', 'ASC');
-
+ $chunks = array_chunk($objIds, 900, false);
$mapping = [];
foreach ($objIds as $objId) {
$mapping[$objId] = [];
}
+ foreach ($chunks as $chunk) {
+ $query->setParameter('objectids', $chunk, IQueryBuilder::PARAM_STR_ARRAY);
+ $result = $query->executeQuery();
+ while ($row = $result->fetch()) {
+ $objectId = $row['objectid'];
+ $mapping[$objectId][] = $row['systemtagid'];
+ }
- $result = $query->execute();
- while ($row = $result->fetch()) {
- $objectId = $row['objectid'];
- $mapping[$objectId][] = $row['systemtagid'];
+ $result->closeCursor();
}
- $result->closeCursor();
return $mapping;
}
diff --git a/lib/private/Template/JSResourceLocator.php b/lib/private/Template/JSResourceLocator.php
index 7648c7953f3..120234146e1 100644
--- a/lib/private/Template/JSResourceLocator.php
+++ b/lib/private/Template/JSResourceLocator.php
@@ -27,16 +27,19 @@
*/
namespace OC\Template;
+use OCP\App\AppPathNotFoundException;
+use OCP\App\IAppManager;
use Psr\Log\LoggerInterface;
class JSResourceLocator extends ResourceLocator {
- /** @var JSCombiner */
- protected $jsCombiner;
+ protected JSCombiner $jsCombiner;
+ protected IAppManager $appManager;
- public function __construct(LoggerInterface $logger, JSCombiner $JSCombiner) {
+ public function __construct(LoggerInterface $logger, JSCombiner $JSCombiner, IAppManager $appManager) {
parent::__construct($logger);
$this->jsCombiner = $JSCombiner;
+ $this->appManager = $appManager;
}
/**
@@ -53,59 +56,63 @@ class JSResourceLocator extends ResourceLocator {
// For language files we try to load them all, so themes can overwrite
// single l10n strings without having to translate all of them.
$found = 0;
- $found += $this->appendIfExist($this->serverroot, 'core/'.$script.'.js');
- $found += $this->appendIfExist($this->serverroot, $theme_dir.'core/'.$script.'.js');
- $found += $this->appendIfExist($this->serverroot, $script.'.js');
- $found += $this->appendIfExist($this->serverroot, $theme_dir.$script.'.js');
- $found += $this->appendIfExist($this->serverroot, 'apps/'.$script.'.js');
- $found += $this->appendIfExist($this->serverroot, $theme_dir.'apps/'.$script.'.js');
+ $found += $this->appendScriptIfExist($this->serverroot, 'core/'.$script);
+ $found += $this->appendScriptIfExist($this->serverroot, $theme_dir.'core/'.$script);
+ $found += $this->appendScriptIfExist($this->serverroot, $script);
+ $found += $this->appendScriptIfExist($this->serverroot, $theme_dir.$script);
+ $found += $this->appendScriptIfExist($this->serverroot, 'apps/'.$script);
+ $found += $this->appendScriptIfExist($this->serverroot, $theme_dir.'apps/'.$script);
if ($found) {
return;
}
- } elseif ($this->appendIfExist($this->serverroot, $theme_dir.'apps/'.$script.'.js')
- || $this->appendIfExist($this->serverroot, $theme_dir.$script.'.js')
- || $this->appendIfExist($this->serverroot, $script.'.js')
- || $this->appendIfExist($this->serverroot, $theme_dir . "dist/$app-$scriptName.js")
- || $this->appendIfExist($this->serverroot, "dist/$app-$scriptName.js")
- || $this->appendIfExist($this->serverroot, 'apps/'.$script.'.js')
+ } elseif ($this->appendScriptIfExist($this->serverroot, $theme_dir.'apps/'.$script)
+ || $this->appendScriptIfExist($this->serverroot, $theme_dir.$script)
+ || $this->appendScriptIfExist($this->serverroot, $script)
+ || $this->appendScriptIfExist($this->serverroot, $theme_dir."dist/$app-$scriptName")
+ || $this->appendScriptIfExist($this->serverroot, "dist/$app-$scriptName")
+ || $this->appendScriptIfExist($this->serverroot, 'apps/'.$script)
|| $this->cacheAndAppendCombineJsonIfExist($this->serverroot, $script.'.json')
- || $this->appendIfExist($this->serverroot, $theme_dir.'core/'.$script.'.js')
- || $this->appendIfExist($this->serverroot, 'core/'.$script.'.js')
- || (strpos($scriptName, '/') === -1 && ($this->appendIfExist($this->serverroot, $theme_dir . "dist/core-$scriptName.js")
- || $this->appendIfExist($this->serverroot, "dist/core-$scriptName.js")))
+ || $this->appendScriptIfExist($this->serverroot, $theme_dir.'core/'.$script)
+ || $this->appendScriptIfExist($this->serverroot, 'core/'.$script)
+ || (strpos($scriptName, '/') === -1 && ($this->appendScriptIfExist($this->serverroot, $theme_dir."dist/core-$scriptName")
+ || $this->appendScriptIfExist($this->serverroot, "dist/core-$scriptName")))
|| $this->cacheAndAppendCombineJsonIfExist($this->serverroot, 'core/'.$script.'.json')
) {
return;
}
$script = substr($script, strpos($script, '/') + 1);
- $app_path = \OC_App::getAppPath($app);
- $app_url = \OC_App::getAppWebPath($app);
+ $app_url = null;
+
+ try {
+ $app_url = $this->appManager->getAppWebPath($app);
+ } catch (AppPathNotFoundException) {
+ // pass
+ }
+
+ try {
+ $app_path = $this->appManager->getAppPath($app);
- if ($app_path !== false) {
// Account for the possibility of having symlinks in app path. Only
// do this if $app_path is set, because an empty argument to realpath
// gets turned into cwd.
$app_path = realpath($app_path);
- }
- // missing translations files fill be ignored
- if (strpos($script, 'l10n/') === 0) {
- $this->appendIfExist($app_path, $script . '.js', $app_url);
- return;
- }
+ // missing translations files will be ignored
+ if (strpos($script, 'l10n/') === 0) {
+ $this->appendScriptIfExist($app_path, $script, $app_url);
+ return;
+ }
- if ($app_path === false && $app_url === false) {
+ if (!$this->cacheAndAppendCombineJsonIfExist($app_path, $script.'.json', $app)) {
+ $this->appendScriptIfExist($app_path, $script, $app_url);
+ }
+ } catch (AppPathNotFoundException) {
$this->logger->error('Could not find resource {resource} to load', [
'resource' => $app . '/' . $script . '.js',
'app' => 'jsresourceloader',
]);
- return;
- }
-
- if (!$this->cacheAndAppendCombineJsonIfExist($app_path, $script.'.json', $app)) {
- $this->append($app_path, $script . '.js', $app_url);
}
}
@@ -115,6 +122,17 @@ class JSResourceLocator extends ResourceLocator {
public function doFindTheme($script) {
}
+ /**
+ * Try to find ES6 script file (`.mjs`) with fallback to plain javascript (`.js`)
+ * @see appendIfExist()
+ */
+ protected function appendScriptIfExist(string $root, string $file, string $webRoot = null) {
+ if (!$this->appendIfExist($root, $file . '.mjs', $webRoot)) {
+ return $this->appendIfExist($root, $file . '.js', $webRoot);
+ }
+ return true;
+ }
+
protected function cacheAndAppendCombineJsonIfExist($root, $file, $app = 'core') {
if (is_file($root.'/'.$file)) {
if ($this->jsCombiner->process($root, $file, $app)) {
@@ -122,7 +140,12 @@ class JSResourceLocator extends ResourceLocator {
} else {
// Add all the files from the json
$files = $this->jsCombiner->getContent($root, $file);
- $app_url = \OC_App::getAppWebPath($app);
+ $app_url = null;
+ try {
+ $app_url = $this->appManager->getAppWebPath($app);
+ } catch (AppPathNotFoundException) {
+ // pass
+ }
foreach ($files as $jsFile) {
$this->append($root, $jsFile, $app_url);
diff --git a/lib/private/Translation/TranslationManager.php b/lib/private/Translation/TranslationManager.php
new file mode 100644
index 00000000000..ec829e83255
--- /dev/null
+++ b/lib/private/Translation/TranslationManager.php
@@ -0,0 +1,120 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @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\Translation;
+
+use InvalidArgumentException;
+use OC\AppFramework\Bootstrap\Coordinator;
+use OCP\IServerContainer;
+use OCP\PreConditionNotMetException;
+use OCP\Translation\IDetectLanguageProvider;
+use OCP\Translation\ITranslationManager;
+use OCP\Translation\ITranslationProvider;
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Container\NotFoundExceptionInterface;
+use Psr\Log\LoggerInterface;
+use RuntimeException;
+use Throwable;
+
+class TranslationManager implements ITranslationManager {
+ /** @var ?ITranslationProvider[] */
+ private ?array $providers = null;
+
+ public function __construct(
+ private IServerContainer $serverContainer,
+ private Coordinator $coordinator,
+ private LoggerInterface $logger,
+ ) {
+ }
+
+ public function getLanguages(): array {
+ $languages = [];
+ foreach ($this->getProviders() as $provider) {
+ $languages = array_merge($languages, $provider->getAvailableLanguages());
+ }
+ return $languages;
+ }
+
+ public function translate(string $text, ?string $fromLanguage, string $toLanguage): string {
+ if (!$this->hasProviders()) {
+ throw new PreConditionNotMetException('No translation providers available');
+ }
+
+ foreach ($this->getProviders() as $provider) {
+ if ($fromLanguage === null && $provider instanceof IDetectLanguageProvider) {
+ $fromLanguage = $provider->detectLanguage($text);
+ }
+
+ if ($fromLanguage === null) {
+ throw new InvalidArgumentException('Could not detect language');
+ }
+
+ try {
+ return $provider->translate($fromLanguage, $toLanguage, $text);
+ } catch (RuntimeException $e) {
+ $this->logger->warning("Failed to translate from {$fromLanguage} to {$toLanguage}", ['exception' => $e]);
+ }
+ }
+
+ throw new RuntimeException('Could not translate text');
+ }
+
+ public function getProviders(): array {
+ $context = $this->coordinator->getRegistrationContext();
+
+ if ($this->providers !== null) {
+ return $this->providers;
+ }
+
+ $this->providers = [];
+ foreach ($context->getTranslationProviders() as $providerRegistration) {
+ $class = $providerRegistration->getService();
+ try {
+ $this->providers[$class] = $this->serverContainer->get($class);
+ } catch (NotFoundExceptionInterface|ContainerExceptionInterface|Throwable $e) {
+ $this->logger->error('Failed to load translation provider ' . $class, [
+ 'exception' => $e
+ ]);
+ }
+ }
+
+ return $this->providers;
+ }
+
+ public function hasProviders(): bool {
+ $context = $this->coordinator->getRegistrationContext();
+ return !empty($context->getTranslationProviders());
+ }
+
+ public function canDetectLanguage(): bool {
+ foreach ($this->getProviders() as $provider) {
+ if ($provider instanceof IDetectLanguageProvider) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/lib/private/User/Database.php b/lib/private/User/Database.php
index 8bbbccd4540..944202f244e 100644
--- a/lib/private/User/Database.php
+++ b/lib/private/User/Database.php
@@ -45,6 +45,7 @@ declare(strict_types=1);
*/
namespace OC\User;
+use OCP\AppFramework\Db\TTransactional;
use OCP\Cache\CappedMemoryCache;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IDBConnection;
@@ -85,6 +86,8 @@ class Database extends ABackend implements
/** @var string */
private $table;
+ use TTransactional;
+
/**
* \OC\User\Database constructor.
*
@@ -122,20 +125,24 @@ class Database extends ABackend implements
if (!$this->userExists($uid)) {
$this->eventDispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password));
- $qb = $this->dbConn->getQueryBuilder();
- $qb->insert($this->table)
- ->values([
- 'uid' => $qb->createNamedParameter($uid),
- 'password' => $qb->createNamedParameter(\OC::$server->getHasher()->hash($password)),
- 'uid_lower' => $qb->createNamedParameter(mb_strtolower($uid)),
- ]);
+ return $this->atomic(function () use ($uid, $password) {
+ $qb = $this->dbConn->getQueryBuilder();
+ $qb->insert($this->table)
+ ->values([
+ 'uid' => $qb->createNamedParameter($uid),
+ 'password' => $qb->createNamedParameter(\OC::$server->getHasher()->hash($password)),
+ 'uid_lower' => $qb->createNamedParameter(mb_strtolower($uid)),
+ ]);
- $result = $qb->execute();
+ $result = $qb->executeStatement();
- // Clear cache
- unset($this->cache[$uid]);
+ // Clear cache
+ unset($this->cache[$uid]);
+ // Repopulate the cache
+ $this->loadUser($uid);
- return $result ? true : false;
+ return (bool) $result;
+ }, $this->dbConn);
}
return false;
diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php
index c7b11e22504..3e45ebeab2b 100644
--- a/lib/private/User/Session.php
+++ b/lib/private/User/Session.php
@@ -59,6 +59,7 @@ use OCP\ISession;
use OCP\IUser;
use OCP\IUserSession;
use OCP\Lockdown\ILockdownManager;
+use OCP\Security\Bruteforce\IThrottler;
use OCP\Security\ISecureRandom;
use OCP\Session\Exceptions\SessionNotAvailableException;
use OCP\User\Events\PostLoginEvent;
@@ -426,7 +427,8 @@ class Session implements IUserSession, Emitter {
$password,
IRequest $request,
OC\Security\Bruteforce\Throttler $throttler) {
- $currentDelay = $throttler->sleepDelay($request->getRemoteAddress(), 'login');
+ $remoteAddress = $request->getRemoteAddress();
+ $currentDelay = $throttler->sleepDelay($remoteAddress, 'login');
if ($this->manager instanceof PublicEmitter) {
$this->manager->emit('\OC\User', 'preLogin', [$user, $password]);
@@ -450,19 +452,12 @@ class Session implements IUserSession, Emitter {
if (!$this->login($user, $password)) {
// Failed, maybe the user used their email address
if (!filter_var($user, FILTER_VALIDATE_EMAIL)) {
+ $this->handleLoginFailed($throttler, $currentDelay, $remoteAddress, $user, $password);
return false;
}
$users = $this->manager->getByEmail($user);
if (!(\count($users) === 1 && $this->login($users[0]->getUID(), $password))) {
- $this->logger->warning('Login failed: \'' . $user . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
-
- $throttler->registerAttempt('login', $request->getRemoteAddress(), ['user' => $user]);
-
- $this->dispatcher->dispatchTyped(new OC\Authentication\Events\LoginFailed($user, $password));
-
- if ($currentDelay === 0) {
- $throttler->sleepDelay($request->getRemoteAddress(), 'login');
- }
+ $this->handleLoginFailed($throttler, $currentDelay, $remoteAddress, $user, $password);
return false;
}
}
@@ -477,6 +472,17 @@ class Session implements IUserSession, Emitter {
return true;
}
+ private function handleLoginFailed(IThrottler $throttler, int $currentDelay, string $remoteAddress, string $user, ?string $password) {
+ $this->logger->warning("Login failed: '" . $user . "' (Remote IP: '" . $remoteAddress . "')", ['app' => 'core']);
+
+ $throttler->registerAttempt('login', $remoteAddress, ['user' => $user]);
+ $this->dispatcher->dispatchTyped(new OC\Authentication\Events\LoginFailed($user, $password));
+
+ if ($currentDelay === 0) {
+ $throttler->sleepDelay($remoteAddress, 'login');
+ }
+ }
+
protected function supportsCookies(IRequest $request) {
if (!is_null($request->getCookie('cookie_test'))) {
return true;
diff --git a/lib/private/User/User.php b/lib/private/User/User.php
index 2b975c290ba..2d80dbc7adf 100644
--- a/lib/private/User/User.php
+++ b/lib/private/User/User.php
@@ -529,6 +529,7 @@ class User implements IUser {
$this->config->setUserValue($this->uid, 'files', 'quota', $quota);
$this->triggerChange('quota', $quota, $oldQuota);
}
+ \OC_Helper::clearStorageInfo('/' . $this->uid . '/files');
}
/**
diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php
index 7f51d81d21b..5051d3e7ab5 100644
--- a/lib/private/legacy/OC_App.php
+++ b/lib/private/legacy/OC_App.php
@@ -50,11 +50,14 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
+
+use OCP\App\Events\AppUpdateEvent;
use OCP\AppFramework\QueryException;
+use OCP\App\IAppManager;
use OCP\App\ManagerEvent;
use OCP\Authentication\IAlternativeLogin;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\ILogger;
-use OCP\Settings\IManager as ISettingsManager;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\App\DependencyAnalyzer;
use OC\App\Platform;
@@ -62,7 +65,6 @@ use OC\DB\MigrationService;
use OC\Installer;
use OC\Repair;
use OC\Repair\Events\RepairErrorEvent;
-use OC\ServerNotAvailableException;
use Psr\Log\LoggerInterface;
/**
@@ -73,8 +75,6 @@ use Psr\Log\LoggerInterface;
class OC_App {
private static $adminForms = [];
private static $personalForms = [];
- private static $appTypes = [];
- private static $loadedApps = [];
private static $altLogin = [];
private static $alreadyRegistered = [];
public const supportedApp = 300;
@@ -98,9 +98,10 @@ class OC_App {
*
* @param string $app
* @return bool
+ * @deprecated 26.0.0 use IAppManager::isAppLoaded
*/
public static function isAppLoaded(string $app): bool {
- return isset(self::$loadedApps[$app]);
+ return \OC::$server->get(IAppManager::class)->isAppLoaded($app);
}
/**
@@ -116,40 +117,11 @@ class OC_App {
* if $types is set to non-empty array, only apps of those types will be loaded
*/
public static function loadApps(array $types = []): bool {
- if ((bool) \OC::$server->getSystemConfig()->getValue('maintenance', false)) {
+ if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
+ // This should be done before calling this method so that appmanager can be used
return false;
}
- // Load the enabled apps here
- $apps = self::getEnabledApps();
-
- // Add each apps' folder as allowed class path
- foreach ($apps as $app) {
- // If the app is already loaded then autoloading it makes no sense
- if (!isset(self::$loadedApps[$app])) {
- $path = self::getAppPath($app);
- if ($path !== false) {
- self::registerAutoloading($app, $path);
- }
- }
- }
-
- // prevent app.php from printing output
- ob_start();
- foreach ($apps as $app) {
- if (!isset(self::$loadedApps[$app]) && ($types === [] || self::isType($app, $types))) {
- try {
- self::loadApp($app);
- } catch (\Throwable $e) {
- \OC::$server->get(LoggerInterface::class)->emergency('Error during app loading: ' . $e->getMessage(), [
- 'exception' => $e,
- 'app' => $app,
- ]);
- }
- }
- }
- ob_end_clean();
-
- return true;
+ return \OC::$server->get(IAppManager::class)->loadApps($types);
}
/**
@@ -157,120 +129,10 @@ class OC_App {
*
* @param string $app
* @throws Exception
+ * @deprecated 26.0.0 use IAppManager::loadApp
*/
public static function loadApp(string $app): void {
- if (isset(self::$loadedApps[$app])) {
- return;
- }
- self::$loadedApps[$app] = true;
- $appPath = self::getAppPath($app);
- if ($appPath === false) {
- return;
- }
- $eventLogger = \OC::$server->get(\OCP\Diagnostics\IEventLogger::class);
- $eventLogger->start("bootstrap:load_app:$app", "Load $app");
-
- // in case someone calls loadApp() directly
- self::registerAutoloading($app, $appPath);
-
- /** @var Coordinator $coordinator */
- $coordinator = \OC::$server->query(Coordinator::class);
- $isBootable = $coordinator->isBootable($app);
-
- $hasAppPhpFile = is_file($appPath . '/appinfo/app.php');
-
- if ($isBootable && $hasAppPhpFile) {
- \OC::$server->getLogger()->error('/appinfo/app.php is not loaded when \OCP\AppFramework\Bootstrap\IBootstrap on the application class is used. Migrate everything from app.php to the Application class.', [
- 'app' => $app,
- ]);
- } elseif ($hasAppPhpFile) {
- $eventLogger->start("bootstrap:load_app:$app:app.php", "Load legacy app.php app $app");
- \OC::$server->getLogger()->debug('/appinfo/app.php is deprecated, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [
- 'app' => $app,
- ]);
- try {
- self::requireAppFile($appPath);
- } catch (Throwable $ex) {
- if ($ex instanceof ServerNotAvailableException) {
- throw $ex;
- }
- if (!\OC::$server->getAppManager()->isShipped($app) && !self::isType($app, ['authentication'])) {
- \OC::$server->getLogger()->logException($ex, [
- 'message' => "App $app threw an error during app.php load and will be disabled: " . $ex->getMessage(),
- ]);
-
- // Only disable apps which are not shipped and that are not authentication apps
- \OC::$server->getAppManager()->disableApp($app, true);
- } else {
- \OC::$server->getLogger()->logException($ex, [
- 'message' => "App $app threw an error during app.php load: " . $ex->getMessage(),
- ]);
- }
- }
- $eventLogger->end("bootstrap:load_app:$app:app.php");
- }
-
- $coordinator->bootApp($app);
-
- $eventLogger->start("bootstrap:load_app:$app:info", "Load info.xml for $app and register any services defined in it");
- $info = self::getAppInfo($app);
- if (!empty($info['activity']['filters'])) {
- foreach ($info['activity']['filters'] as $filter) {
- \OC::$server->getActivityManager()->registerFilter($filter);
- }
- }
- if (!empty($info['activity']['settings'])) {
- foreach ($info['activity']['settings'] as $setting) {
- \OC::$server->getActivityManager()->registerSetting($setting);
- }
- }
- if (!empty($info['activity']['providers'])) {
- foreach ($info['activity']['providers'] as $provider) {
- \OC::$server->getActivityManager()->registerProvider($provider);
- }
- }
-
- if (!empty($info['settings']['admin'])) {
- foreach ($info['settings']['admin'] as $setting) {
- \OC::$server->get(ISettingsManager::class)->registerSetting('admin', $setting);
- }
- }
- if (!empty($info['settings']['admin-section'])) {
- foreach ($info['settings']['admin-section'] as $section) {
- \OC::$server->get(ISettingsManager::class)->registerSection('admin', $section);
- }
- }
- if (!empty($info['settings']['personal'])) {
- foreach ($info['settings']['personal'] as $setting) {
- \OC::$server->get(ISettingsManager::class)->registerSetting('personal', $setting);
- }
- }
- if (!empty($info['settings']['personal-section'])) {
- foreach ($info['settings']['personal-section'] as $section) {
- \OC::$server->get(ISettingsManager::class)->registerSection('personal', $section);
- }
- }
-
- if (!empty($info['collaboration']['plugins'])) {
- // deal with one or many plugin entries
- $plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
- [$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
- foreach ($plugins as $plugin) {
- if ($plugin['@attributes']['type'] === 'collaborator-search') {
- $pluginInfo = [
- 'shareType' => $plugin['@attributes']['share-type'],
- 'class' => $plugin['@value'],
- ];
- \OC::$server->getCollaboratorSearch()->registerPlugin($pluginInfo);
- } elseif ($plugin['@attributes']['type'] === 'autocomplete-sort') {
- \OC::$server->getAutoCompleteManager()->registerSorter($plugin['@value']);
- }
- }
- }
-
- $eventLogger->end("bootstrap:load_app:$app:info");
-
- $eventLogger->end("bootstrap:load_app:$app");
+ \OC::$server->get(IAppManager::class)->loadApp($app);
}
/**
@@ -295,8 +157,6 @@ class OC_App {
require_once $path . '/composer/autoload.php';
} else {
\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
- // Register on legacy autoloader
- \OC::$loader->addValidRoot($path);
}
// Register Test namespace only when testing
@@ -306,50 +166,15 @@ class OC_App {
}
/**
- * Load app.php from the given app
- *
- * @param string $app app name
- * @throws Error
- */
- private static function requireAppFile(string $app) {
- // encapsulated here to avoid variable scope conflicts
- require_once $app . '/appinfo/app.php';
- }
-
- /**
* check if an app is of a specific type
*
* @param string $app
* @param array $types
* @return bool
+ * @deprecated 26.0.0 use IAppManager::isType
*/
public static function isType(string $app, array $types): bool {
- $appTypes = self::getAppTypes($app);
- foreach ($types as $type) {
- if (array_search($type, $appTypes) !== false) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * get the types of an app
- *
- * @param string $app
- * @return array
- */
- private static function getAppTypes(string $app): array {
- //load the cache
- if (count(self::$appTypes) == 0) {
- self::$appTypes = \OC::$server->getAppConfig()->getValues(false, 'types');
- }
-
- if (isset(self::$appTypes[$app])) {
- return explode(',', self::$appTypes[$app]);
- }
-
- return [];
+ return \OC::$server->get(IAppManager::class)->isType($app, $types);
}
/**
@@ -1042,6 +867,7 @@ class OC_App {
$version = \OC_App::getAppVersion($appId);
\OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
+ \OC::$server->get(IEventDispatcher::class)->dispatchTyped(new AppUpdateEvent($appId));
\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
ManagerEvent::EVENT_APP_UPDATE, $appId
));
@@ -1061,7 +887,7 @@ class OC_App {
// load the app
self::loadApp($appId);
- $dispatcher = \OC::$server->get(\OCP\EventDispatcher\IEventDispatcher::class);
+ $dispatcher = \OC::$server->get(IEventDispatcher::class);
// load the steps
$r = new Repair([], $dispatcher, \OC::$server->get(LoggerInterface::class));
diff --git a/lib/private/legacy/OC_Helper.php b/lib/private/legacy/OC_Helper.php
index c2036c7b863..8d708118b96 100644
--- a/lib/private/legacy/OC_Helper.php
+++ b/lib/private/legacy/OC_Helper.php
@@ -473,7 +473,7 @@ class OC_Helper {
if (!$view) {
throw new \OCP\Files\NotFoundException();
}
- $fullPath = $view->getAbsolutePath($path);
+ $fullPath = Filesystem::normalizePath($view->getAbsolutePath($path));
$cacheKey = $fullPath. '::' . ($includeMountPoints ? 'include' : 'exclude');
if ($useCache) {
@@ -620,6 +620,15 @@ class OC_Helper {
];
}
+ public static function clearStorageInfo(string $absolutePath): void {
+ /** @var ICacheFactory $cacheFactory */
+ $cacheFactory = \OC::$server->get(ICacheFactory::class);
+ $memcache = $cacheFactory->createLocal('storage_info');
+ $cacheKeyPrefix = Filesystem::normalizePath($absolutePath) . '::';
+ $memcache->remove($cacheKeyPrefix . 'include');
+ $memcache->remove($cacheKeyPrefix . 'exclude');
+ }
+
/**
* Returns whether the config file is set manually to read-only
* @return bool
diff --git a/lib/private/legacy/OC_Image.php b/lib/private/legacy/OC_Image.php
index f2fa2058faa..74e82e62b16 100644
--- a/lib/private/legacy/OC_Image.php
+++ b/lib/private/legacy/OC_Image.php
@@ -598,7 +598,7 @@ class OC_Image implements \OCP\IImage {
* @return bool true if allocating is allowed, false otherwise
*/
private function checkImageSize($path) {
- $size = getimagesize($path);
+ $size = @getimagesize($path);
if (!$size) {
return true;
}
@@ -619,7 +619,7 @@ class OC_Image implements \OCP\IImage {
* @return bool true if allocating is allowed, false otherwise
*/
private function checkImageDataSize($data) {
- $size = getimagesizefromstring($data);
+ $size = @getimagesizefromstring($data);
if (!$size) {
return true;
}
diff --git a/lib/private/legacy/OC_User.php b/lib/private/legacy/OC_User.php
index 8aaa9072ba4..caa4f5dca65 100644
--- a/lib/private/legacy/OC_User.php
+++ b/lib/private/legacy/OC_User.php
@@ -40,6 +40,7 @@ use OC\User\LoginException;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\ILogger;
use OCP\IUserManager;
+use OCP\User\Events\BeforeUserLoggedInEvent;
use OCP\User\Events\UserLoggedInEvent;
/**
@@ -172,6 +173,10 @@ class OC_User {
if (self::getUser() !== $uid) {
self::setUserId($uid);
$userSession = \OC::$server->getUserSession();
+
+ /** @var IEventDispatcher $dispatcher */
+ $dispatcher = \OC::$server->get(IEventDispatcher::class);
+
if ($userSession->getUser() && !$userSession->getUser()->isEnabled()) {
$message = \OC::$server->getL10N('lib')->t('User disabled');
throw new LoginException($message);
@@ -182,6 +187,10 @@ class OC_User {
if ($backend instanceof \OCP\Authentication\IProvideUserSecretBackend) {
$password = $backend->getCurrentUserSecret();
}
+
+ /** @var IEventDispatcher $dispatcher */
+ $dispatcher->dispatchTyped(new BeforeUserLoggedInEvent($uid, $password, $backend));
+
$userSession->createSessionToken($request, $uid, $uid, $password);
$userSession->createRememberMeToken($userSession->getUser());
// setup the filesystem
@@ -199,8 +208,6 @@ class OC_User {
'isTokenLogin' => false,
]
);
- /** @var IEventDispatcher $dispatcher */
- $dispatcher = \OC::$server->get(IEventDispatcher::class);
$dispatcher->dispatchTyped(new UserLoggedInEvent(
\OC::$server->get(IUserManager::class)->get($uid),
$uid,
diff --git a/lib/private/legacy/template/functions.php b/lib/private/legacy/template/functions.php
index 56c488d5abe..7081bd4f743 100644
--- a/lib/private/legacy/template/functions.php
+++ b/lib/private/legacy/template/functions.php
@@ -72,14 +72,19 @@ function emit_css_loading_tags($obj) {
* Prints a <script> tag with nonce and defer depending on config
* @param string $src the source URL, ignored when empty
* @param string $script_content the inline script content, ignored when empty
+ * @param string $content_type the type of the source (e.g. 'module')
*/
-function emit_script_tag($src, $script_content = '') {
+function emit_script_tag(string $src, string $script_content = '', string $content_type = '') {
+ $nonceManager = \OC::$server->get(\OC\Security\CSP\ContentSecurityPolicyNonceManager::class);
+
$defer_str = ' defer';
- $s = '<script nonce="' . \OC::$server->getContentSecurityPolicyNonceManager()->getNonce() . '"';
+ $type = $content_type !== '' ? ' type="' . $content_type . '"' : '';
+
+ $s = '<script nonce="' . $nonceManager->getNonce() . '"';
if (!empty($src)) {
// emit script tag for deferred loading from $src
- $s .= $defer_str.' src="' . $src .'">';
- } elseif (!empty($script_content)) {
+ $s .= $defer_str.' src="' . $src .'"' . $type . '>';
+ } elseif ($script_content !== '') {
// emit script tag for inline script from $script_content without defer (see MDN)
$s .= ">\n".$script_content."\n";
} else {
@@ -96,7 +101,8 @@ function emit_script_tag($src, $script_content = '') {
*/
function emit_script_loading_tags($obj) {
foreach ($obj['jsfiles'] as $jsfile) {
- emit_script_tag($jsfile, '');
+ $type = str_ends_with($jsfile, '.mjs') ? 'module' : '';
+ emit_script_tag($jsfile, '', $type);
}
if (!empty($obj['inline_ocjs'])) {
emit_script_tag('', $obj['inline_ocjs']);