diff options
Diffstat (limited to 'lib/private')
324 files changed, 6276 insertions, 2672 deletions
diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php index 7b99fb0a410..05feaf87b8f 100644 --- a/lib/private/Accounts/AccountManager.php +++ b/lib/private/Accounts/AccountManager.php @@ -30,13 +30,19 @@ namespace OC\Accounts; +use libphonenumber\NumberParseException; +use libphonenumber\PhoneNumber; +use libphonenumber\PhoneNumberFormat; +use libphonenumber\PhoneNumberUtil; use OCA\Settings\BackgroundJobs\VerifyUserData; use OCP\Accounts\IAccount; use OCP\Accounts\IAccountManager; use OCP\BackgroundJob\IJobList; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IConfig; use OCP\IDBConnection; -use OCP\ILogger; use OCP\IUser; +use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; use function json_decode; @@ -55,44 +61,89 @@ class AccountManager implements IAccountManager { /** @var IDBConnection database connection */ private $connection; + /** @var IConfig */ + private $config; + /** @var string table name */ private $table = 'accounts'; + /** @var string table name */ + private $dataTable = 'accounts_data'; + /** @var EventDispatcherInterface */ private $eventDispatcher; /** @var IJobList */ private $jobList; - /** @var ILogger */ + /** @var LoggerInterface */ private $logger; - /** - * AccountManager constructor. - * - * @param IDBConnection $connection - * @param EventDispatcherInterface $eventDispatcher - * @param IJobList $jobList - */ public function __construct(IDBConnection $connection, + IConfig $config, EventDispatcherInterface $eventDispatcher, IJobList $jobList, - ILogger $logger) { + LoggerInterface $logger) { $this->connection = $connection; + $this->config = $config; $this->eventDispatcher = $eventDispatcher; $this->jobList = $jobList; $this->logger = $logger; } /** + * @param string $input + * @return string Provided phone number in E.164 format when it was a valid number + * @throws \InvalidArgumentException When the phone number was invalid or no default region is set and the number doesn't start with a country code + */ + protected function parsePhoneNumber(string $input): string { + $defaultRegion = $this->config->getSystemValueString('default_phone_region', ''); + + if ($defaultRegion === '') { + // When no default region is set, only +49… numbers are valid + if (strpos($input, '+') !== 0) { + throw new \InvalidArgumentException(self::PROPERTY_PHONE); + } + + $defaultRegion = 'EN'; + } + + $phoneUtil = PhoneNumberUtil::getInstance(); + try { + $phoneNumber = $phoneUtil->parse($input, $defaultRegion); + if ($phoneNumber instanceof PhoneNumber && $phoneUtil->isValidNumber($phoneNumber)) { + return $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164); + } + } catch (NumberParseException $e) { + } + + throw new \InvalidArgumentException(self::PROPERTY_PHONE); + } + + /** * update user record * * @param IUser $user - * @param $data + * @param array $data + * @param bool $throwOnData Set to true if you can inform the user about invalid data + * @return array The potentially modified data (e.g. phone numbers are converted to E.164 format) + * @throws \InvalidArgumentException Message is the property that was invalid */ - public function updateUser(IUser $user, $data) { + public function updateUser(IUser $user, array $data, bool $throwOnData = false): array { $userData = $this->getUser($user); $updated = true; + + if (isset($data[self::PROPERTY_PHONE]) && $data[self::PROPERTY_PHONE]['value'] !== '') { + try { + $data[self::PROPERTY_PHONE]['value'] = $this->parsePhoneNumber($data[self::PROPERTY_PHONE]['value']); + } catch (\InvalidArgumentException $e) { + if ($throwOnData) { + throw $e; + } + $data[self::PROPERTY_PHONE]['value'] = ''; + } + } + if (empty($userData)) { $this->insertNewUser($user, $data); } elseif ($userData !== $data) { @@ -110,6 +161,8 @@ class AccountManager implements IAccountManager { new GenericEvent($user, $data) ); } + + return $data; } /** @@ -123,6 +176,21 @@ class AccountManager implements IAccountManager { $query->delete($this->table) ->where($query->expr()->eq('uid', $query->createNamedParameter($uid))) ->execute(); + + $this->deleteUserData($user); + } + + /** + * delete user from accounts table + * + * @param IUser $user + */ + public function deleteUserData(IUser $user): void { + $uid = $user->getUID(); + $query = $this->connection->getQueryBuilder(); + $query->delete($this->dataTable) + ->where($query->expr()->eq('uid', $query->createNamedParameter($uid))) + ->execute(); } /** @@ -134,19 +202,21 @@ class AccountManager implements IAccountManager { public function getUser(IUser $user) { $uid = $user->getUID(); $query = $this->connection->getQueryBuilder(); - $query->select('data')->from($this->table) + $query->select('data') + ->from($this->table) ->where($query->expr()->eq('uid', $query->createParameter('uid'))) ->setParameter('uid', $uid); - $query->execute(); - $result = $query->execute()->fetchAll(); + $result = $query->execute(); + $accountData = $result->fetchAll(); + $result->closeCursor(); - if (empty($result)) { + if (empty($accountData)) { $userData = $this->buildDefaultUserRecord($user); $this->insertNewUser($user, $userData); return $userData; } - $userDataArray = json_decode($result[0]['data'], true); + $userDataArray = json_decode($accountData[0]['data'], true); $jsonError = json_last_error(); if ($userDataArray === null || $userDataArray === [] || $jsonError !== JSON_ERROR_NONE) { $this->logger->critical("User data of $uid contained invalid JSON (error $jsonError), hence falling back to a default user record"); @@ -158,6 +228,24 @@ class AccountManager implements IAccountManager { return $userDataArray; } + public function searchUsers(string $property, array $values): array { + $query = $this->connection->getQueryBuilder(); + $query->select('*') + ->from($this->dataTable) + ->where($query->expr()->eq('name', $query->createNamedParameter($property))) + ->andWhere($query->expr()->in('value', $query->createNamedParameter($values, IQueryBuilder::PARAM_STR_ARRAY))); + + $result = $query->execute(); + $matches = []; + + while ($row = $result->fetch()) { + $matches[$row['value']] = $row['uid']; + } + $result->closeCursor(); + + return $matches; + } + /** * check if we need to ask the server for email verification, if yes we create a cronjob * @@ -178,7 +266,7 @@ class AccountManager implements IAccountManager { 'lastRun' => time() ] ); - $newData[AccountManager::PROPERTY_EMAIL]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS; + $newData[self::PROPERTY_EMAIL]['verified'] = self::VERIFICATION_IN_PROGRESS; } return $newData; @@ -261,7 +349,7 @@ class AccountManager implements IAccountManager { * @param IUser $user * @param array $data */ - protected function insertNewUser(IUser $user, $data) { + protected function insertNewUser(IUser $user, array $data): void { $uid = $user->getUID(); $jsonEncodedData = json_encode($data); $query = $this->connection->getQueryBuilder(); @@ -273,6 +361,9 @@ class AccountManager implements IAccountManager { ] ) ->execute(); + + $this->deleteUserData($user); + $this->writeUserData($user, $data); } /** @@ -281,7 +372,7 @@ class AccountManager implements IAccountManager { * @param IUser $user * @param array $data */ - protected function updateExistingUser(IUser $user, $data) { + protected function updateExistingUser(IUser $user, array $data): void { $uid = $user->getUID(); $jsonEncodedData = json_encode($data); $query = $this->connection->getQueryBuilder(); @@ -289,6 +380,30 @@ class AccountManager implements IAccountManager { ->set('data', $query->createNamedParameter($jsonEncodedData)) ->where($query->expr()->eq('uid', $query->createNamedParameter($uid))) ->execute(); + + $this->deleteUserData($user); + $this->writeUserData($user, $data); + } + + protected function writeUserData(IUser $user, array $data): void { + $query = $this->connection->getQueryBuilder(); + $query->insert($this->dataTable) + ->values( + [ + 'uid' => $query->createNamedParameter($user->getUID()), + 'name' => $query->createParameter('name'), + 'value' => $query->createParameter('value'), + ] + ); + foreach ($data as $propertyName => $property) { + if ($propertyName === self::PROPERTY_AVATAR) { + continue; + } + + $query->setParameter('name', $propertyName) + ->setParameter('value', $property['value']); + $query->execute(); + } } /** diff --git a/lib/private/Accounts/Hooks.php b/lib/private/Accounts/Hooks.php index 2288b884c5c..d4e9637dfee 100644 --- a/lib/private/Accounts/Hooks.php +++ b/lib/private/Accounts/Hooks.php @@ -4,6 +4,7 @@ * * @author Bjoern Schiessle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * @@ -24,23 +25,19 @@ namespace OC\Accounts; -use OCP\ILogger; +use OCP\Accounts\IAccountManager; use OCP\IUser; +use Psr\Log\LoggerInterface; class Hooks { - /** @var AccountManager */ - private $accountManager = null; + /** @var AccountManager|null */ + private $accountManager; - /** @var ILogger */ + /** @var LoggerInterface */ private $logger; - /** - * Hooks constructor. - * - * @param ILogger $logger - */ - public function __construct(ILogger $logger) { + public function __construct(LoggerInterface $logger) { $this->logger = $logger; } @@ -66,14 +63,14 @@ class Hooks { switch ($feature) { case 'eMailAddress': - if ($accountData[AccountManager::PROPERTY_EMAIL]['value'] !== $newValue) { - $accountData[AccountManager::PROPERTY_EMAIL]['value'] = $newValue; + if ($accountData[IAccountManager::PROPERTY_EMAIL]['value'] !== $newValue) { + $accountData[IAccountManager::PROPERTY_EMAIL]['value'] = $newValue; $accountManager->updateUser($user, $accountData); } break; case 'displayName': - if ($accountData[AccountManager::PROPERTY_DISPLAYNAME]['value'] !== $newValue) { - $accountData[AccountManager::PROPERTY_DISPLAYNAME]['value'] = $newValue; + if ($accountData[IAccountManager::PROPERTY_DISPLAYNAME]['value'] !== $newValue) { + $accountData[IAccountManager::PROPERTY_DISPLAYNAME]['value'] = $newValue; $accountManager->updateUser($user, $accountData); } break; @@ -85,7 +82,7 @@ class Hooks { * * @return AccountManager */ - protected function getAccountManager() { + protected function getAccountManager(): AccountManager { if ($this->accountManager === null) { $this->accountManager = \OC::$server->query(AccountManager::class); } diff --git a/lib/private/Activity/ActivitySettingsAdapter.php b/lib/private/Activity/ActivitySettingsAdapter.php index c49231bee2c..2dd852f0fd4 100644 --- a/lib/private/Activity/ActivitySettingsAdapter.php +++ b/lib/private/Activity/ActivitySettingsAdapter.php @@ -1,9 +1,12 @@ <?php declare(strict_types=1); + /** * @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl> * + * @author Robin Appelman <robin@icewind.nl> + * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify @@ -17,7 +20,7 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ @@ -25,6 +28,7 @@ namespace OC\Activity; use OCP\Activity\ActivitySettings; use OCP\Activity\ISetting; +use OCP\IL10N; /** * Adapt the old interface based settings into the new abstract @@ -32,9 +36,11 @@ use OCP\Activity\ISetting; */ class ActivitySettingsAdapter extends ActivitySettings { private $oldSettings; + private $l10n; - public function __construct(ISetting $oldSettings) { + public function __construct(ISetting $oldSettings, IL10N $l10n) { $this->oldSettings = $oldSettings; + $this->l10n = $l10n; } public function getIdentifier() { @@ -45,6 +51,14 @@ class ActivitySettingsAdapter extends ActivitySettings { return $this->oldSettings->getName(); } + public function getGroupIdentifier() { + return 'other'; + } + + public function getGroupName() { + return $this->l10n->t('Other activities'); + } + public function getPriority() { return $this->oldSettings->getPriority(); } diff --git a/lib/private/Activity/Event.php b/lib/private/Activity/Event.php index 5f82b37c91a..8535561cbbe 100644 --- a/lib/private/Activity/Event.php +++ b/lib/private/Activity/Event.php @@ -9,6 +9,7 @@ declare(strict_types=1); * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Joas Schilling <coding@schilljs.com> * @author Phil Davis <phil.davis@inf.org> + * @author Robin Appelman <robin@icewind.nl> * * @license AGPL-3.0 * diff --git a/lib/private/Activity/Manager.php b/lib/private/Activity/Manager.php index 81fd91c778a..25b666e6a0e 100644 --- a/lib/private/Activity/Manager.php +++ b/lib/private/Activity/Manager.php @@ -7,6 +7,7 @@ * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Joas Schilling <coding@schilljs.com> * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @@ -36,6 +37,7 @@ use OCP\Activity\IManager; use OCP\Activity\IProvider; use OCP\Activity\ISetting; use OCP\IConfig; +use OCP\IL10N; use OCP\IRequest; use OCP\IUser; use OCP\IUserSession; @@ -66,14 +68,20 @@ class Manager implements IManager { /** @var string */ protected $currentUserId; - public function __construct(IRequest $request, - IUserSession $session, - IConfig $config, - IValidator $validator) { + protected $l10n; + + public function __construct( + IRequest $request, + IUserSession $session, + IConfig $config, + IValidator $validator, + IL10N $l10n + ) { $this->request = $request; $this->session = $session; $this->config = $config; $this->validator = $validator; + $this->l10n = $l10n; } /** @var \Closure[] */ @@ -273,7 +281,7 @@ class Manager implements IManager { if ($setting instanceof ISetting) { if (!$setting instanceof ActivitySettings) { - $setting = new ActivitySettingsAdapter($setting); + $setting = new ActivitySettingsAdapter($setting, $this->l10n); } } else { throw new \InvalidArgumentException('Invalid activity filter registered'); diff --git a/lib/private/AllConfig.php b/lib/private/AllConfig.php index 5218d969b47..36f5f2cd40a 100644 --- a/lib/private/AllConfig.php +++ b/lib/private/AllConfig.php @@ -8,6 +8,7 @@ * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Loki3000 <github@labcms.ru> * @author Lukas Reschke <lukas@statuscode.ch> + * @author MichaIng <micha@dietpi.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> @@ -316,7 +317,7 @@ class AllConfig implements \OCP\IConfig { */ public function getUserValue($userId, $appName, $key, $default = '') { $data = $this->getUserValues($userId); - if (isset($data[$appName]) and isset($data[$appName][$key])) { + if (isset($data[$appName][$key])) { return $data[$appName][$key]; } else { return $default; @@ -350,11 +351,11 @@ class AllConfig implements \OCP\IConfig { // TODO - FIXME $this->fixDIInit(); - $sql = 'DELETE FROM `*PREFIX*preferences` '. + $sql = 'DELETE FROM `*PREFIX*preferences` '. 'WHERE `userid` = ? AND `appid` = ? AND `configkey` = ?'; $this->connection->executeUpdate($sql, [$userId, $appName, $key]); - if (isset($this->userCache[$userId]) and isset($this->userCache[$userId][$appName])) { + if (isset($this->userCache[$userId][$appName])) { unset($this->userCache[$userId][$appName][$key]); } } @@ -368,7 +369,7 @@ class AllConfig implements \OCP\IConfig { // TODO - FIXME $this->fixDIInit(); - $sql = 'DELETE FROM `*PREFIX*preferences` '. + $sql = 'DELETE FROM `*PREFIX*preferences` '. 'WHERE `userid` = ?'; $this->connection->executeUpdate($sql, [$userId]); @@ -384,7 +385,7 @@ class AllConfig implements \OCP\IConfig { // TODO - FIXME $this->fixDIInit(); - $sql = 'DELETE FROM `*PREFIX*preferences` '. + $sql = 'DELETE FROM `*PREFIX*preferences` '. 'WHERE `appid` = ?'; $this->connection->executeUpdate($sql, [$appName]); @@ -407,7 +408,7 @@ class AllConfig implements \OCP\IConfig { return $this->userCache[$userId]; } if ($userId === null || $userId === '') { - $this->userCache[$userId]=[]; + $this->userCache[$userId] = []; return $this->userCache[$userId]; } @@ -456,7 +457,7 @@ class AllConfig implements \OCP\IConfig { $placeholders = (count($chunk) === 50) ? $placeholders50 : implode(',', array_fill(0, count($chunk), '?')); - $query = 'SELECT `userid`, `configvalue` ' . + $query = 'SELECT `userid`, `configvalue` ' . 'FROM `*PREFIX*preferences` ' . 'WHERE `appid` = ? AND `configkey` = ? ' . 'AND `userid` IN (' . $placeholders . ')'; @@ -482,7 +483,7 @@ class AllConfig implements \OCP\IConfig { // TODO - FIXME $this->fixDIInit(); - $sql = 'SELECT `userid` FROM `*PREFIX*preferences` ' . + $sql = 'SELECT `userid` FROM `*PREFIX*preferences` ' . 'WHERE `appid` = ? AND `configkey` = ? '; if ($this->getSystemValue('dbtype', 'sqlite') === 'oci') { @@ -514,7 +515,7 @@ class AllConfig implements \OCP\IConfig { // TODO - FIXME $this->fixDIInit(); - $sql = 'SELECT `userid` FROM `*PREFIX*preferences` ' . + $sql = 'SELECT `userid` FROM `*PREFIX*preferences` ' . 'WHERE `appid` = ? AND `configkey` = ? '; if ($this->getSystemValue('dbtype', 'sqlite') === 'oci') { diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php index f756664e457..7063878429a 100644 --- a/lib/private/App/AppManager.php +++ b/lib/private/App/AppManager.php @@ -18,7 +18,7 @@ * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Tobia De Koninck <tobia@ledfan.be> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/App/AppStore/Bundles/HubBundle.php b/lib/private/App/AppStore/Bundles/HubBundle.php index 59b52389ef8..301be810c5d 100644 --- a/lib/private/App/AppStore/Bundles/HubBundle.php +++ b/lib/private/App/AppStore/Bundles/HubBundle.php @@ -6,6 +6,7 @@ declare(strict_types=1); * @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de> * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * @author Julius Härtl <jus@bitgrid.net> * * @license GNU AGPL version 3 or any later version * @@ -32,13 +33,19 @@ class HubBundle extends Bundle { } public function getAppIdentifiers() { - return [ + $hubApps = [ 'spreed', 'contacts', 'calendar', 'mail', - 'richdocumentscode', - 'richdocuments', ]; + + $architecture = php_uname('m'); + if (PHP_OS_FAMILY === 'Linux' && in_array($architecture, ['x86_64', 'aarch64'])) { + $hubApps[] = 'richdocuments'; + $hubApps[] = 'richdocumentscode' . ($architecture === 'aarch64' ? '_arm64' : ''); + } + + return $hubApps; } } diff --git a/lib/private/App/AppStore/Fetcher/AppFetcher.php b/lib/private/App/AppStore/Fetcher/AppFetcher.php index e4c2ba4e85e..4dc517879e8 100644 --- a/lib/private/App/AppStore/Fetcher/AppFetcher.php +++ b/lib/private/App/AppStore/Fetcher/AppFetcher.php @@ -6,6 +6,7 @@ * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Georg Ehrke <oc.list@georgehrke.com> * @author Joas Schilling <coding@schilljs.com> + * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> @@ -78,15 +79,16 @@ class AppFetcher extends Fetcher { * * @param string $ETag * @param string $content + * @param bool [$allowUnstable] Allow unstable releases * * @return array */ - protected function fetch($ETag, $content) { + protected function fetch($ETag, $content, $allowUnstable = false) { /** @var mixed[] $response */ $response = parent::fetch($ETag, $content); - $allowPreReleases = $this->getChannel() === 'beta' || $this->getChannel() === 'daily'; - $allowNightly = $this->getChannel() === 'daily'; + $allowPreReleases = $allowUnstable || $this->getChannel() === 'beta' || $this->getChannel() === 'daily'; + $allowNightly = $allowUnstable || $this->getChannel() === 'daily'; foreach ($response['data'] as $dataKey => $app) { $releases = []; @@ -99,14 +101,32 @@ class AppFetcher extends Fetcher { // Exclude all versions not compatible with the current version try { $versionParser = new VersionParser(); - $version = $versionParser->getVersion($release['rawPlatformVersionSpec']); + $serverVersion = $versionParser->getVersion($release['rawPlatformVersionSpec']); $ncVersion = $this->getVersion(); - $min = $version->getMinimumVersion(); - $max = $version->getMaximumVersion(); - $minFulfilled = $this->compareVersion->isCompatible($ncVersion, $min, '>='); - $maxFulfilled = $max !== '' && - $this->compareVersion->isCompatible($ncVersion, $max, '<='); - if ($minFulfilled && ($this->ignoreMaxVersion || $maxFulfilled)) { + $minServerVersion = $serverVersion->getMinimumVersion(); + $maxServerVersion = $serverVersion->getMaximumVersion(); + $minFulfilled = $this->compareVersion->isCompatible($ncVersion, $minServerVersion, '>='); + $maxFulfilled = $maxServerVersion !== '' && + $this->compareVersion->isCompatible($ncVersion, $maxServerVersion, '<='); + $isPhpCompatible = true; + if (($release['rawPhpVersionSpec'] ?? '*') !== '*') { + $phpVersion = $versionParser->getVersion($release['rawPhpVersionSpec']); + $minPhpVersion = $phpVersion->getMinimumVersion(); + $maxPhpVersion = $phpVersion->getMaximumVersion(); + $minPhpFulfilled = $minPhpVersion === '' || $this->compareVersion->isCompatible( + PHP_VERSION, + $minPhpVersion, + '>=' + ); + $maxPhpFulfilled = $maxPhpVersion === '' || $this->compareVersion->isCompatible( + PHP_VERSION, + $maxPhpVersion, + '<=' + ); + + $isPhpCompatible = $minPhpFulfilled && $maxPhpFulfilled; + } + if ($minFulfilled && ($this->ignoreMaxVersion || $maxFulfilled) && $isPhpCompatible) { $releases[] = $release; } } catch (\InvalidArgumentException $e) { diff --git a/lib/private/App/AppStore/Fetcher/Fetcher.php b/lib/private/App/AppStore/Fetcher/Fetcher.php index 500a0568c99..c5d83fcb1df 100644 --- a/lib/private/App/AppStore/Fetcher/Fetcher.php +++ b/lib/private/App/AppStore/Fetcher/Fetcher.php @@ -2,10 +2,11 @@ /** * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch> * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Georg Ehrke <oc.list@georgehrke.com> * @author Joas Schilling <coding@schilljs.com> + * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> + * @author Julius Härtl <jus@bitgrid.net> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> @@ -42,6 +43,7 @@ use OCP\ILogger; abstract class Fetcher { public const INVALIDATE_AFTER_SECONDS = 3600; + public const RETRY_AFTER_FAILURE_SECONDS = 300; /** @var IAppData */ protected $appData; @@ -91,6 +93,9 @@ abstract class Fetcher { */ protected function fetch($ETag, $content) { $appstoreenabled = $this->config->getSystemValue('appstoreenabled', true); + if ((int)$this->config->getAppValue('settings', 'appstore-fetcher-lastFailure', '0') > time() - self::RETRY_AFTER_FAILURE_SECONDS) { + return []; + } if (!$appstoreenabled) { return []; @@ -107,7 +112,12 @@ abstract class Fetcher { } $client = $this->clientService->newClient(); - $response = $client->get($this->getEndpoint(), $options); + try { + $response = $client->get($this->getEndpoint(), $options); + } catch (ConnectException $e) { + $this->config->setAppValue('settings', 'appstore-fetcher-lastFailure', (string)time()); + throw $e; + } $responseJson = []; if ($response->getStatusCode() === Http::STATUS_NOT_MODIFIED) { @@ -116,6 +126,7 @@ abstract class Fetcher { $responseJson['data'] = json_decode($response->getBody(), true); $ETag = $response->getHeader('ETag'); } + $this->config->deleteAppValue('settings', 'appstore-fetcher-lastFailure'); $responseJson['timestamp'] = $this->timeFactory->getTime(); $responseJson['ncversion'] = $this->getVersion(); @@ -129,9 +140,10 @@ abstract class Fetcher { /** * Returns the array with the categories on the appstore server * + * @param bool [$allowUnstable] Allow unstable releases * @return array */ - public function get() { + public function get($allowUnstable = false) { $appstoreenabled = $this->config->getSystemValue('appstoreenabled', true); $internetavailable = $this->config->getSystemValue('has_internet_connection', true); @@ -148,7 +160,9 @@ abstract class Fetcher { // File does already exists $file = $rootFolder->getFile($this->fileName); $jsonBlob = json_decode($file->getContent(), true); - if (is_array($jsonBlob)) { + + // Always get latests apps info if $allowUnstable + if (!$allowUnstable && is_array($jsonBlob)) { // No caching when the version has been updated if (isset($jsonBlob['ncversion']) && $jsonBlob['ncversion'] === $this->getVersion()) { @@ -171,7 +185,17 @@ abstract class Fetcher { // Refresh the file content try { - $responseJson = $this->fetch($ETag, $content); + $responseJson = $this->fetch($ETag, $content, $allowUnstable); + + if (empty($responseJson)) { + return []; + } + + // Don't store the apps request file + if ($allowUnstable) { + return $responseJson['data']; + } + $file->putContent(json_encode($responseJson)); return json_decode($file->getContent(), true)['data']; } catch (ConnectException $e) { diff --git a/lib/private/App/CodeChecker/CodeChecker.php b/lib/private/App/CodeChecker/CodeChecker.php index 13d6ff887f3..9dec9c9f4d7 100644 --- a/lib/private/App/CodeChecker/CodeChecker.php +++ b/lib/private/App/CodeChecker/CodeChecker.php @@ -40,10 +40,10 @@ class CodeChecker extends BasicEmitter { public const CLASS_IMPLEMENTS_NOT_ALLOWED = 1001; public const STATIC_CALL_NOT_ALLOWED = 1002; public const CLASS_CONST_FETCH_NOT_ALLOWED = 1003; - public const CLASS_NEW_NOT_ALLOWED = 1004; - public const OP_OPERATOR_USAGE_DISCOURAGED = 1005; - public const CLASS_USE_NOT_ALLOWED = 1006; - public const CLASS_METHOD_CALL_NOT_ALLOWED = 1007; + public const CLASS_NEW_NOT_ALLOWED = 1004; + public const OP_OPERATOR_USAGE_DISCOURAGED = 1005; + public const CLASS_USE_NOT_ALLOWED = 1006; + public const CLASS_METHOD_CALL_NOT_ALLOWED = 1007; /** @var Parser */ private $parser; diff --git a/lib/private/App/CodeChecker/NodeVisitor.php b/lib/private/App/CodeChecker/NodeVisitor.php index 635f1357ffd..001a0ee6883 100644 --- a/lib/private/App/CodeChecker/NodeVisitor.php +++ b/lib/private/App/CodeChecker/NodeVisitor.php @@ -2,6 +2,7 @@ /** * @copyright Copyright (c) 2016, ownCloud, Inc. * + * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Joas Schilling <coding@schilljs.com> * @author Morris Jobke <hey@morrisjobke.de> @@ -103,7 +104,7 @@ class NodeVisitor extends NodeVisitorAbstract { public function enterNode(Node $node) { if ($this->checkEqualOperatorUsage && $node instanceof Node\Expr\BinaryOp\Equal) { - $this->errors[]= [ + $this->errors[] = [ 'disallowedToken' => '==', 'errorCode' => CodeChecker::OP_OPERATOR_USAGE_DISCOURAGED, 'line' => $node->getLine(), @@ -111,7 +112,7 @@ class NodeVisitor extends NodeVisitorAbstract { ]; } if ($this->checkEqualOperatorUsage && $node instanceof Node\Expr\BinaryOp\NotEqual) { - $this->errors[]= [ + $this->errors[] = [ 'disallowedToken' => '!=', 'errorCode' => CodeChecker::OP_OPERATOR_USAGE_DISCOURAGED, 'line' => $node->getLine(), @@ -247,7 +248,7 @@ class NodeVisitor extends NodeVisitorAbstract { $lowerName = strtolower($name); if (isset($this->blackListedClassNames[$lowerName])) { - $this->errors[]= [ + $this->errors[] = [ 'disallowedToken' => $name, 'errorCode' => $errorCode, 'line' => $node->getLine(), @@ -261,7 +262,7 @@ class NodeVisitor extends NodeVisitorAbstract { $lowerName = strtolower($name); if (isset($this->blackListedConstants[$lowerName])) { - $this->errors[]= [ + $this->errors[] = [ 'disallowedToken' => $name, 'errorCode' => CodeChecker::CLASS_CONST_FETCH_NOT_ALLOWED, 'line' => $node->getLine(), @@ -275,7 +276,7 @@ class NodeVisitor extends NodeVisitorAbstract { $lowerName = strtolower($name); if (isset($this->blackListedFunctions[$lowerName])) { - $this->errors[]= [ + $this->errors[] = [ 'disallowedToken' => $name, 'errorCode' => CodeChecker::STATIC_CALL_NOT_ALLOWED, 'line' => $node->getLine(), @@ -289,7 +290,7 @@ class NodeVisitor extends NodeVisitorAbstract { $lowerName = strtolower($name); if (isset($this->blackListedMethods[$lowerName])) { - $this->errors[]= [ + $this->errors[] = [ 'disallowedToken' => $name, 'errorCode' => CodeChecker::CLASS_METHOD_CALL_NOT_ALLOWED, 'line' => $node->getLine(), diff --git a/lib/private/App/DependencyAnalyzer.php b/lib/private/App/DependencyAnalyzer.php index 63d84cd5a43..eb114adbb48 100644 --- a/lib/private/App/DependencyAnalyzer.php +++ b/lib/private/App/DependencyAnalyzer.php @@ -6,11 +6,13 @@ * @author Bernhard Posselt <dev@bernhard-posselt.com> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Joas Schilling <coding@schilljs.com> + * @author Julius Härtl <jus@bitgrid.net> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Stefan Weil <sw@weilnetz.de> * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Valdnet <47037905+Valdnet@users.noreply.github.com> * * @license AGPL-3.0 * @@ -63,6 +65,7 @@ class DependencyAnalyzer { } return array_merge( + $this->analyzeArchitecture($dependencies), $this->analyzePhpVersion($dependencies), $this->analyzeDatabases($dependencies), $this->analyzeCommands($dependencies), @@ -166,13 +169,36 @@ class DependencyAnalyzer { } if (isset($dependencies['php']['@attributes']['min-int-size'])) { $intSize = $dependencies['php']['@attributes']['min-int-size']; - if ($intSize > $this->platform->getIntSize()*8) { + if ($intSize > $this->platform->getIntSize() * 8) { $missing[] = (string)$this->l->t('%sbit or higher PHP required.', [$intSize]); } } return $missing; } + private function analyzeArchitecture(array $dependencies) { + $missing = []; + if (!isset($dependencies['architecture'])) { + return $missing; + } + + $supportedArchitectures = $dependencies['architecture']; + if (empty($supportedArchitectures)) { + return $missing; + } + if (!is_array($supportedArchitectures)) { + $supportedArchitectures = [$supportedArchitectures]; + } + $supportedArchitectures = array_map(function ($architecture) { + return $this->getValue($architecture); + }, $supportedArchitectures); + $currentArchitecture = $this->platform->getArchitecture(); + if (!in_array($currentArchitecture, $supportedArchitectures, true)) { + $missing[] = (string)$this->l->t('The following architectures are supported: %s', [implode(', ', $supportedArchitectures)]); + } + return $missing; + } + /** * @param array $dependencies * @return array diff --git a/lib/private/App/Platform.php b/lib/private/App/Platform.php index 4a64177232f..f83e7cf8272 100644 --- a/lib/private/App/Platform.php +++ b/lib/private/App/Platform.php @@ -4,6 +4,7 @@ * * @author Bernhard Posselt <dev@bernhard-posselt.com> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Julius Härtl <jus@bitgrid.net> * @author Morris Jobke <hey@morrisjobke.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * @@ -97,4 +98,8 @@ class Platform { $repo = new PlatformRepository(); return $repo->findLibrary($name); } + + public function getArchitecture(): string { + return php_uname('m'); + } } diff --git a/lib/private/AppConfig.php b/lib/private/AppConfig.php index 97338c1895b..84305e60a12 100644 --- a/lib/private/AppConfig.php +++ b/lib/private/AppConfig.php @@ -13,6 +13,7 @@ * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> + * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license AGPL-3.0 * @@ -49,11 +50,12 @@ class AppConfig implements IAppConfig { '/^sites$/', ], 'spreed' => [ + '/^bridge_bot_password/', + '/^signaling_servers$/', '/^signaling_ticket_secret$/', - '/^turn_server_secret$/', '/^stun_servers$/', '/^turn_servers$/', - '/^signaling_servers$/', + '/^turn_server_secret$/', ], 'theming' => [ '/^imprintUrl$/', @@ -201,12 +203,9 @@ class AppConfig implements IAppConfig { $sql = $this->conn->getQueryBuilder(); $sql->update('appconfig') - ->set('configvalue', $sql->createParameter('configvalue')) - ->where($sql->expr()->eq('appid', $sql->createParameter('app'))) - ->andWhere($sql->expr()->eq('configkey', $sql->createParameter('configkey'))) - ->setParameter('configvalue', $value) - ->setParameter('app', $app) - ->setParameter('configkey', $key); + ->set('configvalue', $sql->createNamedParameter($value)) + ->where($sql->expr()->eq('appid', $sql->createNamedParameter($app))) + ->andWhere($sql->expr()->eq('configkey', $sql->createNamedParameter($key))); /* * Only limit to the existing value for non-Oracle DBs: @@ -214,9 +213,25 @@ class AppConfig implements IAppConfig { * > Large objects (LOBs) are not supported in comparison conditions. */ if (!($this->conn instanceof OracleConnection)) { - // Only update the value when it is not the same - $sql->andWhere($sql->expr()->neq('configvalue', $sql->createParameter('configvalue'))) - ->setParameter('configvalue', $value); + + /* + * Only update the value when it is not the same + * Note that NULL requires some special handling. Since comparing + * against null can have special results. + */ + + if ($value === null) { + $sql->andWhere( + $sql->expr()->isNotNull('configvalue') + ); + } else { + $sql->andWhere( + $sql->expr()->orX( + $sql->expr()->isNull('configvalue'), + $sql->expr()->neq('configvalue', $sql->createNamedParameter($value)) + ) + ); + } } $changedRow = (bool) $sql->execute(); @@ -334,13 +349,23 @@ class AppConfig implements IAppConfig { $rows = $result->fetchAll(); foreach ($rows as $row) { if (!isset($this->cache[$row['appid']])) { - $this->cache[$row['appid']] = []; + $this->cache[(string)$row['appid']] = []; } - $this->cache[$row['appid']][$row['configkey']] = $row['configvalue']; + $this->cache[(string)$row['appid']][(string)$row['configkey']] = (string)$row['configvalue']; } $result->closeCursor(); $this->configLoaded = true; } + + /** + * Clear all the cached app config values + * + * WARNING: do not use this - this is only for usage with the SCSSCacher to + * clear the memory cache of the app config + */ + public function clearCachedConfig() { + $this->configLoaded = false; + } } diff --git a/lib/private/AppFramework/App.php b/lib/private/AppFramework/App.php index 75876055d80..d6bf7bc81c3 100644 --- a/lib/private/AppFramework/App.php +++ b/lib/private/AppFramework/App.php @@ -10,6 +10,7 @@ declare(strict_types=1); * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @@ -33,6 +34,7 @@ namespace OC\AppFramework; use OC\AppFramework\DependencyInjection\DIContainer; use OC\AppFramework\Http\Dispatcher; +use OC\AppFramework\Http\Request; use OC\HintException; use OCP\AppFramework\Http; use OCP\AppFramework\Http\ICallbackResponse; @@ -59,7 +61,7 @@ class App { * the transformed app id, defaults to OCA\ * @return string the starting namespace for the app */ - public static function buildAppNamespace(string $appId, string $topNamespace='OCA\\'): string { + public static function buildAppNamespace(string $appId, string $topNamespace = 'OCA\\'): string { // Hit the cache! if (isset(self::$nameSpaceCache[$appId])) { return $topNamespace . self::$nameSpaceCache[$appId]; @@ -87,7 +89,7 @@ class App { return $topNamespace . self::$nameSpaceCache[$appId]; } - public static function getAppIdForClass(string $className, string $topNamespace='OCA\\'): ?string { + public static function getAppIdForClass(string $className, string $topNamespace = 'OCA\\'): ?string { if (strpos($className, $topNamespace) !== 0) { return null; } @@ -113,9 +115,13 @@ class App { */ public static function main(string $controllerName, string $methodName, DIContainer $container, array $urlParams = null) { if (!is_null($urlParams)) { - $container->query(IRequest::class)->setUrlParameters($urlParams); + /** @var Request $request */ + $request = $container->query(IRequest::class); + $request->setUrlParameters($urlParams); } elseif (isset($container['urlParams']) && !is_null($container['urlParams'])) { - $container->query(IRequest::class)->setUrlParameters($container['urlParams']); + /** @var Request $request */ + $request = $container->query(IRequest::class); + $request->setUrlParameters($container['urlParams']); } $appName = $container['AppName']; @@ -222,7 +228,7 @@ class App { $dispatcher = $container['Dispatcher']; - list(, , $output) = $dispatcher->dispatch($controller, $methodName); + list(, , $output) = $dispatcher->dispatch($controller, $methodName); return $output; } } diff --git a/lib/private/AppFramework/Bootstrap/BootContext.php b/lib/private/AppFramework/Bootstrap/BootContext.php index 45aaf167e75..0acfaad7ab6 100644 --- a/lib/private/AppFramework/Bootstrap/BootContext.php +++ b/lib/private/AppFramework/Bootstrap/BootContext.php @@ -5,7 +5,8 @@ declare(strict_types=1); /** * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * - * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Morris Jobke <hey@morrisjobke.de> * * @license GNU AGPL version 3 or any later version * @@ -20,7 +21,8 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * */ namespace OC\AppFramework\Bootstrap; diff --git a/lib/private/AppFramework/Bootstrap/Coordinator.php b/lib/private/AppFramework/Bootstrap/Coordinator.php index 358e71d7854..06a17e5242b 100644 --- a/lib/private/AppFramework/Bootstrap/Coordinator.php +++ b/lib/private/AppFramework/Bootstrap/Coordinator.php @@ -5,7 +5,10 @@ declare(strict_types=1); /** * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * - * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Julius Härtl <jus@bitgrid.net> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <robin@icewind.nl> * * @license GNU AGPL version 3 or any later version * @@ -20,7 +23,8 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * */ namespace OC\AppFramework\Bootstrap; @@ -34,7 +38,6 @@ use OCP\Dashboard\IManager; use OCP\EventDispatcher\IEventDispatcher; use OCP\ILogger; use OCP\IServerContainer; -use RuntimeException; use Throwable; use function class_exists; use function class_implements; @@ -60,6 +63,9 @@ class Coordinator { /** @var RegistrationContext|null */ private $registrationContext; + /** @var string[] */ + private $bootedApps = []; + public function __construct(IServerContainer $container, Registry $registry, IManager $dashboardManager, @@ -72,14 +78,23 @@ class Coordinator { $this->logger = $logger; } - public function runRegistration(): void { - if ($this->registrationContext !== null) { - throw new RuntimeException('Registration has already been run'); - } + public function runInitialRegistration(): void { + $this->registerApps(OC_App::getEnabledApps()); + } + + public function runLazyRegistration(string $appId): void { + $this->registerApps([$appId]); + } - $this->registrationContext = new RegistrationContext($this->logger); + /** + * @param string[] $appIds + */ + private function registerApps(array $appIds): void { + if ($this->registrationContext === null) { + $this->registrationContext = new RegistrationContext($this->logger); + } $apps = []; - foreach (OC_App::getEnabledApps() as $appId) { + foreach ($appIds as $appId) { /* * First, we have to enable the app's autoloader * @@ -134,6 +149,11 @@ class Coordinator { } public function bootApp(string $appId): void { + if (isset($this->bootedApps[$appId])) { + return; + } + $this->bootedApps[$appId] = true; + $appNameSpace = App::buildAppNamespace($appId); $applicationClassName = $appNameSpace . '\\AppInfo\\Application'; if (!class_exists($applicationClassName)) { diff --git a/lib/private/AppFramework/Bootstrap/FunctionInjector.php b/lib/private/AppFramework/Bootstrap/FunctionInjector.php index 25c1fbc0be6..3c20701618b 100644 --- a/lib/private/AppFramework/Bootstrap/FunctionInjector.php +++ b/lib/private/AppFramework/Bootstrap/FunctionInjector.php @@ -5,7 +5,7 @@ declare(strict_types=1); /** * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * - * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Christoph Wurst <christoph@winzerhof-wurst.at> * * @license GNU AGPL version 3 or any later version * @@ -20,7 +20,8 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * */ namespace OC\AppFramework\Bootstrap; diff --git a/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/lib/private/AppFramework/Bootstrap/RegistrationContext.php index efcf9175b97..d6271f5ce45 100644 --- a/lib/private/AppFramework/Bootstrap/RegistrationContext.php +++ b/lib/private/AppFramework/Bootstrap/RegistrationContext.php @@ -5,7 +5,11 @@ declare(strict_types=1); /** * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * - * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> + * @author Julius Härtl <jus@bitgrid.net> + * @author Robin Windey <ro.windey@gmail.com> + * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version * @@ -20,7 +24,8 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * */ namespace OC\AppFramework\Bootstrap; @@ -66,6 +71,12 @@ class RegistrationContext { /** @var array[] */ private $alternativeLogins = []; + /** @var array[] */ + private $initialStates = []; + + /** @var array[] */ + private $wellKnownHandlers = []; + /** @var ILogger */ private $logger; @@ -100,10 +111,10 @@ class RegistrationContext { ); } - public function registerDashboardPanel(string $panelClass): void { + public function registerDashboardWidget(string $widgetClass): void { $this->context->registerDashboardPanel( $this->appId, - $panelClass + $widgetClass ); } @@ -161,6 +172,20 @@ class RegistrationContext { $class ); } + + public function registerInitialStateProvider(string $class): void { + $this->context->registerInitialState( + $this->appId, + $class + ); + } + + public function registerWellKnownHandler(string $class): void { + $this->context->registerWellKnown( + $this->appId, + $class + ); + } }; } @@ -190,7 +215,7 @@ class RegistrationContext { "appId" => $appId, "name" => $name, "factory" => $factory, - "sharred" => $shared, + "shared" => $shared, ]; } @@ -240,11 +265,25 @@ class RegistrationContext { ]; } + public function registerInitialState(string $appId, string $class): void { + $this->initialStates[] = [ + 'appId' => $appId, + 'class' => $class, + ]; + } + + public function registerWellKnown(string $appId, string $class): void { + $this->wellKnownHandlers[] = [ + 'appId' => $appId, + 'class' => $class, + ]; + } + /** * @param App[] $apps */ public function delegateCapabilityRegistrations(array $apps): void { - foreach ($this->capabilities as $registration) { + while (($registration = array_shift($this->capabilities)) !== null) { try { $apps[$registration['appId']] ->getContainer() @@ -263,7 +302,7 @@ class RegistrationContext { * @param App[] $apps */ public function delegateCrashReporterRegistrations(array $apps, Registry $registry): void { - foreach ($this->crashReporters as $registration) { + while (($registration = array_shift($this->crashReporters)) !== null) { try { $registry->registerLazy($registration['class']); } catch (Throwable $e) { @@ -280,9 +319,9 @@ class RegistrationContext { * @param App[] $apps */ public function delegateDashboardPanelRegistrations(array $apps, IManager $dashboardManager): void { - foreach ($this->dashboardPanels as $panel) { + while (($panel = array_shift($this->dashboardPanels)) !== null) { try { - $dashboardManager->lazyRegisterPanel($panel['class']); + $dashboardManager->lazyRegisterWidget($panel['class']); } catch (Throwable $e) { $appId = $panel['appId']; $this->logger->logException($e, [ @@ -294,7 +333,7 @@ class RegistrationContext { } public function delegateEventListenerRegistrations(IEventDispatcher $eventDispatcher): void { - foreach ($this->eventListeners as $registration) { + while (($registration = array_shift($this->eventListeners)) !== null) { try { if (isset($registration['priority'])) { $eventDispatcher->addServiceListener( @@ -322,7 +361,7 @@ class RegistrationContext { * @param App[] $apps */ public function delegateContainerRegistrations(array $apps): void { - foreach ($this->services as $registration) { + while (($registration = array_shift($this->services)) !== null) { try { /** * Register the service and convert the callable into a \Closure if necessary @@ -382,7 +421,7 @@ class RegistrationContext { * @param App[] $apps */ public function delegateMiddlewareRegistrations(array $apps): void { - foreach ($this->middlewares as $middleware) { + while (($middleware = array_shift($this->middlewares)) !== null) { try { $apps[$middleware['appId']] ->getContainer() @@ -410,4 +449,18 @@ class RegistrationContext { public function getAlternativeLogins(): array { return $this->alternativeLogins; } + + /** + * @erturn array[] + */ + public function getInitialStates(): array { + return $this->initialStates; + } + + /** + * @return array[] + */ + public function getWellKnownHandlers(): array { + return $this->wellKnownHandlers; + } } diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index cd426d64699..3ef816f503e 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -6,7 +6,6 @@ * @author Bernhard Posselt <dev@bernhard-posselt.com> * @author Bjoern Schiessle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Joas Schilling <coding@schilljs.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Lukas Reschke <lukas@statuscode.ch> @@ -62,6 +61,7 @@ use OCP\Files\Folder; use OCP\Files\IAppData; use OCP\Group\ISubAdmin; use OCP\IConfig; +use OCP\IDBConnection; use OCP\IInitialStateService; use OCP\IL10N; use OCP\ILogger; @@ -181,7 +181,10 @@ class DIContainer extends SimpleContainer implements IAppContainer { $c->get('Protocol'), $c->get(MiddlewareDispatcher::class), $c->get(IControllerMethodReflector::class), - $c->get(IRequest::class) + $c->get(IRequest::class), + $c->get(IConfig::class), + $c->get(IDBConnection::class), + $c->get(LoggerInterface::class) ); }); diff --git a/lib/private/AppFramework/Http.php b/lib/private/AppFramework/Http.php index 88e49816eb9..828efe390e7 100644 --- a/lib/private/AppFramework/Http.php +++ b/lib/private/AppFramework/Http.php @@ -41,7 +41,7 @@ class Http extends BaseHttp { * @param array $server $_SERVER * @param string $protocolVersion the http version to use defaults to HTTP/1.1 */ - public function __construct($server, $protocolVersion='HTTP/1.1') { + public function __construct($server, $protocolVersion = 'HTTP/1.1') { $this->server = $server; $this->protocolVersion = $protocolVersion; diff --git a/lib/private/AppFramework/Http/Dispatcher.php b/lib/private/AppFramework/Http/Dispatcher.php index 3892bb667f0..47e6650c999 100644 --- a/lib/private/AppFramework/Http/Dispatcher.php +++ b/lib/private/AppFramework/Http/Dispatcher.php @@ -7,6 +7,7 @@ declare(strict_types=1); * * @author Bernhard Posselt <dev@bernhard-posselt.com> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * @author Julius Härtl <jus@bitgrid.net> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> @@ -35,11 +36,14 @@ namespace OC\AppFramework\Http; use OC\AppFramework\Http; use OC\AppFramework\Middleware\MiddlewareDispatcher; use OC\AppFramework\Utility\ControllerMethodReflector; - +use OC\DB\Connection; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\Response; +use OCP\IConfig; +use OCP\IDBConnection; use OCP\IRequest; +use Psr\Log\LoggerInterface; /** * Class to dispatch the request to the middleware dispatcher @@ -58,6 +62,15 @@ class Dispatcher { /** @var IRequest */ private $request; + /** @var IConfig */ + private $config; + + /** @var IDBConnection|Connection */ + private $connection; + + /** @var LoggerInterface */ + private $logger; + /** * @param Http $protocol the http protocol with contains all status headers * @param MiddlewareDispatcher $middlewareDispatcher the dispatcher which @@ -65,15 +78,24 @@ class Dispatcher { * @param ControllerMethodReflector $reflector the reflector that is used to inject * the arguments for the controller * @param IRequest $request the incoming request + * @param IConfig $config + * @param IDBConnection $connection + * @param LoggerInterface $logger */ public function __construct(Http $protocol, MiddlewareDispatcher $middlewareDispatcher, ControllerMethodReflector $reflector, - IRequest $request) { + IRequest $request, + IConfig $config, + IDBConnection $connection, + LoggerInterface $logger) { $this->protocol = $protocol; $this->middlewareDispatcher = $middlewareDispatcher; $this->reflector = $reflector; $this->request = $request; + $this->config = $config; + $this->connection = $connection; + $this->logger = $logger; } @@ -97,8 +119,36 @@ class Dispatcher { $this->middlewareDispatcher->beforeController($controller, $methodName); + + $databaseStatsBefore = []; + if ($this->config->getSystemValueBool('debug', false)) { + $databaseStatsBefore = $this->connection->getStats(); + } + $response = $this->executeController($controller, $methodName); + if (!empty($databaseStatsBefore)) { + $databaseStatsAfter = $this->connection->getStats(); + $numBuilt = $databaseStatsAfter['built'] - $databaseStatsBefore['built']; + $numExecuted = $databaseStatsAfter['executed'] - $databaseStatsBefore['executed']; + + if ($numBuilt > 50) { + $this->logger->debug('Controller {class}::{method} created {count} QueryBuilder objects, please check if they are created inside a loop by accident.' , [ + 'class' => (string) get_class($controller), + 'method' => $methodName, + 'count' => $numBuilt, + ]); + } + + if ($numExecuted > 100) { + $this->logger->warning('Controller {class}::{method} executed {count} queries.' , [ + 'class' => (string) get_class($controller), + 'method' => $methodName, + 'count' => $numExecuted, + ]); + } + } + // if an exception appears, the middleware checks if it can handle the // exception and creates a response. If no response is created, it is // assumed that theres no middleware who can handle it and the error is diff --git a/lib/private/AppFramework/Http/Output.php b/lib/private/AppFramework/Http/Output.php index 8777c1970a6..d2e8cd338ba 100644 --- a/lib/private/AppFramework/Http/Output.php +++ b/lib/private/AppFramework/Http/Output.php @@ -3,8 +3,10 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Bernhard Posselt <dev@bernhard-posselt.com> + * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Lukas Reschke <lukas@statuscode.ch> * @author Robin Appelman <robin@icewind.nl> + * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Stefan Weil <sw@weilnetz.de> * * @license AGPL-3.0 @@ -95,17 +97,13 @@ class Output implements IOutput { public function setCookie($name, $value, $expire, $path, $domain, $secure, $httpOnly, $sameSite = 'Lax') { $path = $this->webRoot ? : '/'; - if (PHP_VERSION_ID < 70300) { - setcookie($name, $value, $expire, $path, $domain, $secure, $httpOnly); - } else { - setcookie($name, $value, [ - 'expires' => $expire, - 'path' => $path, - 'domain' => $domain, - 'secure' => $secure, - 'httponly' => $httpOnly, - 'samesite' => $sameSite - ]); - } + setcookie($name, $value, [ + 'expires' => $expire, + 'path' => $path, + 'domain' => $domain, + 'secure' => $secure, + 'httponly' => $httpOnly, + 'samesite' => $sameSite + ]); } } diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index d0ecdbea6c7..deabbe5206a 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -10,6 +10,7 @@ declare(strict_types=1); * @author Bernhard Posselt <dev@bernhard-posselt.com> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Daniel Kesselberg <mail@danielkesselberg.de> + * @author Georg Ehrke <oc.list@georgehrke.com> * @author Joas Schilling <coding@schilljs.com> * @author Juan Pablo Villafáñez <jvillafanez@solidgear.es> * @author Julius Härtl <jus@bitgrid.net> @@ -22,7 +23,7 @@ declare(strict_types=1); * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Thomas Tanghus <thomas@tanghus.net> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -74,7 +75,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { // Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent public const USER_AGENT_ANDROID_MOBILE_CHROME = '#Android.*Chrome/[.0-9]*#'; public const USER_AGENT_FREEBOX = '#^Mozilla/5\.0$#'; - public const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost|::1)$/'; + public const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost|\[::1\])$/'; /** * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_IOS instead @@ -135,8 +136,8 @@ class Request implements \ArrayAccess, \Countable, IRequest { * @param string $stream * @see http://www.php.net/manual/en/reserved.variables.php */ - public function __construct(array $vars= [], - ISecureRandom $secureRandom = null, + public function __construct(array $vars, + ISecureRandom $secureRandom, IConfig $config, CsrfTokenManager $csrfTokenManager = null, string $stream = 'php://input') { @@ -826,7 +827,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { */ public function getScriptName(): string { $name = $this->server['SCRIPT_NAME']; - $overwriteWebRoot = $this->config->getSystemValue('overwritewebroot'); + $overwriteWebRoot = $this->config->getSystemValue('overwritewebroot'); if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) { // FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous $serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -\strlen('lib/private/appframework/http/'))); diff --git a/lib/private/AppFramework/Logger.php b/lib/private/AppFramework/Logger.php index 062a705efc4..16f4dd36e78 100644 --- a/lib/private/AppFramework/Logger.php +++ b/lib/private/AppFramework/Logger.php @@ -5,6 +5,7 @@ declare(strict_types=1); /** * @copyright Copyright (c) 2018, Roeland Jago Douma <roeland@famdouma.nl> * + * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version diff --git a/lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php b/lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php index 9eafd5b740c..28f322f42b7 100644 --- a/lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php +++ b/lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php @@ -5,6 +5,7 @@ declare(strict_types=1); /** * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl> * + * @author Julius Härtl <jus@bitgrid.net> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @@ -70,7 +71,7 @@ class AdditionalScriptsMiddleware extends Middleware { $isLoggedIn = false; } - $this->dispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($isLoggedIn)); + $this->dispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($isLoggedIn, $response)); } return $response; diff --git a/lib/private/AppFramework/Middleware/CompressionMiddleware.php b/lib/private/AppFramework/Middleware/CompressionMiddleware.php index 6956be76693..f6d3839891e 100644 --- a/lib/private/AppFramework/Middleware/CompressionMiddleware.php +++ b/lib/private/AppFramework/Middleware/CompressionMiddleware.php @@ -1,9 +1,11 @@ <?php declare(strict_types=1); + /** * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> * + * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version @@ -19,7 +21,7 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ diff --git a/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php b/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php index 270058dc554..577931b8222 100644 --- a/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php +++ b/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php @@ -116,7 +116,7 @@ class MiddlewareDispatcher { * @throws \Exception the passed in exception if it can't handle it */ public function afterException(Controller $controller, string $methodName, \Exception $exception): Response { - for ($i=$this->middlewareCounter-1; $i>=0; $i--) { + for ($i = $this->middlewareCounter - 1; $i >= 0; $i--) { $middleware = $this->middlewares[$i]; try { return $middleware->afterException($controller, $methodName, $exception); @@ -139,7 +139,7 @@ class MiddlewareDispatcher { * @return Response a Response object */ public function afterController(Controller $controller, string $methodName, Response $response): Response { - for ($i= \count($this->middlewares)-1; $i>=0; $i--) { + for ($i = \count($this->middlewares) - 1; $i >= 0; $i--) { $middleware = $this->middlewares[$i]; $response = $middleware->afterController($controller, $methodName, $response); } @@ -158,7 +158,7 @@ class MiddlewareDispatcher { * @return string the output that should be printed */ public function beforeOutput(Controller $controller, string $methodName, string $output): string { - for ($i= \count($this->middlewares)-1; $i>=0; $i--) { + for ($i = \count($this->middlewares) - 1; $i >= 0; $i--) { $middleware = $this->middlewares[$i]; $output = $middleware->beforeOutput($controller, $methodName, $output); } diff --git a/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php b/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php index e34310b4116..083761dcf02 100644 --- a/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php +++ b/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php @@ -1,6 +1,7 @@ <?php declare(strict_types=1); + /** * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> * @@ -19,7 +20,7 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ diff --git a/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php b/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php index 398c2f3f3a4..31a4791845e 100644 --- a/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php @@ -1,8 +1,12 @@ <?php + +declare(strict_types=1); + /** * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * * @license GNU AGPL version 3 or any later version @@ -26,9 +30,15 @@ namespace OC\AppFramework\Middleware\Security; use OC\AppFramework\Utility\ControllerMethodReflector; use OC\Security\Bruteforce\Throttler; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http; use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Http\TooManyRequestsResponse; use OCP\AppFramework\Middleware; +use OCP\AppFramework\OCS\OCSException; +use OCP\AppFramework\OCSController; use OCP\IRequest; +use OCP\Security\Bruteforce\MaxDelayReached; /** * Class BruteForceMiddleware performs the bruteforce protection for controllers @@ -66,7 +76,7 @@ class BruteForceMiddleware extends Middleware { if ($this->reflector->hasAnnotation('BruteForceProtection')) { $action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action'); - $this->throttler->sleepDelay($this->request->getRemoteAddress(), $action); + $this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), $action); } } @@ -83,4 +93,23 @@ class BruteForceMiddleware extends Middleware { return parent::afterController($controller, $methodName, $response); } + + /** + * @param Controller $controller + * @param string $methodName + * @param \Exception $exception + * @throws \Exception + * @return Response + */ + public function afterException($controller, $methodName, \Exception $exception): Response { + if ($exception instanceof MaxDelayReached) { + if ($controller instanceof OCSController) { + throw new OCSException($exception->getMessage(), Http::STATUS_TOO_MANY_REQUESTS); + } + + return new TooManyRequestsResponse(); + } + + throw $exception; + } } diff --git a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php index af6d3de6570..765311858de 100644 --- a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php @@ -145,7 +145,7 @@ class CORSMiddleware extends Middleware { */ public function afterException($controller, $methodName, \Exception $exception) { if ($exception instanceof SecurityException) { - $response = new JSONResponse(['message' => $exception->getMessage()]); + $response = new JSONResponse(['message' => $exception->getMessage()]); if ($exception->getCode() !== 0) { $response->setStatus($exception->getCode()); } else { diff --git a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php index 7a358f6ebf2..48d386e749e 100644 --- a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php @@ -48,7 +48,7 @@ class SameSiteCookieMiddleware extends Middleware { public function beforeController($controller, $methodName) { $requestUri = $this->request->getScriptName(); $processingScript = explode('/', $requestUri); - $processingScript = $processingScript[count($processingScript)-1]; + $processingScript = $processingScript[count($processingScript) - 1]; if ($processingScript !== 'index.php') { return; diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index 089f3589454..76665f8998f 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -10,6 +10,7 @@ declare(strict_types=1); * @author Bjoern Schiessle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Daniel Kesselberg <mail@danielkesselberg.de> + * @author Holger Hees <holger.hees@gmail.com> * @author Joas Schilling <coding@schilljs.com> * @author Julien Veyssier <eneiluj@posteo.net> * @author Lukas Reschke <lukas@statuscode.ch> diff --git a/lib/private/AppFramework/OCS/V1Response.php b/lib/private/AppFramework/OCS/V1Response.php index 5a3e4090425..97a3acce681 100644 --- a/lib/private/AppFramework/OCS/V1Response.php +++ b/lib/private/AppFramework/OCS/V1Response.php @@ -2,6 +2,7 @@ /** * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl> * + * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version @@ -35,7 +36,7 @@ class V1Response extends BaseResponse { * @return int */ public function getStatus() { - $status = parent::getStatus(); + $status = parent::getStatus(); if ($status === Http::STATUS_FORBIDDEN || $status === API::RESPOND_UNAUTHORISED) { return Http::STATUS_UNAUTHORIZED; } diff --git a/lib/private/AppFramework/OCS/V2Response.php b/lib/private/AppFramework/OCS/V2Response.php index b6863262d81..3d1868857ce 100644 --- a/lib/private/AppFramework/OCS/V2Response.php +++ b/lib/private/AppFramework/OCS/V2Response.php @@ -36,7 +36,7 @@ class V2Response extends BaseResponse { * @return int */ public function getStatus() { - $status = parent::getStatus(); + $status = parent::getStatus(); if ($status === API::RESPOND_UNAUTHORISED) { return Http::STATUS_UNAUTHORIZED; } elseif ($status === API::RESPOND_NOT_FOUND) { diff --git a/lib/private/AppFramework/Routing/RouteConfig.php b/lib/private/AppFramework/Routing/RouteConfig.php index 1921ce65128..b382e7eb4b0 100644 --- a/lib/private/AppFramework/Routing/RouteConfig.php +++ b/lib/private/AppFramework/Routing/RouteConfig.php @@ -32,7 +32,7 @@ declare(strict_types=1); namespace OC\AppFramework\Routing; use OC\AppFramework\DependencyInjection\DIContainer; -use OCP\Route\IRouter; +use OC\Route\Router; /** * Class RouteConfig @@ -42,7 +42,7 @@ class RouteConfig { /** @var DIContainer */ private $container; - /** @var IRouter */ + /** @var Router */ private $router; /** @var array */ @@ -65,11 +65,11 @@ class RouteConfig { /** * @param \OC\AppFramework\DependencyInjection\DIContainer $container - * @param \OCP\Route\IRouter $router + * @param \OC\Route\Router $router * @param array $routes * @internal param $appName */ - public function __construct(DIContainer $container, IRouter $router, $routes) { + public function __construct(DIContainer $container, Router $router, $routes) { $this->routes = $routes; $this->container = $container; $this->router = $router; diff --git a/lib/private/AppFramework/Routing/RouteParser.php b/lib/private/AppFramework/Routing/RouteParser.php new file mode 100644 index 00000000000..8511ff9ee39 --- /dev/null +++ b/lib/private/AppFramework/Routing/RouteParser.php @@ -0,0 +1,263 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2016, ownCloud, Inc. + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\AppFramework\Routing; + +use OC\Route\Route; +use Symfony\Component\Routing\RouteCollection; + +class RouteParser { + /** @var string[] */ + private $controllerNameCache = []; + + private const rootUrlApps = [ + 'cloud_federation_api', + 'core', + 'files_sharing', + 'files', + 'settings', + 'spreed', + ]; + + public function parseDefaultRoutes(array $routes, string $appName): RouteCollection { + $collection = $this->processIndexRoutes($routes, $appName); + $collection->addCollection($this->processIndexResources($routes, $appName)); + + return $collection; + } + + public function parseOCSRoutes(array $routes, string $appName): RouteCollection { + $collection = $this->processOCS($routes, $appName); + $collection->addCollection($this->processOCSResources($routes, $appName)); + + return $collection; + } + + private function processOCS(array $routes, string $appName): RouteCollection { + $collection = new RouteCollection(); + $ocsRoutes = $routes['ocs'] ?? []; + foreach ($ocsRoutes as $ocsRoute) { + $result = $this->processRoute($ocsRoute, $appName, 'ocs.'); + + $collection->add($result[0], $result[1]); + } + + return $collection; + } + + /** + * Creates one route base on the give configuration + * @param array $routes + * @throws \UnexpectedValueException + */ + private function processIndexRoutes(array $routes, string $appName): RouteCollection { + $collection = new RouteCollection(); + $simpleRoutes = $routes['routes'] ?? []; + foreach ($simpleRoutes as $simpleRoute) { + $result = $this->processRoute($simpleRoute, $appName); + + $collection->add($result[0], $result[1]); + } + + return $collection; + } + + private function processRoute(array $route, string $appName, string $routeNamePrefix = ''): array { + $name = $route['name']; + $postfix = $route['postfix'] ?? ''; + $root = $this->buildRootPrefix($route, $appName, $routeNamePrefix); + + $url = $root . '/' . ltrim($route['url'], '/'); + $verb = strtoupper($route['verb'] ?? 'GET'); + + $split = explode('#', $name, 2); + if (count($split) !== 2) { + throw new \UnexpectedValueException('Invalid route name'); + } + list($controller, $action) = $split; + + $controllerName = $this->buildControllerName($controller); + $actionName = $this->buildActionName($action); + + $routeName = $routeNamePrefix . $appName . '.' . $controller . '.' . $action . $postfix; + + $routeObject = new Route($url); + $routeObject->method($verb); + + // optionally register requirements for route. This is used to + // tell the route parser how url parameters should be matched + if (array_key_exists('requirements', $route)) { + $routeObject->requirements($route['requirements']); + } + + // optionally register defaults for route. This is used to + // tell the route parser how url parameters should be default valued + $defaults = []; + if (array_key_exists('defaults', $route)) { + $defaults = $route['defaults']; + } + + $defaults['caller'] = [$appName, $controllerName, $actionName]; + $routeObject->defaults($defaults); + + return [$routeName, $routeObject]; + } + + /** + * For a given name and url restful OCS routes are created: + * - index + * - show + * - create + * - update + * - destroy + * + * @param array $routes + */ + private function processOCSResources(array $routes, string $appName): RouteCollection { + return $this->processResources($routes['ocs-resources'] ?? [], $appName, 'ocs.'); + } + + /** + * For a given name and url restful routes are created: + * - index + * - show + * - create + * - update + * - destroy + * + * @param array $routes + */ + private function processIndexResources(array $routes, string $appName): RouteCollection { + return $this->processResources($routes['resources'] ?? [], $appName); + } + + /** + * For a given name and url restful routes are created: + * - index + * - show + * - create + * - update + * - destroy + * + * @param array $resources + * @param string $routeNamePrefix + */ + private function processResources(array $resources, string $appName, string $routeNamePrefix = ''): RouteCollection { + // declaration of all restful actions + $actions = [ + ['name' => 'index', 'verb' => 'GET', 'on-collection' => true], + ['name' => 'show', 'verb' => 'GET'], + ['name' => 'create', 'verb' => 'POST', 'on-collection' => true], + ['name' => 'update', 'verb' => 'PUT'], + ['name' => 'destroy', 'verb' => 'DELETE'], + ]; + + $collection = new RouteCollection(); + foreach ($resources as $resource => $config) { + $root = $this->buildRootPrefix($config, $appName, $routeNamePrefix); + + // the url parameter used as id to the resource + foreach ($actions as $action) { + $url = $root . '/' . ltrim($config['url'], '/'); + $method = $action['name']; + + $verb = strtoupper($action['verb'] ?? 'GET'); + $collectionAction = $action['on-collection'] ?? false; + if (!$collectionAction) { + $url .= '/{id}'; + } + + $controller = $resource; + + $controllerName = $this->buildControllerName($controller); + $actionName = $this->buildActionName($method); + + $routeName = $routeNamePrefix . $appName . '.' . strtolower($resource) . '.' . $method; + + $route = new Route($url); + $route->method($verb); + + $route->defaults(['caller' => [$appName, $controllerName, $actionName]]); + + $collection->add($routeName, $route); + } + } + + return $collection; + } + + private function buildRootPrefix(array $route, string $appName, string $routeNamePrefix): string { + $defaultRoot = $appName === 'core' ? '' : '/apps/' . $appName; + $root = $route['root'] ?? $defaultRoot; + + if ($routeNamePrefix !== '') { + // In OCS all apps are whitelisted + return $root; + } + + if (!\in_array($appName, self::rootUrlApps, true)) { + // Only allow root URLS for some apps + return $defaultRoot; + } + + return $root; + } + + /** + * Based on a given route name the controller name is generated + * @param string $controller + * @return string + */ + private function buildControllerName(string $controller): string { + if (!isset($this->controllerNameCache[$controller])) { + $this->controllerNameCache[$controller] = $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller'; + } + return $this->controllerNameCache[$controller]; + } + + /** + * Based on the action part of the route name the controller method name is generated + * @param string $action + * @return string + */ + private function buildActionName(string $action): string { + return $this->underScoreToCamelCase($action); + } + + /** + * Underscored strings are converted to camel case strings + * @param string $str + * @return string + */ + private function underScoreToCamelCase(string $str): string { + $pattern = '/_[a-z]?/'; + return preg_replace_callback( + $pattern, + function ($matches) { + return strtoupper(ltrim($matches[0], '_')); + }, + $str); + } +} diff --git a/lib/private/AppFramework/ScopedPsrLogger.php b/lib/private/AppFramework/ScopedPsrLogger.php index d0ace1790c9..5f4872efd97 100644 --- a/lib/private/AppFramework/ScopedPsrLogger.php +++ b/lib/private/AppFramework/ScopedPsrLogger.php @@ -5,7 +5,8 @@ declare(strict_types=1); /** * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * - * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Maxence Lange <maxence@artificial-owl.com> * * @license GNU AGPL version 3 or any later version * @@ -20,7 +21,8 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * */ namespace OC\AppFramework; @@ -140,6 +142,7 @@ class ScopedPsrLogger implements LoggerInterface { public function log($level, $message, array $context = []) { $this->inner->log( + $level, $message, array_merge( [ diff --git a/lib/private/AppFramework/Services/AppConfig.php b/lib/private/AppFramework/Services/AppConfig.php index eaca647f438..37e73fb55b1 100644 --- a/lib/private/AppFramework/Services/AppConfig.php +++ b/lib/private/AppFramework/Services/AppConfig.php @@ -1,9 +1,11 @@ <?php declare(strict_types=1); + /** * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> * + * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version @@ -19,7 +21,7 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ diff --git a/lib/private/AppFramework/Services/InitialState.php b/lib/private/AppFramework/Services/InitialState.php index fd076bbebf1..25c7339f1e7 100644 --- a/lib/private/AppFramework/Services/InitialState.php +++ b/lib/private/AppFramework/Services/InitialState.php @@ -1,6 +1,7 @@ <?php declare(strict_types=1); + /** * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> * @@ -19,7 +20,7 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ diff --git a/lib/private/AppFramework/Utility/ControllerMethodReflector.php b/lib/private/AppFramework/Utility/ControllerMethodReflector.php index 81ae7407120..77417974d9a 100644 --- a/lib/private/AppFramework/Utility/ControllerMethodReflector.php +++ b/lib/private/AppFramework/Utility/ControllerMethodReflector.php @@ -9,6 +9,7 @@ declare(strict_types=1); * @author Bjoern Schiessle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Daniel Kesselberg <mail@danielkesselberg.de> + * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Olivier Paroz <github@oparoz.com> diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php index c17578183ac..f73e09e645e 100644 --- a/lib/private/AppFramework/Utility/SimpleContainer.php +++ b/lib/private/AppFramework/Utility/SimpleContainer.php @@ -7,7 +7,6 @@ * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> @@ -74,13 +73,13 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { } return $class->newInstanceArgs(array_map(function (ReflectionParameter $parameter) { - $parameterClass = $parameter->getClass(); + $parameterType = $parameter->getType(); + + $resolveName = $parameter->getName(); // try to find out if it is a class or a simple parameter - if ($parameterClass === null) { - $resolveName = $parameter->getName(); - } else { - $resolveName = $parameterClass->name; + if ($parameterType !== null && !$parameterType->isBuiltin()) { + $resolveName = $parameterType->getName(); } try { @@ -92,7 +91,7 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { return $parameter->getDefaultValue(); } - if ($parameterClass !== null) { + if ($parameterType !== null && !$parameterType->isBuiltin()) { $resolveName = $parameter->getName(); return $this->query($resolveName); } diff --git a/lib/private/Archive/Archive.php b/lib/private/Archive/Archive.php index ee27009ca83..633e5e8ab0e 100644 --- a/lib/private/Archive/Archive.php +++ b/lib/private/Archive/Archive.php @@ -47,7 +47,7 @@ abstract class Archive { * @param string $source either a local file or string data * @return bool */ - abstract public function addFile($path, $source=''); + abstract public function addFile($path, $source = ''); /** * rename a file or folder in the archive * @param string $source diff --git a/lib/private/Archive/TAR.php b/lib/private/Archive/TAR.php index 0d4103299ce..a9dd28b389b 100644 --- a/lib/private/Archive/TAR.php +++ b/lib/private/Archive/TAR.php @@ -5,6 +5,7 @@ * @author Bart Visscher <bartv@thisnet.nl> * @author Christopher Schäpers <kondou@ts.unde.re> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Joas Schilling <coding@schilljs.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Morris Jobke <hey@morrisjobke.de> @@ -381,4 +382,14 @@ class TAR extends Archive { $types = [null, 'gz', 'bz']; $this->tar = new \Archive_Tar($this->path, $types[self::getTarType($this->path)]); } + + /** + * Get error object from archive_tar. + */ + public function getError(): ?\PEAR_Error { + if ($this->tar instanceof \Archive_Tar && $this->tar->error_object instanceof \PEAR_Error) { + return $this->tar->error_object; + } + return null; + } } diff --git a/lib/private/Archive/ZIP.php b/lib/private/Archive/ZIP.php index 31aea420a3d..fd34f4d4cea 100644 --- a/lib/private/Archive/ZIP.php +++ b/lib/private/Archive/ZIP.php @@ -39,15 +39,15 @@ class ZIP extends Archive { /** * @var \ZipArchive zip */ - private $zip=null; + private $zip = null; private $path; /** * @param string $source */ public function __construct($source) { - $this->path=$source; - $this->zip=new \ZipArchive(); + $this->path = $source; + $this->zip = new \ZipArchive(); if ($this->zip->open($source, \ZipArchive::CREATE)) { } else { \OCP\Util::writeLog('files_archive', 'Error while opening archive '.$source, ILogger::WARN); @@ -67,11 +67,11 @@ class ZIP extends Archive { * @param string $source either a local file or string data * @return bool */ - public function addFile($path, $source='') { - if ($source and $source[0]=='/' and file_exists($source)) { - $result=$this->zip->addFile($source, $path); + public function addFile($path, $source = '') { + if ($source and $source[0] == '/' and file_exists($source)) { + $result = $this->zip->addFile($source, $path); } else { - $result=$this->zip->addFromString($path, $source); + $result = $this->zip->addFromString($path, $source); } if ($result) { $this->zip->close();//close and reopen to save the zip @@ -86,8 +86,8 @@ class ZIP extends Archive { * @return boolean|null */ public function rename($source, $dest) { - $source=$this->stripPath($source); - $dest=$this->stripPath($dest); + $source = $this->stripPath($source); + $dest = $this->stripPath($dest); $this->zip->renameName($source, $dest); } /** @@ -96,7 +96,7 @@ class ZIP extends Archive { * @return int */ public function filesize($path) { - $stat=$this->zip->statName($path); + $stat = $this->zip->statName($path); return $stat['size']; } /** @@ -113,13 +113,13 @@ class ZIP extends Archive { * @return array */ public function getFolder($path) { - $files=$this->getFiles(); - $folderContent=[]; - $pathLength=strlen($path); + $files = $this->getFiles(); + $folderContent = []; + $pathLength = strlen($path); foreach ($files as $file) { - if (substr($file, 0, $pathLength)==$path and $file!=$path) { - if (strrpos(substr($file, 0, -1), '/')<=$pathLength) { - $folderContent[]=substr($file, $pathLength); + if (substr($file, 0, $pathLength) == $path and $file != $path) { + if (strrpos(substr($file, 0, -1), '/') <= $pathLength) { + $folderContent[] = substr($file, $pathLength); } } } @@ -130,10 +130,10 @@ class ZIP extends Archive { * @return array */ public function getFiles() { - $fileCount=$this->zip->numFiles; - $files=[]; - for ($i=0;$i<$fileCount;$i++) { - $files[]=$this->zip->getNameIndex($i); + $fileCount = $this->zip->numFiles; + $files = []; + for ($i = 0;$i < $fileCount;$i++) { + $files[] = $this->zip->getNameIndex($i); } return $files; } @@ -169,7 +169,7 @@ class ZIP extends Archive { * @return bool */ public function fileExists($path) { - return ($this->zip->locateName($path)!==false) or ($this->zip->locateName($path.'/')!==false); + return ($this->zip->locateName($path) !== false) or ($this->zip->locateName($path.'/') !== false); } /** * remove a file or folder from the archive @@ -190,16 +190,16 @@ class ZIP extends Archive { * @return resource */ public function getStream($path, $mode) { - if ($mode=='r' or $mode=='rb') { + if ($mode == 'r' or $mode == 'rb') { return $this->zip->getStream($path); } else { //since we can't directly get a writable stream, //make a temp copy of the file and put it back //in the archive when the stream is closed - if (strrpos($path, '.')!==false) { - $ext=substr($path, strrpos($path, '.')); + if (strrpos($path, '.') !== false) { + $ext = substr($path, strrpos($path, '.')); } else { - $ext=''; + $ext = ''; } $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); if ($this->fileExists($path)) { @@ -225,7 +225,7 @@ class ZIP extends Archive { * @return string */ private function stripPath($path) { - if (!$path || $path[0]=='/') { + if (!$path || $path[0] == '/') { return substr($path, 1); } else { return $path; diff --git a/lib/private/Authentication/Listeners/LoginFailedListener.php b/lib/private/Authentication/Listeners/LoginFailedListener.php index 72800ad509c..19f0b92c3a6 100644 --- a/lib/private/Authentication/Listeners/LoginFailedListener.php +++ b/lib/private/Authentication/Listeners/LoginFailedListener.php @@ -35,6 +35,9 @@ use OCP\EventDispatcher\IEventListener; use OCP\IUserManager; use OCP\Util; +/** + * @template-implements IEventListener<\OC\Authentication\Events\LoginFailed> + */ class LoginFailedListener implements IEventListener { /** @var IEventDispatcher */ diff --git a/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php b/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php index 0ab2dcf4837..0bdfa7cb39b 100644 --- a/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php +++ b/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php @@ -6,6 +6,7 @@ declare(strict_types=1); * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at> * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * @@ -33,18 +34,21 @@ use OC\Authentication\Token\IToken; use OCP\Activity\IManager as IActvityManager; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; -use OCP\ILogger; +use Psr\Log\LoggerInterface; +/** + * @template-implements IEventListener<\OC\Authentication\Events\ARemoteWipeEvent> + */ class RemoteWipeActivityListener implements IEventListener { /** @var IActvityManager */ private $activityManager; - /** @var ILogger */ + /** @var LoggerInterface */ private $logger; public function __construct(IActvityManager $activityManager, - ILogger $logger) { + LoggerInterface $logger) { $this->activityManager = $activityManager; $this->logger = $logger; } @@ -69,10 +73,9 @@ class RemoteWipeActivityListener implements IEventListener { try { $this->activityManager->publish($activity); } catch (BadMethodCallException $e) { - $this->logger->logException($e, [ + $this->logger->warning('could not publish activity', [ 'app' => 'core', - 'level' => ILogger::WARN, - 'message' => 'could not publish activity', + 'exception' => $e, ]); } } diff --git a/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php b/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php index 799c4ea4cf5..2f330879a2a 100644 --- a/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php +++ b/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php @@ -6,6 +6,7 @@ declare(strict_types=1); * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at> * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * @@ -32,14 +33,17 @@ use OC\Authentication\Events\RemoteWipeStarted; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\IL10N; -use OCP\ILogger; use OCP\IUser; use OCP\IUserManager; use OCP\L10N\IFactory as IL10nFactory; use OCP\Mail\IMailer; use OCP\Mail\IMessage; +use Psr\Log\LoggerInterface; use function substr; +/** + * @template-implements IEventListener<\OC\Authentication\Events\ARemoteWipeEvent> + */ class RemoteWipeEmailListener implements IEventListener { /** @var IMailer */ @@ -51,13 +55,13 @@ class RemoteWipeEmailListener implements IEventListener { /** @var IL10N */ private $l10n; - /** @var ILogger */ + /** @var LoggerInterface */ private $logger; public function __construct(IMailer $mailer, IUserManager $userManager, IL10nFactory $l10nFactory, - ILogger $logger) { + LoggerInterface $logger) { $this->mailer = $mailer; $this->userManager = $userManager; $this->l10n = $l10nFactory->get('core'); @@ -85,9 +89,8 @@ class RemoteWipeEmailListener implements IEventListener { $this->getWipingStartedMessage($event, $user) ); } catch (Exception $e) { - $this->logger->logException($e, [ - 'message' => "Could not send remote wipe started email to <$uid>", - 'level' => ILogger::ERROR, + $this->logger->error("Could not send remote wipe started email to <$uid>", [ + 'exception' => $e, ]); } } elseif ($event instanceof RemoteWipeFinished) { @@ -107,9 +110,8 @@ class RemoteWipeEmailListener implements IEventListener { $this->getWipingFinishedMessage($event, $user) ); } catch (Exception $e) { - $this->logger->logException($e, [ - 'message' => "Could not send remote wipe finished email to <$uid>", - 'level' => ILogger::ERROR, + $this->logger->error("Could not send remote wipe finished email to <$uid>", [ + 'exception' => $e, ]); } } diff --git a/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php b/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php index 831107a05cd..965fb291fbf 100644 --- a/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php +++ b/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php @@ -35,6 +35,9 @@ use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\Notification\IManager as INotificationManager; +/** + * @template-implements IEventListener<\OC\Authentication\Events\ARemoteWipeEvent> + */ class RemoteWipeNotificationsListener implements IEventListener { /** @var INotificationManager */ diff --git a/lib/private/Authentication/Listeners/UserDeletedStoreCleanupListener.php b/lib/private/Authentication/Listeners/UserDeletedStoreCleanupListener.php index c8f2da82db6..057568f514e 100644 --- a/lib/private/Authentication/Listeners/UserDeletedStoreCleanupListener.php +++ b/lib/private/Authentication/Listeners/UserDeletedStoreCleanupListener.php @@ -31,6 +31,9 @@ use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\User\Events\UserDeletedEvent; +/** + * @template-implements IEventListener<\OCP\User\Events\UserDeletedEvent> + */ class UserDeletedStoreCleanupListener implements IEventListener { /** @var Registry */ diff --git a/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php b/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php index d6238eb5ac8..2d78bd2d01c 100644 --- a/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php +++ b/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php @@ -5,7 +5,8 @@ declare(strict_types=1); /** * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * - * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * @@ -20,7 +21,8 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * */ namespace OC\Authentication\Listeners; @@ -28,20 +30,23 @@ namespace OC\Authentication\Listeners; use OC\Authentication\Token\Manager; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; -use OCP\ILogger; use OCP\User\Events\UserDeletedEvent; +use Psr\Log\LoggerInterface; use Throwable; +/** + * @template-implements IEventListener<\OCP\User\Events\UserDeletedEvent> + */ class UserDeletedTokenCleanupListener implements IEventListener { /** @var Manager */ private $manager; - /** @var ILogger */ + /** @var LoggerInterface */ private $logger; public function __construct(Manager $manager, - ILogger $logger) { + LoggerInterface $logger) { $this->manager = $manager; $this->logger = $logger; } @@ -63,9 +68,8 @@ class UserDeletedTokenCleanupListener implements IEventListener { $this->manager->invalidateTokenById($uid, $token->getId()); } } catch (Throwable $e) { - $this->logger->logException($e, [ - 'message' => 'Could not clean up auth tokens after user deletion: ' . $e->getMessage(), - 'error' => ILogger::ERROR, + $this->logger->error('Could not clean up auth tokens after user deletion: ' . $e->getMessage(), [ + 'exception' => $e, ]); } } diff --git a/lib/private/Authentication/Listeners/UserLoggedInListener.php b/lib/private/Authentication/Listeners/UserLoggedInListener.php new file mode 100644 index 00000000000..711a759fad4 --- /dev/null +++ b/lib/private/Authentication/Listeners/UserLoggedInListener.php @@ -0,0 +1,59 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Authentication\Listeners; + +use OC\Authentication\Token\Manager; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\User\Events\PostLoginEvent; + +/** + * @template-implements IEventListener<\OCP\User\Events\PostLoginEvent> + */ +class UserLoggedInListener implements IEventListener { + + /** @var Manager */ + private $manager; + + public function __construct(Manager $manager) { + $this->manager = $manager; + } + + public function handle(Event $event): void { + if (!($event instanceof PostLoginEvent)) { + return; + } + + // If this is already a token login there is nothing to do + if ($event->isTokenLogin()) { + return; + } + + $this->manager->updatePasswords($event->getUser()->getUID(), $event->getPassword()); + } +} diff --git a/lib/private/Authentication/Login/LoggedInCheckCommand.php b/lib/private/Authentication/Login/LoggedInCheckCommand.php index d2546a62d16..3c71c844d72 100644 --- a/lib/private/Authentication/Login/LoggedInCheckCommand.php +++ b/lib/private/Authentication/Login/LoggedInCheckCommand.php @@ -3,6 +3,7 @@ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at> * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version @@ -29,19 +30,17 @@ namespace OC\Authentication\Login; use OC\Authentication\Events\LoginFailed; use OC\Core\Controller\LoginController; use OCP\EventDispatcher\IEventDispatcher; -use OCP\ILogger; -use OCP\IUserManager; +use Psr\Log\LoggerInterface; class LoggedInCheckCommand extends ALoginCommand { - /** @var ILogger */ + /** @var LoggerInterface */ private $logger; /** @var IEventDispatcher */ private $dispatcher; - /** @var IUserManager */ - private $userManager; - public function __construct(ILogger $logger, IEventDispatcher $dispatcher) { + public function __construct(LoggerInterface $logger, + IEventDispatcher $dispatcher) { $this->logger = $logger; $this->dispatcher = $dispatcher; } diff --git a/lib/private/Authentication/Login/UserDisabledCheckCommand.php b/lib/private/Authentication/Login/UserDisabledCheckCommand.php index e33853ef5bd..0889fc99b20 100644 --- a/lib/private/Authentication/Login/UserDisabledCheckCommand.php +++ b/lib/private/Authentication/Login/UserDisabledCheckCommand.php @@ -3,6 +3,7 @@ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at> * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * @@ -26,19 +27,19 @@ declare(strict_types=1); namespace OC\Authentication\Login; use OC\Core\Controller\LoginController; -use OCP\ILogger; use OCP\IUserManager; +use Psr\Log\LoggerInterface; class UserDisabledCheckCommand extends ALoginCommand { /** @var IUserManager */ private $userManager; - /** @var ILogger */ + /** @var LoggerInterface */ private $logger; public function __construct(IUserManager $userManager, - ILogger $logger) { + LoggerInterface $logger) { $this->userManager = $userManager; $this->logger = $logger; } diff --git a/lib/private/Authentication/LoginCredentials/Store.php b/lib/private/Authentication/LoginCredentials/Store.php index f4bedd88a18..1810732ac3a 100644 --- a/lib/private/Authentication/LoginCredentials/Store.php +++ b/lib/private/Authentication/LoginCredentials/Store.php @@ -6,6 +6,7 @@ declare(strict_types=1); * @copyright 2016 Christoph Wurst <christoph@winzerhof-wurst.at> * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * @@ -32,28 +33,25 @@ use OC\Authentication\Token\IProvider; use OCP\Authentication\Exceptions\CredentialsUnavailableException; use OCP\Authentication\LoginCredentials\ICredentials; use OCP\Authentication\LoginCredentials\IStore; -use OCP\ILogger; use OCP\ISession; use OCP\Session\Exceptions\SessionNotAvailableException; use OCP\Util; +use Psr\Log\LoggerInterface; class Store implements IStore { /** @var ISession */ private $session; - /** @var ILogger */ + /** @var LoggerInterface */ private $logger; /** @var IProvider|null */ private $tokenProvider; - /** - * @param ISession $session - * @param ILogger $logger - * @param IProvider $tokenProvider - */ - public function __construct(ISession $session, ILogger $logger, IProvider $tokenProvider = null) { + public function __construct(ISession $session, + LoggerInterface $logger, + IProvider $tokenProvider = null) { $this->session = $session; $this->logger = $logger; $this->tokenProvider = $tokenProvider; @@ -111,8 +109,13 @@ class Store implements IStore { } if ($trySession && $this->session->exists('login_credentials')) { - $creds = json_decode($this->session->get('login_credentials')); - return new Credentials($creds->uid, $creds->uid, $creds->password); + /** @var array $creds */ + $creds = json_decode($this->session->get('login_credentials'), true); + return new Credentials( + $creds['uid'], + $creds['loginName'] ?? $this->session->get('loginname') ?? $creds['uid'], // Pre 20 didn't have a loginName property, hence fall back to the session value and then to the UID + $creds['password'] + ); } // If we reach this line, an exception was thrown. diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php index 9df907dfb4a..d91866ae66f 100644 --- a/lib/private/Authentication/Token/DefaultToken.php +++ b/lib/private/Authentication/Token/DefaultToken.php @@ -163,7 +163,7 @@ class DefaultToken extends Entity implements INamedToken { $scope = json_decode($this->getScope(), true); if (!$scope) { return [ - 'filesystem'=> true + 'filesystem' => true ]; } return $scope; diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php index e51033ed1df..40d503772b0 100644 --- a/lib/private/Authentication/Token/DefaultTokenMapper.php +++ b/lib/private/Authentication/Token/DefaultTokenMapper.php @@ -35,6 +35,9 @@ use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; +/** + * @template-extends QBMapper<DefaultToken> + */ class DefaultTokenMapper extends QBMapper { public function __construct(IDBConnection $db) { parent::__construct($db, 'authtoken'); diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php index ee8a28d3cb0..a6a1af5a97a 100644 --- a/lib/private/Authentication/Token/DefaultTokenProvider.php +++ b/lib/private/Authentication/Token/DefaultTokenProvider.php @@ -39,8 +39,8 @@ use OC\Authentication\Exceptions\PasswordlessTokenException; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IConfig; -use OCP\ILogger; use OCP\Security\ICrypto; +use Psr\Log\LoggerInterface; class DefaultTokenProvider implements IProvider { @@ -53,7 +53,7 @@ class DefaultTokenProvider implements IProvider { /** @var IConfig */ private $config; - /** @var ILogger */ + /** @var LoggerInterface */ private $logger; /** @var ITimeFactory */ @@ -62,7 +62,7 @@ class DefaultTokenProvider implements IProvider { public function __construct(DefaultTokenMapper $mapper, ICrypto $crypto, IConfig $config, - ILogger $logger, + LoggerInterface $logger, ITimeFactory $time) { $this->mapper = $mapper; $this->crypto = $crypto; diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php index 384828c207e..2b6223fded9 100644 --- a/lib/private/Authentication/Token/IProvider.php +++ b/lib/private/Authentication/Token/IProvider.php @@ -140,13 +140,13 @@ interface IProvider { /** * Get the (unencrypted) password of the given token * - * @param IToken $token + * @param IToken $savedToken * @param string $tokenId * @throws InvalidTokenException * @throws PasswordlessTokenException * @return string */ - public function getPassword(IToken $token, string $tokenId): string; + public function getPassword(IToken $savedToken, string $tokenId): string; /** * Encrypt and set the password of the given token diff --git a/lib/private/Authentication/Token/PublicKeyToken.php b/lib/private/Authentication/Token/PublicKeyToken.php index 320373436f1..d2336c81efd 100644 --- a/lib/private/Authentication/Token/PublicKeyToken.php +++ b/lib/private/Authentication/Token/PublicKeyToken.php @@ -180,7 +180,7 @@ class PublicKeyToken extends Entity implements INamedToken, IWipeableToken { $scope = json_decode($this->getScope(), true); if (!$scope) { return [ - 'filesystem'=> true + 'filesystem' => true ]; } return $scope; diff --git a/lib/private/Authentication/Token/PublicKeyTokenMapper.php b/lib/private/Authentication/Token/PublicKeyTokenMapper.php index e05325fec30..d44ff3c50dd 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenMapper.php +++ b/lib/private/Authentication/Token/PublicKeyTokenMapper.php @@ -5,6 +5,7 @@ declare(strict_types=1); /** * @copyright Copyright (c) 2018 Roeland Jago Douma <roeland@famdouma.nl> * + * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Roeland Jago Douma <roeland@famdouma.nl> * @@ -32,6 +33,9 @@ use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; +/** + * @template-extends QBMapper<PublicKeyToken> + */ class PublicKeyTokenMapper extends QBMapper { public function __construct(IDBConnection $db) { parent::__construct($db, 'authtoken'); diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php index 091f47d7da3..38551e63b87 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php +++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php @@ -5,6 +5,7 @@ declare(strict_types=1); /** * @copyright Copyright 2018, Roeland Jago Douma <roeland@famdouma.nl> * + * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Joas Schilling <coding@schilljs.com> * @author Morris Jobke <hey@morrisjobke.de> @@ -38,8 +39,8 @@ use OC\Cache\CappedMemoryCache; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IConfig; -use OCP\ILogger; use OCP\Security\ICrypto; +use Psr\Log\LoggerInterface; class PublicKeyTokenProvider implements IProvider { /** @var PublicKeyTokenMapper */ @@ -51,10 +52,10 @@ class PublicKeyTokenProvider implements IProvider { /** @var IConfig */ private $config; - /** @var ILogger $logger */ + /** @var LoggerInterface */ private $logger; - /** @var ITimeFactory $time */ + /** @var ITimeFactory */ private $time; /** @var CappedMemoryCache */ @@ -63,7 +64,7 @@ class PublicKeyTokenProvider implements IProvider { public function __construct(PublicKeyTokenMapper $mapper, ICrypto $crypto, IConfig $config, - ILogger $logger, + LoggerInterface $logger, ITimeFactory $time) { $this->mapper = $mapper; $this->crypto = $crypto; @@ -214,9 +215,13 @@ class PublicKeyTokenProvider implements IProvider { if (!($token instanceof PublicKeyToken)) { throw new InvalidTokenException("Invalid token type"); } - /** @var DefaultToken $token */ + + $activityInterval = $this->config->getSystemValueInt('token_auth_activity_update', 60); + $activityInterval = min(max($activityInterval, 0), 300); + + /** @var PublicKeyToken $token */ $now = $this->time->getTime(); - if ($token->getLastActivity() < ($now - 60)) { + if ($token->getLastActivity() < ($now - $activityInterval)) { // Update token only once per minute $token->setLastActivity($now); $this->mapper->update($token); @@ -227,20 +232,20 @@ class PublicKeyTokenProvider implements IProvider { return $this->mapper->getTokenByUser($uid); } - public function getPassword(IToken $token, string $tokenId): string { - if (!($token instanceof PublicKeyToken)) { + public function getPassword(IToken $savedToken, string $tokenId): string { + if (!($savedToken instanceof PublicKeyToken)) { throw new InvalidTokenException("Invalid token type"); } - if ($token->getPassword() === null) { + if ($savedToken->getPassword() === null) { throw new PasswordlessTokenException(); } // Decrypt private key with tokenId - $privateKey = $this->decrypt($token->getPrivateKey(), $tokenId); + $privateKey = $this->decrypt($savedToken->getPrivateKey(), $tokenId); // Decrypt password with private key - return $this->decryptPassword($token->getPassword(), $privateKey); + return $this->decryptPassword($savedToken->getPassword(), $privateKey); } public function setPassword(IToken $token, string $tokenId, string $password) { @@ -419,6 +424,7 @@ class PublicKeyTokenProvider implements IProvider { foreach ($tokens as $t) { $publicKey = $t->getPublicKey(); $t->setPassword($this->encryptPassword($password, $publicKey)); + $t->setPasswordInvalid(false); $this->updateToken($t); } } diff --git a/lib/private/Authentication/Token/RemoteWipe.php b/lib/private/Authentication/Token/RemoteWipe.php index cab68357a01..b762073a8bc 100644 --- a/lib/private/Authentication/Token/RemoteWipe.php +++ b/lib/private/Authentication/Token/RemoteWipe.php @@ -28,13 +28,13 @@ declare(strict_types=1); namespace OC\Authentication\Token; +use Psr\Log\LoggerInterface; use function array_filter; use OC\Authentication\Events\RemoteWipeFinished; use OC\Authentication\Events\RemoteWipeStarted; use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Exceptions\WipeTokenException; use OCP\EventDispatcher\IEventDispatcher; -use OCP\ILogger; use OCP\IUser; class RemoteWipe { @@ -45,12 +45,12 @@ class RemoteWipe { /** @var IEventDispatcher */ private $eventDispatcher; - /** @var ILogger */ + /** @var LoggerInterface */ private $logger; public function __construct(IProvider $tokenProvider, IEventDispatcher $eventDispatcher, - ILogger $logger) { + LoggerInterface $logger) { $this->tokenProvider = $tokenProvider; $this->eventDispatcher = $eventDispatcher; $this->logger = $logger; diff --git a/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php b/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php index 02e6863d1c4..bd8ff0353ee 100644 --- a/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php +++ b/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php @@ -29,6 +29,7 @@ namespace OC\Authentication\TwoFactorAuth\Db; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; +use function array_map; /** * Data access object to query and assign (provider_id, uid, enabled) tuples of @@ -91,13 +92,35 @@ class ProviderUserAssignmentDao { } } - public function deleteByUser(string $uid) { - $qb = $this->conn->getQueryBuilder(); - - $deleteQuery = $qb->delete(self::TABLE_NAME) - ->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid))); - + /** + * Delete all provider states of a user and return the provider IDs + * + * @param string $uid + * + * @return int[] + */ + public function deleteByUser(string $uid): array { + $qb1 = $this->conn->getQueryBuilder(); + $selectQuery = $qb1->select('*') + ->from(self::TABLE_NAME) + ->where($qb1->expr()->eq('uid', $qb1->createNamedParameter($uid))); + $selectResult = $selectQuery->execute(); + $rows = $selectResult->fetchAll(); + $selectResult->closeCursor(); + + $qb2 = $this->conn->getQueryBuilder(); + $deleteQuery = $qb2 + ->delete(self::TABLE_NAME) + ->where($qb2->expr()->eq('uid', $qb2->createNamedParameter($uid))); $deleteQuery->execute(); + + return array_map(function (array $row) { + return [ + 'provider_id' => $row['provider_id'], + 'uid' => $row['uid'], + 'enabled' => 1 === (int) $row['enabled'], + ]; + }, $rows); } public function deleteAll(string $providerId) { diff --git a/lib/private/Authentication/TwoFactorAuth/Manager.php b/lib/private/Authentication/TwoFactorAuth/Manager.php index 07e61175361..0a60606ad65 100644 --- a/lib/private/Authentication/TwoFactorAuth/Manager.php +++ b/lib/private/Authentication/TwoFactorAuth/Manager.php @@ -6,6 +6,7 @@ declare(strict_types=1); * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Roeland Jago Douma <roeland@famdouma.nl> * @@ -27,8 +28,6 @@ declare(strict_types=1); namespace OC\Authentication\TwoFactorAuth; -use function array_diff; -use function array_filter; use BadMethodCallException; use Exception; use OC\Authentication\Exceptions\InvalidTokenException; @@ -39,11 +38,13 @@ use OCP\Authentication\TwoFactorAuth\IActivatableAtLogin; use OCP\Authentication\TwoFactorAuth\IProvider; use OCP\Authentication\TwoFactorAuth\IRegistry; use OCP\IConfig; -use OCP\ILogger; use OCP\ISession; use OCP\IUser; +use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; +use function array_diff; +use function array_filter; class Manager { public const SESSION_UID_KEY = 'two_factor_auth_uid'; @@ -69,7 +70,7 @@ class Manager { /** @var IManager */ private $activityManager; - /** @var ILogger */ + /** @var LoggerInterface */ private $logger; /** @var TokenProvider */ @@ -84,9 +85,13 @@ class Manager { public function __construct(ProviderLoader $providerLoader, IRegistry $providerRegistry, MandatoryTwoFactor $mandatoryTwoFactor, - ISession $session, IConfig $config, - IManager $activityManager, ILogger $logger, TokenProvider $tokenProvider, - ITimeFactory $timeFactory, EventDispatcherInterface $eventDispatcher) { + ISession $session, + IConfig $config, + IManager $activityManager, + LoggerInterface $logger, + TokenProvider $tokenProvider, + ITimeFactory $timeFactory, + EventDispatcherInterface $eventDispatcher) { $this->providerLoader = $providerLoader; $this->providerRegistry = $providerRegistry; $this->mandatoryTwoFactor = $mandatoryTwoFactor; @@ -295,8 +300,7 @@ class Manager { try { $this->activityManager->publish($activity); } catch (BadMethodCallException $e) { - $this->logger->warning('could not publish activity', ['app' => 'core']); - $this->logger->logException($e, ['app' => 'core']); + $this->logger->warning('could not publish activity', ['app' => 'core', 'exception' => $e]); } } diff --git a/lib/private/Authentication/TwoFactorAuth/Registry.php b/lib/private/Authentication/TwoFactorAuth/Registry.php index 97df2bd5311..2af8566d3e5 100644 --- a/lib/private/Authentication/TwoFactorAuth/Registry.php +++ b/lib/private/Authentication/TwoFactorAuth/Registry.php @@ -31,6 +31,7 @@ use OC\Authentication\TwoFactorAuth\Db\ProviderUserAssignmentDao; use OCP\Authentication\TwoFactorAuth\IProvider; use OCP\Authentication\TwoFactorAuth\IRegistry; use OCP\Authentication\TwoFactorAuth\RegistryEvent; +use OCP\Authentication\TwoFactorAuth\TwoFactorProviderDisabled; use OCP\EventDispatcher\IEventDispatcher; use OCP\IUser; @@ -66,11 +67,11 @@ class Registry implements IRegistry { $this->dispatcher->dispatch(self::EVENT_PROVIDER_DISABLED, $event); } - /** - * @todo evaluate if we should emit RegistryEvents for each of the deleted rows -> needs documentation - */ public function deleteUserData(IUser $user): void { - $this->assignmentDao->deleteByUser($user->getUID()); + foreach ($this->assignmentDao->deleteByUser($user->getUID()) as $provider) { + $event = new TwoFactorProviderDisabled($provider['provider_id']); + $this->dispatcher->dispatchTyped($event); + } } public function cleanUp(string $providerId) { diff --git a/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialMapper.php b/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialMapper.php index 9c436b38b5d..d08cb71d72f 100644 --- a/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialMapper.php +++ b/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialMapper.php @@ -5,6 +5,7 @@ declare(strict_types=1); /** * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> * + * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version @@ -30,6 +31,9 @@ use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\QBMapper; use OCP\IDBConnection; +/** + * @template-extends QBMapper<PublicKeyCredentialEntity> + */ class PublicKeyCredentialMapper extends QBMapper { public function __construct(IDBConnection $db) { parent::__construct($db, 'webauthn', PublicKeyCredentialEntity::class); diff --git a/lib/private/Authentication/WebAuthn/Manager.php b/lib/private/Authentication/WebAuthn/Manager.php index 4415badc9b0..9ef3a0718ca 100644 --- a/lib/private/Authentication/WebAuthn/Manager.php +++ b/lib/private/Authentication/WebAuthn/Manager.php @@ -6,6 +6,7 @@ declare(strict_types=1); * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version @@ -35,8 +36,8 @@ use OC\Authentication\WebAuthn\Db\PublicKeyCredentialEntity; use OC\Authentication\WebAuthn\Db\PublicKeyCredentialMapper; use OCP\AppFramework\Db\DoesNotExistException; use OCP\IConfig; -use OCP\ILogger; use OCP\IUser; +use Psr\Log\LoggerInterface; use Webauthn\AttestationStatement\AttestationObjectLoader; use Webauthn\AttestationStatement\AttestationStatementSupportManager; use Webauthn\AttestationStatement\NoneAttestationStatementSupport; @@ -63,7 +64,7 @@ class Manager { /** @var PublicKeyCredentialMapper */ private $credentialMapper; - /** @var ILogger */ + /** @var LoggerInterface */ private $logger; /** @var IConfig */ @@ -72,7 +73,7 @@ class Manager { public function __construct( CredentialRepository $repository, PublicKeyCredentialMapper $credentialMapper, - ILogger $logger, + LoggerInterface $logger, IConfig $config ) { $this->repository = $repository; diff --git a/lib/private/BackgroundJob/Job.php b/lib/private/BackgroundJob/Job.php index a464ea2ba1f..312ec628e0b 100644 --- a/lib/private/BackgroundJob/Job.php +++ b/lib/private/BackgroundJob/Job.php @@ -3,7 +3,6 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Daniel Kesselberg <mail@danielkesselberg.de> - * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Noveen Sachdeva <noveen.sachdeva@research.iiit.ac.in> * @author Robin Appelman <robin@icewind.nl> diff --git a/lib/private/BackgroundJob/JobList.php b/lib/private/BackgroundJob/JobList.php index 1adecb5b32d..5a89dcdeba6 100644 --- a/lib/private/BackgroundJob/JobList.php +++ b/lib/private/BackgroundJob/JobList.php @@ -6,7 +6,6 @@ * @author Georg Ehrke <oc.list@georgehrke.com> * @author Joas Schilling <coding@schilljs.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Noveen Sachdeva <noveen.sachdeva@research.iiit.ac.in> * @author Robin Appelman <robin@icewind.nl> @@ -180,7 +179,6 @@ class JobList implements IJobList { * get the next job in the list * * @return IJob|null - * @suppress SqlInjectionChecker */ public function getNext() { $query = $this->connection->getQueryBuilder(); @@ -300,7 +298,6 @@ class JobList implements IJobList { * Remove the reservation for a job * * @param IJob $job - * @suppress SqlInjectionChecker */ public function unlockJob(IJob $job) { $query = $this->connection->getQueryBuilder(); @@ -311,18 +308,6 @@ class JobList implements IJobList { } /** - * get the id of the last ran job - * - * @return int - * @deprecated 9.1.0 - The functionality behind the value is deprecated, it - * only tells you which job finished last, but since we now allow multiple - * executors to run in parallel, it's not used to calculate the next job. - */ - public function getLastJob() { - return (int) $this->config->getAppValue('backgroundjob', 'lastjob', 0); - } - - /** * set the lastRun of $job to now * * @param IJob $job diff --git a/lib/private/BackgroundJob/TimedJob.php b/lib/private/BackgroundJob/TimedJob.php index d33cdd90b37..c0ce130c6cf 100644 --- a/lib/private/BackgroundJob/TimedJob.php +++ b/lib/private/BackgroundJob/TimedJob.php @@ -3,6 +3,7 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> diff --git a/lib/private/Cache/File.php b/lib/private/Cache/File.php index be6540ff2d2..04ec33f7615 100644 --- a/lib/private/Cache/File.php +++ b/lib/private/Cache/File.php @@ -10,7 +10,7 @@ * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Sebastian Wessalowski <sebastian@wessalowski.org> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Calendar/Manager.php b/lib/private/Calendar/Manager.php index ab8af870221..f8401259eb4 100644 --- a/lib/private/Calendar/Manager.php +++ b/lib/private/Calendar/Manager.php @@ -31,12 +31,12 @@ class Manager implements \OCP\Calendar\IManager { /** * @var ICalendar[] holds all registered calendars */ - private $calendars=[]; + private $calendars = []; /** * @var \Closure[] to call to load/register calendar providers */ - private $calendarLoaders=[]; + private $calendarLoaders = []; /** * This function is used to search and find objects within the user's calendars. @@ -51,7 +51,7 @@ class Manager implements \OCP\Calendar\IManager { * @return array an array of events/journals/todos which are arrays of arrays of key-value-pairs * @since 13.0.0 */ - public function search($pattern, array $searchProperties=[], array $options=[], $limit=null, $offset=null) { + public function search($pattern, array $searchProperties = [], array $options = [], $limit = null, $offset = null) { $this->loadCalendars(); $result = []; foreach ($this->calendars as $calendar) { diff --git a/lib/private/Collaboration/AutoComplete/Manager.php b/lib/private/Collaboration/AutoComplete/Manager.php index dc3b76f1f4d..d03de3a9fd3 100644 --- a/lib/private/Collaboration/AutoComplete/Manager.php +++ b/lib/private/Collaboration/AutoComplete/Manager.php @@ -30,7 +30,7 @@ use OCP\IServerContainer; class Manager implements IManager { /** @var string[] */ - protected $sorters =[]; + protected $sorters = []; /** @var ISorter[] */ protected $sorterInstances = []; diff --git a/lib/private/Collaboration/Collaborators/GroupPlugin.php b/lib/private/Collaboration/Collaborators/GroupPlugin.php index d4051cab36f..18a6631ed80 100644 --- a/lib/private/Collaboration/Collaborators/GroupPlugin.php +++ b/lib/private/Collaboration/Collaborators/GroupPlugin.php @@ -4,6 +4,7 @@ * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * @author Julius Härtl <jus@bitgrid.net> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> @@ -70,7 +71,7 @@ class GroupPlugin implements ISearchPlugin { $hasMoreResults = true; } - $userGroups = []; + $userGroups = []; if (!empty($groups) && ($this->shareWithGroupOnly || $this->shareeEnumerationInGroupOnly)) { // Intersect all the groups that match with the groups this user is a member of $userGroups = $this->groupManager->getUserGroups($this->userSession->getUser()); diff --git a/lib/private/Collaboration/Collaborators/LookupPlugin.php b/lib/private/Collaboration/Collaborators/LookupPlugin.php index ab36d712d2a..974254e9a3f 100644 --- a/lib/private/Collaboration/Collaborators/LookupPlugin.php +++ b/lib/private/Collaboration/Collaborators/LookupPlugin.php @@ -5,6 +5,7 @@ * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Bjoern Schiessle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php index 90dc5919176..7bdd29afc4e 100644 --- a/lib/private/Collaboration/Collaborators/MailPlugin.php +++ b/lib/private/Collaboration/Collaborators/MailPlugin.php @@ -136,6 +136,8 @@ class MailPlugin implements ISearchPlugin { 'shareType' => IShare::TYPE_USER, 'shareWith' => $cloud->getUser(), ], + 'shareWithDisplayNameUnique' => !empty($emailAddress) ? $emailAddress : $cloud->getUser() + ]]; $searchResult->addResultSet($userType, [], $singleResult); $searchResult->markExactIdMatch($emailType); @@ -170,6 +172,7 @@ class MailPlugin implements ISearchPlugin { 'shareType' => IShare::TYPE_USER, 'shareWith' => $cloud->getUser(), ], + 'shareWithDisplayNameUnique' => !empty($emailAddress) ? $emailAddress : $cloud->getUser() ]; continue; } diff --git a/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php b/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php index 17e7793a685..62e0a43b41e 100644 --- a/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php +++ b/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php @@ -3,6 +3,7 @@ * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> * * @author Bjoern Schiessle <bjoern@schiessle.org> + * @author Joas Schilling <coding@schilljs.com> * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> * * @license GNU AGPL version 3 or any later version diff --git a/lib/private/Collaboration/Collaborators/RemotePlugin.php b/lib/private/Collaboration/Collaborators/RemotePlugin.php index af94027fbe9..3d9b1f9847a 100644 --- a/lib/private/Collaboration/Collaborators/RemotePlugin.php +++ b/lib/private/Collaboration/Collaborators/RemotePlugin.php @@ -103,7 +103,8 @@ class RemotePlugin implements ISearchPlugin { 'value' => [ 'shareType' => IShare::TYPE_USER, 'shareWith' => $remoteUser - ] + ], + 'shareWithDisplayNameUnique' => $contact['EMAIL'] !== null && $contact['EMAIL'] !== '' ? $contact['EMAIL'] : $contact['UID'], ]; } diff --git a/lib/private/Collaboration/Collaborators/Search.php b/lib/private/Collaboration/Collaborators/Search.php index 8594e87e4a9..7b5789640a0 100644 --- a/lib/private/Collaboration/Collaborators/Search.php +++ b/lib/private/Collaboration/Collaborators/Search.php @@ -4,6 +4,8 @@ * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> + * @author onehappycat <one.happy.cat@gmx.com> * @author Robin Appelman <robin@icewind.nl> * * @license GNU AGPL version 3 or any later version @@ -67,14 +69,14 @@ class Search implements ISearch { foreach ($this->pluginList[$type] as $plugin) { /** @var ISearchPlugin $searchPlugin */ $searchPlugin = $this->c->resolve($plugin); - $hasMoreResults |= $searchPlugin->search($search, $limit, $offset, $searchResult); + $hasMoreResults = $searchPlugin->search($search, $limit, $offset, $searchResult) || $hasMoreResults; } } // Get from lookup server, not a separate share type if ($lookup) { $searchPlugin = $this->c->resolve(LookupPlugin::class); - $hasMoreResults |= $searchPlugin->search($search, $limit, $offset, $searchResult); + $hasMoreResults = $searchPlugin->search($search, $limit, $offset, $searchResult) || $hasMoreResults; } // sanitizing, could go into the plugins as well @@ -90,9 +92,10 @@ class Search implements ISearch { $searchResult->unsetResult($emailType); } - // if we have an exact local user match, there is no need to show the remote and email matches + // if we have an exact local user match with an email-a-like query, + // there is no need to show the remote and email matches. $userType = new SearchResultType('users'); - if ($searchResult->hasExactIdMatch($userType)) { + if (strpos($search, '@') !== false && $searchResult->hasExactIdMatch($userType)) { $searchResult->unsetResult($remoteType); $searchResult->unsetResult($emailType); } diff --git a/lib/private/Collaboration/Collaborators/UserPlugin.php b/lib/private/Collaboration/Collaborators/UserPlugin.php index d1f29350734..d832a42000c 100644 --- a/lib/private/Collaboration/Collaborators/UserPlugin.php +++ b/lib/private/Collaboration/Collaborators/UserPlugin.php @@ -4,10 +4,13 @@ * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Georg Ehrke <oc.list@georgehrke.com> * @author Joas Schilling <coding@schilljs.com> + * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> * @author Julius Härtl <jus@bitgrid.net> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> + * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Citharel <nextcloud@tcit.fr> * * @license GNU AGPL version 3 or any later version @@ -38,6 +41,7 @@ use OCP\IUser; use OCP\IUserManager; use OCP\IUserSession; use OCP\Share\IShare; +use OCP\UserStatus\IManager as IUserStatusManager; class UserPlugin implements ISearchPlugin { /* @var bool */ @@ -53,13 +57,29 @@ class UserPlugin implements ISearchPlugin { private $userSession; /** @var IUserManager */ private $userManager; + /** @var IUserStatusManager */ + private $userStatusManager; - public function __construct(IConfig $config, IUserManager $userManager, IGroupManager $groupManager, IUserSession $userSession) { + /** + * UserPlugin constructor. + * + * @param IConfig $config + * @param IUserManager $userManager + * @param IGroupManager $groupManager + * @param IUserSession $userSession + * @param IUserStatusManager $userStatusManager + */ + public function __construct(IConfig $config, + IUserManager $userManager, + IGroupManager $groupManager, + IUserSession $userSession, + IUserStatusManager $userStatusManager) { $this->config = $config; $this->groupManager = $groupManager; $this->userSession = $userSession; $this->userManager = $userManager; + $this->userStatusManager = $userStatusManager; $this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes'; $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; @@ -78,7 +98,15 @@ class UserPlugin implements ISearchPlugin { $usersInGroup = $this->groupManager->displayNamesInGroup($userGroupId, $search, $limit, $offset); foreach ($usersInGroup as $userId => $displayName) { $userId = (string) $userId; - $users[$userId] = $this->userManager->get($userId); + $user = $this->userManager->get($userId); + if (!$user->isEnabled()) { + // Ignore disabled users + continue; + } + $users[$userId] = $user; + } + if (count($usersInGroup) >= $limit) { + $hasMoreResults = true; } } } else { @@ -99,24 +127,44 @@ class UserPlugin implements ISearchPlugin { $foundUserById = false; $lowerSearch = strtolower($search); + $userStatuses = $this->userStatusManager->getUserStatuses(array_keys($users)); foreach ($users as $uid => $user) { $userDisplayName = $user->getDisplayName(); $userEmail = $user->getEMailAddress(); $uid = (string) $uid; + + $status = []; + if (array_key_exists($uid, $userStatuses)) { + $userStatus = $userStatuses[$uid]; + $status = [ + 'status' => $userStatus->getStatus(), + 'message' => $userStatus->getMessage(), + 'icon' => $userStatus->getIcon(), + 'clearAt' => $userStatus->getClearAt() + ? (int)$userStatus->getClearAt()->format('U') + : null, + ]; + } + + if ( - strtolower($uid) === $lowerSearch || + $lowerSearch !== '' && (strtolower($uid) === $lowerSearch || strtolower($userDisplayName) === $lowerSearch || - strtolower($userEmail) === $lowerSearch + strtolower($userEmail) === $lowerSearch) ) { if (strtolower($uid) === $lowerSearch) { $foundUserById = true; } $result['exact'][] = [ 'label' => $userDisplayName, + 'subline' => $status['message'] ?? '', + 'icon' => 'icon-user', 'value' => [ 'shareType' => IShare::TYPE_USER, 'shareWith' => $uid, ], + 'shareWithDisplayNameUnique' => !empty($userEmail) ? $userEmail : $uid, + 'status' => $status, ]; } else { $addToWideResults = false; @@ -134,10 +182,14 @@ class UserPlugin implements ISearchPlugin { if ($addToWideResults) { $result['wide'][] = [ 'label' => $userDisplayName, + 'subline' => $status['message'] ?? '', + 'icon' => 'icon-user', 'value' => [ 'shareType' => IShare::TYPE_USER, 'shareWith' => $uid, ], + 'shareWithDisplayNameUnique' => !empty($userEmail) ? $userEmail : $uid, + 'status' => $status, ]; } } @@ -157,12 +209,31 @@ class UserPlugin implements ISearchPlugin { } if ($addUser) { + $status = []; + $uid = $user->getUID(); + $userEmail = $user->getEMailAddress(); + if (array_key_exists($user->getUID(), $userStatuses)) { + $userStatus = $userStatuses[$user->getUID()]; + $status = [ + 'status' => $userStatus->getStatus(), + 'message' => $userStatus->getMessage(), + 'icon' => $userStatus->getIcon(), + 'clearAt' => $userStatus->getClearAt() + ? (int)$userStatus->getClearAt()->format('U') + : null, + ]; + } + $result['exact'][] = [ 'label' => $user->getDisplayName(), + 'icon' => 'icon-user', + 'subline' => $status['message'] ?? '', 'value' => [ 'shareType' => IShare::TYPE_USER, 'shareWith' => $user->getUID(), ], + 'shareWithDisplayNameUnique' => $userEmail !== null && $userEmail !== '' ? $userEmail : $uid, + 'status' => $status, ]; } } diff --git a/lib/private/Command/ClosureJob.php b/lib/private/Command/ClosureJob.php index d67dad348a3..c92fd304f91 100644 --- a/lib/private/Command/ClosureJob.php +++ b/lib/private/Command/ClosureJob.php @@ -3,6 +3,7 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Robin Appelman <robin@icewind.nl> + * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license AGPL-3.0 * @@ -23,12 +24,10 @@ namespace OC\Command; use OC\BackgroundJob\QueuedJob; -use SuperClosure\Serializer; class ClosureJob extends QueuedJob { protected function run($serializedCallable) { - $serializer = new Serializer(); - $callable = $serializer->unserialize($serializedCallable); + $callable = \Opis\Closure\unserialize($serializedCallable); if (is_callable($callable)) { $callable(); } else { diff --git a/lib/private/Command/CommandJob.php b/lib/private/Command/CommandJob.php index 7b2ae60beab..ed251b841ed 100644 --- a/lib/private/Command/CommandJob.php +++ b/lib/private/Command/CommandJob.php @@ -3,6 +3,7 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Robin Appelman <robin@icewind.nl> + * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license AGPL-3.0 * @@ -30,7 +31,7 @@ use OCP\Command\ICommand; */ class CommandJob extends QueuedJob { protected function run($serializedCommand) { - $command = unserialize($serializedCommand); + $command = \Opis\Closure\unserialize($serializedCommand); if ($command instanceof ICommand) { $command->handle(); } else { diff --git a/lib/private/Command/CronBus.php b/lib/private/Command/CronBus.php index 5bb8e4b0045..86aa239bdde 100644 --- a/lib/private/Command/CronBus.php +++ b/lib/private/Command/CronBus.php @@ -5,6 +5,7 @@ * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> + * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version * @@ -26,7 +27,6 @@ namespace OC\Command; use OCP\Command\ICommand; -use SuperClosure\Serializer; class CronBus extends AsyncBus { /** @@ -68,10 +68,9 @@ class CronBus extends AsyncBus { */ private function serializeCommand($command) { if ($command instanceof \Closure) { - $serializer = new Serializer(); - return $serializer->serialize($command); + return \Opis\Closure\serialize($command); } elseif (is_callable($command) or $command instanceof ICommand) { - return serialize($command); + return \Opis\Closure\serialize($command); } else { throw new \InvalidArgumentException('Invalid command'); } diff --git a/lib/private/Comments/Comment.php b/lib/private/Comments/Comment.php index 7c553c25a1c..7368425174a 100644 --- a/lib/private/Comments/Comment.php +++ b/lib/private/Comments/Comment.php @@ -32,19 +32,19 @@ use OCP\Comments\MessageTooLongException; class Comment implements IComment { protected $data = [ - 'id' => '', - 'parentId' => '0', + 'id' => '', + 'parentId' => '0', 'topmostParentId' => '0', - 'childrenCount' => '0', - 'message' => '', - 'verb' => '', - 'actorType' => '', - 'actorId' => '', - 'objectType' => '', - 'objectId' => '', - 'referenceId' => null, - 'creationDT' => null, - 'latestChildDT' => null, + 'childrenCount' => '0', + 'message' => '', + 'verb' => '', + 'actorType' => '', + 'actorId' => '', + 'objectType' => '', + 'objectId' => '', + 'referenceId' => null, + 'creationDT' => null, + 'latestChildDT' => null, ]; /** @@ -301,12 +301,12 @@ class Comment implements IComment { public function setActor($actorType, $actorId) { if ( !is_string($actorType) || !trim($actorType) - || !is_string($actorId) || $actorId === '' + || !is_string($actorId) || $actorId === '' ) { throw new \InvalidArgumentException('String expected.'); } $this->data['actorType'] = trim($actorType); - $this->data['actorId'] = $actorId; + $this->data['actorId'] = $actorId; return $this; } @@ -387,12 +387,12 @@ class Comment implements IComment { public function setObject($objectType, $objectId) { if ( !is_string($objectType) || !trim($objectType) - || !is_string($objectId) || trim($objectId) === '' + || !is_string($objectId) || trim($objectId) === '' ) { throw new \InvalidArgumentException('String expected.'); } $this->data['objectType'] = trim($objectType); - $this->data['objectId'] = trim($objectId); + $this->data['objectId'] = trim($objectId); return $this; } diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index a0a670817f4..1cc82b5c2ce 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -4,7 +4,7 @@ * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> + * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> @@ -30,6 +30,8 @@ namespace OC\Comments; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Exception\InvalidFieldNameException; +use OCA\Comments\AppInfo\Application; +use OCP\AppFramework\Utility\ITimeFactory; use OCP\Comments\CommentsEvent; use OCP\Comments\IComment; use OCP\Comments\ICommentsEventHandler; @@ -38,20 +40,28 @@ use OCP\Comments\NotFoundException; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IConfig; use OCP\IDBConnection; -use OCP\ILogger; use OCP\IUser; +use OCP\IInitialStateService; +use OCP\Util; +use Psr\Log\LoggerInterface; class Manager implements ICommentsManager { /** @var IDBConnection */ protected $dbConn; - /** @var ILogger */ + /** @var LoggerInterface */ protected $logger; /** @var IConfig */ protected $config; + /** @var ITimeFactory */ + protected $timeFactory; + + /** @var IInitialStateService */ + protected $initialStateService; + /** @var IComment[] */ protected $commentsCache = []; @@ -64,21 +74,16 @@ class Manager implements ICommentsManager { /** @var \Closure[] */ protected $displayNameResolvers = []; - /** - * Manager constructor. - * - * @param IDBConnection $dbConn - * @param ILogger $logger - * @param IConfig $config - */ - public function __construct( - IDBConnection $dbConn, - ILogger $logger, - IConfig $config - ) { + public function __construct(IDBConnection $dbConn, + LoggerInterface $logger, + IConfig $config, + ITimeFactory $timeFactory, + IInitialStateService $initialStateService) { $this->dbConn = $dbConn; $this->logger = $logger; $this->config = $config; + $this->timeFactory = $timeFactory; + $this->initialStateService = $initialStateService; } /** @@ -396,6 +401,7 @@ class Manager implements ICommentsManager { * @param string $sortDirection direction of the comments (`asc` or `desc`) * @param int $limit optional, number of maximum comments to be returned. if * set to 0, all comments are returned. + * @param bool $includeLastKnown * @return IComment[] * @return array */ @@ -404,7 +410,8 @@ class Manager implements ICommentsManager { string $objectId, int $lastKnownCommentId, string $sortDirection = 'asc', - int $limit = 30 + int $limit = 30, + bool $includeLastKnown = false ): array { $comments = []; @@ -428,6 +435,11 @@ class Manager implements ICommentsManager { if ($lastKnownComment instanceof IComment) { $lastKnownCommentDateTime = $lastKnownComment->getCreationDateTime(); if ($sortDirection === 'desc') { + if ($includeLastKnown) { + $idComparison = $query->expr()->lte('id', $query->createNamedParameter($lastKnownCommentId)); + } else { + $idComparison = $query->expr()->lt('id', $query->createNamedParameter($lastKnownCommentId)); + } $query->andWhere( $query->expr()->orX( $query->expr()->lt( @@ -441,11 +453,16 @@ class Manager implements ICommentsManager { $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE), IQueryBuilder::PARAM_DATE ), - $query->expr()->lt('id', $query->createNamedParameter($lastKnownCommentId)) + $idComparison ) ) ); } else { + if ($includeLastKnown) { + $idComparison = $query->expr()->gte('id', $query->createNamedParameter($lastKnownCommentId)); + } else { + $idComparison = $query->expr()->gt('id', $query->createNamedParameter($lastKnownCommentId)); + } $query->andWhere( $query->expr()->orX( $query->expr()->gt( @@ -459,7 +476,7 @@ class Manager implements ICommentsManager { $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE), IQueryBuilder::PARAM_DATE ), - $query->expr()->gt('id', $query->createNamedParameter($lastKnownCommentId)) + $idComparison ) ) ); @@ -518,6 +535,25 @@ class Manager implements ICommentsManager { * @return IComment[] */ public function search(string $search, string $objectType, string $objectId, string $verb, int $offset, int $limit = 50): array { + $objectIds = []; + if ($objectId) { + $objectIds[] = $objectIds; + } + return $this->searchForObjects($search, $objectType, $objectIds, $verb, $offset, $limit); + } + + /** + * Search for comments on one or more objects with a given content + * + * @param string $search content to search for + * @param string $objectType Limit the search by object type + * @param array $objectIds Limit the search by object ids + * @param string $verb Limit the verb of the comment + * @param int $offset + * @param int $limit + * @return IComment[] + */ + public function searchForObjects(string $search, string $objectType, array $objectIds, string $verb, int $offset, int $limit = 50): array { $query = $this->dbConn->getQueryBuilder(); $query->select('*') @@ -532,8 +568,8 @@ class Manager implements ICommentsManager { if ($objectType !== '') { $query->andWhere($query->expr()->eq('object_type', $query->createNamedParameter($objectType))); } - if ($objectId !== '') { - $query->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId))); + if (!empty($objectIds)) { + $query->andWhere($query->expr()->in('object_id', $query->createNamedParameter($objectIds, IQueryBuilder::PARAM_STR_ARRAY))); } if ($verb !== '') { $query->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb))); @@ -589,13 +625,145 @@ class Manager implements ICommentsManager { } /** + * @param string $objectType the object type, e.g. 'files' + * @param string[] $objectIds the id of the object + * @param IUser $user + * @param string $verb Limit the verb of the comment - Added in 14.0.0 + * @return array Map with object id => # of unread comments + * @psalm-return array<string, int> + * @since 21.0.0 + */ + public function getNumberOfUnreadCommentsForObjects(string $objectType, array $objectIds, IUser $user, $verb = ''): array { + $query = $this->dbConn->getQueryBuilder(); + $query->select('c.object_id', $query->func()->count('c.id', 'num_comments')) + ->from('comments', 'c') + ->leftJoin('c', 'comments_read_markers', 'm', $query->expr()->andX( + $query->expr()->eq('m.user_id', $query->createNamedParameter($user->getUID())), + $query->expr()->eq('c.object_type', 'm.object_type'), + $query->expr()->eq('c.object_id', 'm.object_id') + )) + ->where($query->expr()->eq('c.object_type', $query->createNamedParameter($objectType))) + ->andWhere($query->expr()->in('c.object_id', $query->createNamedParameter($objectIds, IQueryBuilder::PARAM_STR_ARRAY))) + ->andWhere($query->expr()->orX( + $query->expr()->gt('c.creation_timestamp', 'm.marker_datetime'), + $query->expr()->isNull('m.marker_datetime') + )) + ->groupBy('c.object_id'); + + if ($verb !== '') { + $query->andWhere($query->expr()->eq('c.verb', $query->createNamedParameter($verb))); + } + + $result = $query->execute(); + $unreadComments = array_fill_keys($objectIds, 0); + while ($row = $result->fetch()) { + $unreadComments[$row['object_id']] = (int) $row['num_comments']; + } + $result->closeCursor(); + + return $unreadComments; + } + + /** + * @param string $objectType + * @param string $objectId + * @param int $lastRead + * @param string $verb + * @return int + * @since 21.0.0 + */ + public function getNumberOfCommentsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, string $verb = ''): int { + $query = $this->dbConn->getQueryBuilder(); + $query->select($query->func()->count('id', 'num_messages')) + ->from('comments') + ->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType))) + ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId))) + ->andWhere($query->expr()->gt('id', $query->createNamedParameter($lastRead))); + + if ($verb !== '') { + $query->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb))); + } + + $result = $query->execute(); + $data = $result->fetch(); + $result->closeCursor(); + + return (int) ($data['num_messages'] ?? 0); + } + + /** + * @param string $objectType + * @param string $objectId + * @param \DateTime $beforeDate + * @param string $verb + * @return int + * @since 21.0.0 + */ + public function getLastCommentBeforeDate(string $objectType, string $objectId, \DateTime $beforeDate, string $verb = ''): int { + $query = $this->dbConn->getQueryBuilder(); + $query->select('id') + ->from('comments') + ->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType))) + ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId))) + ->andWhere($query->expr()->lt('creation_timestamp', $query->createNamedParameter($beforeDate, IQueryBuilder::PARAM_DATE))) + ->orderBy('creation_timestamp', 'desc'); + + if ($verb !== '') { + $query->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb))); + } + + $result = $query->execute(); + $data = $result->fetch(); + $result->closeCursor(); + + return (int) ($data['id'] ?? 0); + } + + /** + * @param string $objectType + * @param string $objectId + * @param string $verb + * @param string $actorType + * @param string[] $actors + * @return \DateTime[] Map of "string actor" => "\DateTime most recent comment date" + * @psalm-return array<string, \DateTime> + * @since 21.0.0 + */ + public function getLastCommentDateByActor( + string $objectType, + string $objectId, + string $verb, + string $actorType, + array $actors + ): array { + $lastComments = []; + + $query = $this->dbConn->getQueryBuilder(); + $query->select('actor_id') + ->selectAlias($query->createFunction('MAX(' . $query->getColumnName('creation_timestamp') . ')'), 'last_comment') + ->from('comments') + ->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType))) + ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId))) + ->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb))) + ->andWhere($query->expr()->eq('actor_type', $query->createNamedParameter($actorType))) + ->andWhere($query->expr()->in('actor_id', $query->createNamedParameter($actors, IQueryBuilder::PARAM_STR_ARRAY))) + ->groupBy('actor_id'); + + $result = $query->execute(); + while ($row = $result->fetch()) { + $lastComments[$row['actor_id']] = $this->timeFactory->getDateTime($row['last_comment']); + } + $result->closeCursor(); + + return $lastComments; + } + + /** * Get the number of unread comments for all files in a folder * * @param int $folderId * @param IUser $user * @return array [$fileId => $unreadCount] - * - * @suppress SqlInjectionChecker */ public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user) { $qb = $this->dbConn->getQueryBuilder(); @@ -695,7 +863,10 @@ class Manager implements ICommentsManager { $affectedRows = $query->execute(); $this->uncache($id); } catch (DriverException $e) { - $this->logger->logException($e, ['app' => 'core_comments']); + $this->logger->error($e->getMessage(), [ + 'exception' => $e, + 'app' => 'core_comments', + ]); return false; } @@ -920,7 +1091,10 @@ class Manager implements ICommentsManager { try { $affectedRows = $query->execute(); } catch (DriverException $e) { - $this->logger->logException($e, ['app' => 'core_comments']); + $this->logger->error($e->getMessage(), [ + 'exception' => $e, + 'app' => 'core_comments', + ]); return false; } return ($affectedRows > 0); @@ -935,7 +1109,6 @@ class Manager implements ICommentsManager { * @param \DateTime $dateTime * @param IUser $user * @since 9.0.0 - * @suppress SqlInjectionChecker */ public function setReadMark($objectType, $objectId, \DateTime $dateTime, IUser $user) { $this->checkRoleParameters('Object', $objectType, $objectId); @@ -1025,7 +1198,10 @@ class Manager implements ICommentsManager { try { $affectedRows = $query->execute(); } catch (DriverException $e) { - $this->logger->logException($e, ['app' => 'core_comments']); + $this->logger->error($e->getMessage(), [ + 'exception' => $e, + 'app' => 'core_comments', + ]); return false; } return ($affectedRows > 0); @@ -1121,4 +1297,14 @@ class Manager implements ICommentsManager { $entity->handle($event); } } + + /** + * Load the Comments app into the page + * + * @since 21.0.0 + */ + public function load(): void { + $this->initialStateService->provideInitialState(Application::APP_ID, 'max-message-length', IComment::MAX_MESSAGE_LENGTH); + Util::addScript(Application::APP_ID, 'comments-app'); + } } diff --git a/lib/private/Comments/ManagerFactory.php b/lib/private/Comments/ManagerFactory.php index dd69a4f9261..ec2cb1e69a1 100644 --- a/lib/private/Comments/ManagerFactory.php +++ b/lib/private/Comments/ManagerFactory.php @@ -3,9 +3,10 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * @author Joas Schilling <coding@schilljs.com> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -54,10 +55,6 @@ class ManagerFactory implements ICommentsManagerFactory { * @since 9.0.0 */ public function getManager() { - return new Manager( - $this->serverContainer->getDatabaseConnection(), - $this->serverContainer->getLogger(), - $this->serverContainer->getConfig() - ); + return $this->serverContainer->get(Manager::class); } } diff --git a/lib/private/Config.php b/lib/private/Config.php index cbdbc5b2e64..a6c4acb7d96 100644 --- a/lib/private/Config.php +++ b/lib/private/Config.php @@ -19,6 +19,7 @@ * @author Philipp Schaffrath <github@philipp.schaffrath.email> * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> + * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license AGPL-3.0 * @@ -47,6 +48,8 @@ class Config { /** @var array Associative array ($key => $value) */ protected $cache = []; + /** @var array */ + protected $envCache = []; /** @var string */ protected $configDir; /** @var string */ @@ -88,9 +91,9 @@ class Config { * @return mixed the value or $default */ public function getValue($key, $default = null) { - $envValue = getenv(self::ENV_PREFIX . $key); - if ($envValue !== false) { - return $envValue; + $envKey = self::ENV_PREFIX . $key; + if (isset($this->envCache[$envKey])) { + return $this->envCache[$envKey]; } if (isset($this->cache[$key])) { @@ -222,6 +225,8 @@ class Config { flock($filePointer, LOCK_UN); fclose($filePointer); } + + $this->envCache = getenv(); } /** diff --git a/lib/private/Contacts/ContactsMenu/ContactsStore.php b/lib/private/Contacts/ContactsMenu/ContactsStore.php index b0bd99b844e..e2bd7edc63d 100644 --- a/lib/private/Contacts/ContactsMenu/ContactsStore.php +++ b/lib/private/Contacts/ContactsMenu/ContactsStore.php @@ -3,6 +3,7 @@ * @copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at> * @copyright 2017 Lukas Reschke <lukas@statuscode.ch> * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Daniel Calviño Sánchez <danxuliu@gmail.com> * @author Georg Ehrke <oc.list@georgehrke.com> @@ -73,11 +74,23 @@ class ContactsStore implements IContactsStore { * @param string|null $filter * @return IEntry[] */ - public function getContacts(IUser $user, $filter) { - $allContacts = $this->contactsManager->search($filter ?: '', [ - 'FN', - 'EMAIL' - ]); + public function getContacts(IUser $user, $filter, ?int $limit = null, ?int $offset = null) { + $options = []; + if ($limit !== null) { + $options['limit'] = $limit; + } + if ($offset !== null) { + $options['offset'] = $offset; + } + + $allContacts = $this->contactsManager->search( + $filter ?: '', + [ + 'FN', + 'EMAIL' + ], + $options + ); $entries = array_map(function (array $contact) { return $this->contactArrayToEntry($contact); @@ -122,7 +135,7 @@ class ContactsStore implements IContactsStore { if ($excludedGroups) { $excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', ''); $decodedExcludeGroups = json_decode($excludedGroups, true); - $excludeGroupsList = ($decodedExcludeGroups !== null) ? $decodedExcludeGroups : []; + $excludeGroupsList = ($decodedExcludeGroups !== null) ? $decodedExcludeGroups : []; if (count(array_intersect($excludeGroupsList, $selfGroups)) !== 0) { // a group of the current user is excluded -> filter all local users diff --git a/lib/private/Contacts/ContactsMenu/Manager.php b/lib/private/Contacts/ContactsMenu/Manager.php index 293f1366b05..d08db5775e5 100644 --- a/lib/private/Contacts/ContactsMenu/Manager.php +++ b/lib/private/Contacts/ContactsMenu/Manager.php @@ -2,6 +2,7 @@ /** * @copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at> * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Georg Ehrke <oc.list@georgehrke.com> * @author Julius Härtl <jus@bitgrid.net> @@ -26,6 +27,7 @@ namespace OC\Contacts\ContactsMenu; use OCP\App\IAppManager; +use OCP\Constants; use OCP\Contacts\ContactsMenu\IEntry; use OCP\IConfig; use OCP\IUser; @@ -62,11 +64,11 @@ class Manager { * @return array */ public function getEntries(IUser $user, $filter) { - $maxAutocompleteResults = $this->config->getSystemValueInt('sharing.maxAutocompleteResults', 25); + $maxAutocompleteResults = max(0, $this->config->getSystemValueInt('sharing.maxAutocompleteResults', Constants::SHARING_MAX_AUTOCOMPLETE_RESULTS_DEFAULT)); $minSearchStringLength = $this->config->getSystemValueInt('sharing.minSearchStringLength', 0); $topEntries = []; if (strlen($filter) >= $minSearchStringLength) { - $entries = $this->store->getContacts($user, $filter); + $entries = $this->store->getContacts($user, $filter, $maxAutocompleteResults); $sortedEntries = $this->sortEntries($entries); $topEntries = array_slice($sortedEntries, 0, $maxAutocompleteResults); diff --git a/lib/private/ContactsManager.php b/lib/private/ContactsManager.php index 999b6597978..4cdc3d48fff 100644 --- a/lib/private/ContactsManager.php +++ b/lib/private/ContactsManager.php @@ -2,14 +2,10 @@ /** * @copyright Copyright (c) 2016, ownCloud, Inc. * - * @author Arne Hamann <kontakt+github@arne.email> * @author Bart Visscher <bartv@thisnet.nl> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Joas Schilling <coding@schilljs.com> * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> - * @author Julius Härtl <jus@bitgrid.net> * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Tobia De Koninck <tobia@ledfan.be> * @@ -69,11 +65,11 @@ class ContactsManager implements IManager { * This function can be used to delete the contact identified by the given id * * @param object $id the unique identifier to a contact - * @param string $addressBookKey identifier of the address book in which the contact shall be deleted + * @param string $address_book_key identifier of the address book in which the contact shall be deleted * @return bool successful or not */ - public function delete($id, $addressBookKey) { - $addressBook = $this->getAddressBook($addressBookKey); + public function delete($id, $address_book_key) { + $addressBook = $this->getAddressBook($address_book_key); if (!$addressBook) { return null; } @@ -90,11 +86,11 @@ class ContactsManager implements IManager { * Otherwise the contact will be updated by replacing the entire data set. * * @param array $properties this array if key-value-pairs defines a contact - * @param string $addressBookKey identifier of the address book in which the contact shall be created or updated + * @param string $address_book_key identifier of the address book in which the contact shall be created or updated * @return array representing the contact just created or updated */ - public function createOrUpdate($properties, $addressBookKey) { - $addressBook = $this->getAddressBook($addressBookKey); + public function createOrUpdate($properties, $address_book_key) { + $addressBook = $this->getAddressBook($address_book_key); if (!$addressBook) { return null; } diff --git a/lib/private/DB/Adapter.php b/lib/private/DB/Adapter.php index f27a90dd690..26fd018dec1 100644 --- a/lib/private/DB/Adapter.php +++ b/lib/private/DB/Adapter.php @@ -103,7 +103,7 @@ class Adapter { } $query = 'INSERT INTO `' .$table . '` (`' . implode('`,`', array_keys($input)) . '`) SELECT ' - . str_repeat('?,', count($input)-1).'? ' // Is there a prettier alternative? + . str_repeat('?,', count($input) - 1).'? ' // Is there a prettier alternative? . 'FROM `' . $table . '` WHERE '; $inserts = array_values($input); @@ -130,9 +130,6 @@ class Adapter { } } - /** - * @suppress SqlInjectionChecker - */ public function insertIgnoreConflict(string $table,array $values) : int { try { $builder = $this->conn->getQueryBuilder(); diff --git a/lib/private/DB/AdapterPgSql.php b/lib/private/DB/AdapterPgSql.php index 7f7f5150780..77f0b6b7722 100644 --- a/lib/private/DB/AdapterPgSql.php +++ b/lib/private/DB/AdapterPgSql.php @@ -41,9 +41,6 @@ class AdapterPgSql extends Adapter { return $statement; } - /** - * @suppress SqlInjectionChecker - */ public function insertIgnoreConflict(string $table,array $values) : int { if ($this->isPre9_5CompatMode() === true) { return parent::insertIgnoreConflict($table, $values); diff --git a/lib/private/DB/AdapterSqlite.php b/lib/private/DB/AdapterSqlite.php index 43ec4a90c62..5731ee1721a 100644 --- a/lib/private/DB/AdapterSqlite.php +++ b/lib/private/DB/AdapterSqlite.php @@ -71,7 +71,7 @@ class AdapterSqlite extends Adapter { } $fieldList = '`' . implode('`,`', array_keys($input)) . '`'; $query = "INSERT INTO `$table` ($fieldList) SELECT " - . str_repeat('?,', count($input)-1).'? ' + . str_repeat('?,', count($input) - 1).'? ' . " WHERE NOT EXISTS (SELECT 1 FROM `$table` WHERE "; $inserts = array_values($input); diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php index 3b24703d434..b024989ac04 100644 --- a/lib/private/DB/Connection.php +++ b/lib/private/DB/Connection.php @@ -5,7 +5,7 @@ * @author Bart Visscher <bartv@thisnet.nl> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> + * @author Julius Härtl <jus@bitgrid.net> * @author Morris Jobke <hey@morrisjobke.de> * @author Ole Ostergaard <ole.c.ostergaard@gmail.com> * @author Ole Ostergaard <ole.ostergaard@knime.com> @@ -39,26 +39,37 @@ use Doctrine\DBAL\Configuration; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Exception\ConstraintViolationException; +use Doctrine\DBAL\Exception\NotNullConstraintViolationException; use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\DBAL\Schema\Schema; use OC\DB\QueryBuilder\QueryBuilder; +use OC\SystemConfig; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; +use OCP\ILogger; use OCP\PreConditionNotMetException; class Connection extends ReconnectWrapper implements IDBConnection { - /** - * @var string $tablePrefix - */ + /** @var string */ protected $tablePrefix; - /** - * @var \OC\DB\Adapter $adapter - */ + /** @var \OC\DB\Adapter $adapter */ protected $adapter; + /** @var SystemConfig */ + private $systemConfig; + + /** @var ILogger */ + private $logger; + protected $lockedTable = null; + /** @var int */ + protected $queriesBuilt = 0; + + /** @var int */ + protected $queriesExecuted = 0; + public function connect() { try { return parent::connect(); @@ -68,16 +79,24 @@ class Connection extends ReconnectWrapper implements IDBConnection { } } + public function getStats(): array { + return [ + 'built' => $this->queriesBuilt, + 'executed' => $this->queriesExecuted, + ]; + } + /** * Returns a QueryBuilder for the connection. * * @return \OCP\DB\QueryBuilder\IQueryBuilder */ public function getQueryBuilder() { + $this->queriesBuilt++; return new QueryBuilder( $this, - \OC::$server->getSystemConfig(), - \OC::$server->getLogger() + $this->systemConfig, + $this->logger ); } @@ -90,6 +109,7 @@ class Connection extends ReconnectWrapper implements IDBConnection { public function createQueryBuilder() { $backtrace = $this->getCallerBacktrace(); \OC::$server->getLogger()->debug('Doctrine QueryBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]); + $this->queriesBuilt++; return parent::createQueryBuilder(); } @@ -102,6 +122,7 @@ class Connection extends ReconnectWrapper implements IDBConnection { public function getExpressionBuilder() { $backtrace = $this->getCallerBacktrace(); \OC::$server->getLogger()->debug('Doctrine ExpressionBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]); + $this->queriesBuilt++; return parent::getExpressionBuilder(); } @@ -149,6 +170,9 @@ class Connection extends ReconnectWrapper implements IDBConnection { parent::__construct($params, $driver, $config, $eventManager); $this->adapter = new $params['adapter']($this); $this->tablePrefix = $params['tablePrefix']; + + $this->systemConfig = \OC::$server->getSystemConfig(); + $this->logger = \OC::$server->getLogger(); } /** @@ -159,7 +183,7 @@ class Connection extends ReconnectWrapper implements IDBConnection { * @param int $offset * @return \Doctrine\DBAL\Driver\Statement The prepared statement. */ - public function prepare($statement, $limit=null, $offset=null) { + public function prepare($statement, $limit = null, $offset = null) { if ($limit === -1) { $limit = null; } @@ -179,7 +203,7 @@ class Connection extends ReconnectWrapper implements IDBConnection { * If the query is parametrized, a prepared statement is used. * If an SQLLogger is configured, the execution is logged. * - * @param string $query The SQL query to execute. + * @param string $sql The SQL query to execute. * @param array $params The parameters to bind to the query, if any. * @param array $types The types the previous parameters are in. * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp The query cache profile, optional. @@ -188,10 +212,18 @@ class Connection extends ReconnectWrapper implements IDBConnection { * * @throws \Doctrine\DBAL\DBALException */ - public function executeQuery($query, array $params = [], $types = [], QueryCacheProfile $qcp = null) { - $query = $this->replaceTablePrefix($query); - $query = $this->adapter->fixupStatement($query); - return parent::executeQuery($query, $params, $types, $qcp); + public function executeQuery($sql, array $params = [], $types = [], QueryCacheProfile $qcp = null) { + $sql = $this->replaceTablePrefix($sql); + $sql = $this->adapter->fixupStatement($sql); + $this->queriesExecuted++; + return parent::executeQuery($sql, $params, $types, $qcp); + } + + public function executeUpdate($sql, array $params = [], array $types = []) { + $sql = $this->replaceTablePrefix($sql); + $sql = $this->adapter->fixupStatement($sql); + $this->queriesExecuted++; + return parent::executeUpdate($sql, $params, $types); } /** @@ -200,7 +232,7 @@ class Connection extends ReconnectWrapper implements IDBConnection { * * This method supports PDO binding types as well as DBAL mapping types. * - * @param string $query The SQL query. + * @param string $sql The SQL query. * @param array $params The query parameters. * @param array $types The parameter types. * @@ -208,10 +240,11 @@ class Connection extends ReconnectWrapper implements IDBConnection { * * @throws \Doctrine\DBAL\DBALException */ - public function executeUpdate($query, array $params = [], array $types = []) { - $query = $this->replaceTablePrefix($query); - $query = $this->adapter->fixupStatement($query); - return parent::executeUpdate($query, $params, $types); + public function executeStatement($sql, array $params = [], array $types = []) { + $sql = $this->replaceTablePrefix($sql); + $sql = $this->adapter->fixupStatement($sql); + $this->queriesExecuted++; + return parent::executeStatement($sql, $params, $types); } /** @@ -279,7 +312,6 @@ class Connection extends ReconnectWrapper implements IDBConnection { * @return int number of new rows * @throws \Doctrine\DBAL\DBALException * @throws PreConditionNotMetException - * @suppress SqlInjectionChecker */ public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []) { try { @@ -291,6 +323,8 @@ class Connection extends ReconnectWrapper implements IDBConnection { }, array_merge($keys, $values)) ); return $insertQb->execute(); + } catch (NotNullConstraintViolationException $e) { + throw $e; } catch (ConstraintViolationException $e) { // value already exists, try update $updateQb = $this->getQueryBuilder(); @@ -301,11 +335,17 @@ class Connection extends ReconnectWrapper implements IDBConnection { $where = $updateQb->expr()->andX(); $whereValues = array_merge($keys, $updatePreconditionValues); foreach ($whereValues as $name => $value) { - $where->add($updateQb->expr()->eq( - $name, - $updateQb->createNamedParameter($value, $this->getType($value)), - $this->getType($value) - )); + if ($value === '') { + $where->add($updateQb->expr()->emptyString( + $name + )); + } else { + $where->add($updateQb->expr()->eq( + $name, + $updateQb->createNamedParameter($value, $this->getType($value)), + $this->getType($value) + )); + } } $updateQb->where($where); $affected = $updateQb->execute(); diff --git a/lib/private/DB/MDB2SchemaManager.php b/lib/private/DB/MDB2SchemaManager.php index 145272619b4..44ea3986214 100644 --- a/lib/private/DB/MDB2SchemaManager.php +++ b/lib/private/DB/MDB2SchemaManager.php @@ -11,7 +11,7 @@ * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Victor Dubiniuk <dubiniuk@owncloud.com> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -145,7 +145,6 @@ class MDB2SchemaManager { $toSchema = new Schema([], [], $this->conn->getSchemaManager()->createSchemaConfig()); $fromSchema = $schemaReader->loadSchemaFromFile($file, $toSchema); $toSchema = clone $fromSchema; - /** @var $table \Doctrine\DBAL\Schema\Table */ foreach ($toSchema->getTables() as $table) { $toSchema->dropTable($table->getName()); } diff --git a/lib/private/DB/MDB2SchemaReader.php b/lib/private/DB/MDB2SchemaReader.php index b371e1a16b2..abf58ffa053 100644 --- a/lib/private/DB/MDB2SchemaReader.php +++ b/lib/private/DB/MDB2SchemaReader.php @@ -10,7 +10,7 @@ * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Victor Dubiniuk <dubiniuk@owncloud.com> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/DB/MigrationService.php b/lib/private/DB/MigrationService.php index 62f1d731f55..6a1cb4be016 100644 --- a/lib/private/DB/MigrationService.php +++ b/lib/private/DB/MigrationService.php @@ -4,10 +4,11 @@ * @copyright Copyright (c) 2017, ownCloud GmbH * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Joas Schilling <coding@schilljs.com> + * @author Julius Härtl <jus@bitgrid.net> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> - * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license AGPL-3.0 * @@ -27,6 +28,7 @@ namespace OC\DB; +use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Platforms\PostgreSqlPlatform; use Doctrine\DBAL\Schema\Index; @@ -124,6 +126,11 @@ class MigrationService { return false; } + if ($this->connection->tableExists('migrations') && \OC::$server->getConfig()->getAppValue('core', 'vendor', '') !== 'owncloud') { + $this->migrationTableCreated = true; + return false; + } + $schema = new SchemaWrapper($this->connection); /** @@ -408,10 +415,57 @@ class MigrationService { * @throws \InvalidArgumentException */ public function migrate($to = 'latest', $schemaOnly = false) { + if ($schemaOnly) { + $this->migrateSchemaOnly($to); + return; + } + + // read known migrations + $toBeExecuted = $this->getMigrationsToExecute($to); + foreach ($toBeExecuted as $version) { + try { + $this->executeStep($version, $schemaOnly); + } catch (DriverException $e) { + // The exception itself does not contain the name of the migration, + // so we wrap it here, to make debugging easier. + throw new \Exception('Database error when running migration ' . $to . ' for app ' . $this->getApp(), 0, $e); + } + } + } + + /** + * Applies all not yet applied versions up to $to + * + * @param string $to + * @throws \InvalidArgumentException + */ + public function migrateSchemaOnly($to = 'latest') { // read known migrations $toBeExecuted = $this->getMigrationsToExecute($to); + + if (empty($toBeExecuted)) { + return; + } + + $toSchema = null; foreach ($toBeExecuted as $version) { - $this->executeStep($version, $schemaOnly); + $instance = $this->createInstance($version); + + $toSchema = $instance->changeSchema($this->output, function () use ($toSchema) { + return $toSchema ?: new SchemaWrapper($this->connection); + }, ['tablePrefix' => $this->connection->getPrefix()]) ?: $toSchema; + + $this->markAsExecuted($version); + } + + if ($toSchema instanceof SchemaWrapper) { + $targetSchema = $toSchema->getWrappedSchema(); + if ($this->checkOracle) { + $beforeSchema = $this->connection->createSchema(); + $this->ensureOracleIdentifierLengthLimit($beforeSchema, $targetSchema, strlen($this->connection->getPrefix())); + } + $this->connection->migrateToSchema($targetSchema); + $toSchema->performDropTableCalls(); } } @@ -513,6 +567,11 @@ class MigrationService { if ((!$sourceTable instanceof Table || !$sourceTable->hasColumn($thing->getName())) && \strlen($thing->getName()) > 30) { throw new \InvalidArgumentException('Column name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.'); } + + if ($thing->getNotnull() && $thing->getDefault() === '' + && $sourceTable instanceof Table && !$sourceTable->hasColumn($thing->getName())) { + throw new \InvalidArgumentException('Column name "' . $table->getName() . '"."' . $thing->getName() . '" is NotNull, but has empty string or null as default.'); + } } foreach ($table->getIndexes() as $thing) { diff --git a/lib/private/DB/Migrator.php b/lib/private/DB/Migrator.php index 2ea365ab294..569b1a6c282 100644 --- a/lib/private/DB/Migrator.php +++ b/lib/private/DB/Migrator.php @@ -11,7 +11,7 @@ * @author tbelau666 <thomas.belau@gmx.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Victor Dubiniuk <dubiniuk@owncloud.com> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -226,7 +226,6 @@ class Migrator { $sourceSchema = $connection->getSchemaManager()->createSchema(); // remove tables we don't know about - /** @var $table \Doctrine\DBAL\Schema\Table */ foreach ($sourceSchema->getTables() as $table) { if (!$targetSchema->hasTable($table->getName())) { $sourceSchema->dropTable($table->getName()); @@ -305,13 +304,13 @@ class Migrator { if (is_null($this->dispatcher)) { return; } - $this->dispatcher->dispatch('\OC\DB\Migrator::executeSql', new GenericEvent($sql, [$step+1, $max])); + $this->dispatcher->dispatch('\OC\DB\Migrator::executeSql', new GenericEvent($sql, [$step + 1, $max])); } private function emitCheckStep($tableName, $step, $max) { if (is_null($this->dispatcher)) { return; } - $this->dispatcher->dispatch('\OC\DB\Migrator::checkTable', new GenericEvent($tableName, [$step+1, $max])); + $this->dispatcher->dispatch('\OC\DB\Migrator::checkTable', new GenericEvent($tableName, [$step + 1, $max])); } } diff --git a/lib/private/DB/MissingPrimaryKeyInformation.php b/lib/private/DB/MissingPrimaryKeyInformation.php new file mode 100644 index 00000000000..62b5dd2c631 --- /dev/null +++ b/lib/private/DB/MissingPrimaryKeyInformation.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2018 Morris Jobke <hey@morrisjobke.de> + * + * @author Joas Schilling <coding@schilljs.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\DB; + +class MissingPrimaryKeyInformation { + private $listOfMissingPrimaryKeys = []; + + public function addHintForMissingSubject(string $tableName) { + $this->listOfMissingPrimaryKeys[] = [ + 'tableName' => $tableName, + ]; + } + + public function getListOfMissingPrimaryKeys(): array { + return $this->listOfMissingPrimaryKeys; + } +} diff --git a/lib/private/DB/OracleConnection.php b/lib/private/DB/OracleConnection.php index b53ee850149..8198f3bd5e5 100644 --- a/lib/private/DB/OracleConnection.php +++ b/lib/private/DB/OracleConnection.php @@ -8,6 +8,7 @@ * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> + * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license AGPL-3.0 * @@ -47,35 +48,35 @@ class OracleConnection extends Connection { /** * {@inheritDoc} */ - public function insert($tableName, array $data, array $types = []) { - if ($tableName[0] !== $this->getDatabasePlatform()->getIdentifierQuoteCharacter()) { - $tableName = $this->quoteIdentifier($tableName); + public function insert($table, array $data, array $types = []) { + if ($table[0] !== $this->getDatabasePlatform()->getIdentifierQuoteCharacter()) { + $table = $this->quoteIdentifier($table); } $data = $this->quoteKeys($data); - return parent::insert($tableName, $data, $types); + return parent::insert($table, $data, $types); } /** * {@inheritDoc} */ - public function update($tableName, array $data, array $identifier, array $types = []) { - if ($tableName[0] !== $this->getDatabasePlatform()->getIdentifierQuoteCharacter()) { - $tableName = $this->quoteIdentifier($tableName); + public function update($table, array $data, array $criteria, array $types = []) { + if ($table[0] !== $this->getDatabasePlatform()->getIdentifierQuoteCharacter()) { + $table = $this->quoteIdentifier($table); } $data = $this->quoteKeys($data); - $identifier = $this->quoteKeys($identifier); - return parent::update($tableName, $data, $identifier, $types); + $criteria = $this->quoteKeys($criteria); + return parent::update($table, $data, $criteria, $types); } /** * {@inheritDoc} */ - public function delete($tableExpression, array $identifier, array $types = []) { - if ($tableExpression[0] !== $this->getDatabasePlatform()->getIdentifierQuoteCharacter()) { - $tableExpression = $this->quoteIdentifier($tableExpression); + public function delete($table, array $criteria, array $types = []) { + if ($table[0] !== $this->getDatabasePlatform()->getIdentifierQuoteCharacter()) { + $table = $this->quoteIdentifier($table); } - $identifier = $this->quoteKeys($identifier); - return parent::delete($tableExpression, $identifier); + $criteria = $this->quoteKeys($criteria); + return parent::delete($table, $criteria); } /** diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php index f4b434466e4..61eea80640e 100644 --- a/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php +++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php @@ -2,6 +2,7 @@ /** * @copyright Copyright (c) 2016, ownCloud, Inc. * + * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Joas Schilling <coding@schilljs.com> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> @@ -33,6 +34,7 @@ use OC\DB\QueryBuilder\QueryFunction; use OC\DB\QueryBuilder\QuoteHelper; use OCP\DB\QueryBuilder\IExpressionBuilder; use OCP\DB\QueryBuilder\ILiteral; +use OCP\DB\QueryBuilder\IParameter; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryFunction; use OCP\IDBConnection; @@ -278,7 +280,7 @@ class ExpressionBuilder implements IExpressionBuilder { /** * Creates a LIKE() comparison expression with the given arguments. * - * @param string $x Field in string format to be inspected by LIKE() comparison. + * @param ILiteral|IParameter|IQueryFunction|string $x Field in string format to be inspected by LIKE() comparison. * @param mixed $y Argument to be used in LIKE() comparison. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants * required when comparing text fields for oci compatibility @@ -309,7 +311,7 @@ class ExpressionBuilder implements IExpressionBuilder { /** * Creates a NOT LIKE() comparison expression with the given arguments. * - * @param string $x Field in string format to be inspected by NOT LIKE() comparison. + * @param ILiteral|IParameter|IQueryFunction|string $x Field in string format to be inspected by NOT LIKE() comparison. * @param mixed $y Argument to be used in NOT LIKE() comparison. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants * required when comparing text fields for oci compatibility @@ -325,8 +327,8 @@ class ExpressionBuilder implements IExpressionBuilder { /** * Creates a IN () comparison expression with the given arguments. * - * @param string $x The field in string format to be inspected by IN() comparison. - * @param string|array $y The placeholder or the array of values to be used by IN() comparison. + * @param ILiteral|IParameter|IQueryFunction|string $x The field in string format to be inspected by IN() comparison. + * @param ILiteral|IParameter|IQueryFunction|string|array $y The placeholder or the array of values to be used by IN() comparison. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants * required when comparing text fields for oci compatibility * @@ -341,8 +343,8 @@ class ExpressionBuilder implements IExpressionBuilder { /** * Creates a NOT IN () comparison expression with the given arguments. * - * @param string $x The field in string format to be inspected by NOT IN() comparison. - * @param string|array $y The placeholder or the array of values to be used by NOT IN() comparison. + * @param ILiteral|IParameter|IQueryFunction|string $x The field in string format to be inspected by NOT IN() comparison. + * @param ILiteral|IParameter|IQueryFunction|string|array $y The placeholder or the array of values to be used by NOT IN() comparison. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants * required when comparing text fields for oci compatibility * diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php index 2aa007dba04..f41242fdc60 100644 --- a/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php +++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php @@ -170,6 +170,10 @@ class OCIExpressionBuilder extends ExpressionBuilder { $column = $this->helper->quoteColumnName($column); return new QueryFunction('to_char(' . $column . ')'); } + if ($type === IQueryBuilder::PARAM_INT) { + $column = $this->helper->quoteColumnName($column); + return new QueryFunction('to_number(to_char(' . $column . '))'); + } return parent::castColumn($column, $type); } @@ -185,8 +189,6 @@ class OCIExpressionBuilder extends ExpressionBuilder { * @inheritdoc */ public function iLike($x, $y, $type = null) { - $x = $this->helper->quoteColumnName($x); - $y = $this->helper->quoteColumnName($y); - return new QueryFunction('REGEXP_LIKE(' . $x . ', \'^\' || REPLACE(REPLACE(' . $y . ', \'%\', \'.*\'), \'_\', \'.\') || \'$\', \'i\')'); + return $this->like($this->functionBuilder->lower($x), $this->functionBuilder->lower($y)); } } diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php index a49edc505f5..b8c54546b78 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php @@ -27,6 +27,7 @@ namespace OC\DB\QueryBuilder\FunctionBuilder; use OC\DB\QueryBuilder\QueryFunction; use OC\DB\QueryBuilder\QuoteHelper; use OCP\DB\QueryBuilder\IFunctionBuilder; +use OCP\DB\QueryBuilder\IQueryFunction; class FunctionBuilder implements IFunctionBuilder { /** @var QuoteHelper */ @@ -41,15 +42,15 @@ class FunctionBuilder implements IFunctionBuilder { $this->helper = $helper; } - public function md5($input) { + public function md5($input): IQueryFunction { return new QueryFunction('MD5(' . $this->helper->quoteColumnName($input) . ')'); } - public function concat($x, $y) { + public function concat($x, $y): IQueryFunction { return new QueryFunction('CONCAT(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')'); } - public function substring($input, $start, $length = null) { + public function substring($input, $start, $length = null): IQueryFunction { if ($length) { return new QueryFunction('SUBSTR(' . $this->helper->quoteColumnName($input) . ', ' . $this->helper->quoteColumnName($start) . ', ' . $this->helper->quoteColumnName($length) . ')'); } else { @@ -57,41 +58,41 @@ class FunctionBuilder implements IFunctionBuilder { } } - public function sum($field) { + public function sum($field): IQueryFunction { return new QueryFunction('SUM(' . $this->helper->quoteColumnName($field) . ')'); } - public function lower($field) { + public function lower($field): IQueryFunction { return new QueryFunction('LOWER(' . $this->helper->quoteColumnName($field) . ')'); } - public function add($x, $y) { + public function add($x, $y): IQueryFunction { return new QueryFunction($this->helper->quoteColumnName($x) . ' + ' . $this->helper->quoteColumnName($y)); } - public function subtract($x, $y) { + public function subtract($x, $y): IQueryFunction { return new QueryFunction($this->helper->quoteColumnName($x) . ' - ' . $this->helper->quoteColumnName($y)); } - public function count($count = '', $alias = '') { + public function count($count = '', $alias = ''): IQueryFunction { $alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : ''; $quotedName = $count === '' ? '*' : $this->helper->quoteColumnName($count); return new QueryFunction('COUNT(' . $quotedName . ')' . $alias); } - public function max($field) { + public function max($field): IQueryFunction { return new QueryFunction('MAX(' . $this->helper->quoteColumnName($field) . ')'); } - public function min($field) { + public function min($field): IQueryFunction { return new QueryFunction('MIN(' . $this->helper->quoteColumnName($field) . ')'); } - public function greatest($x, $y) { + public function greatest($x, $y): IQueryFunction { return new QueryFunction('GREATEST(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')'); } - public function least($x, $y) { + public function least($x, $y): IQueryFunction { return new QueryFunction('LEAST(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')'); } } diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php index f9347210c13..c269fc03c8a 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php @@ -2,6 +2,7 @@ /** * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> * + * @author Joas Schilling <coding@schilljs.com> * @author Robin Appelman <robin@icewind.nl> * * @license GNU AGPL version 3 or any later version @@ -24,9 +25,52 @@ namespace OC\DB\QueryBuilder\FunctionBuilder; use OC\DB\QueryBuilder\QueryFunction; +use OCP\DB\QueryBuilder\ILiteral; +use OCP\DB\QueryBuilder\IParameter; +use OCP\DB\QueryBuilder\IQueryFunction; class OCIFunctionBuilder extends FunctionBuilder { - public function md5($input) { + public function md5($input): IQueryFunction { return new QueryFunction('LOWER(DBMS_OBFUSCATION_TOOLKIT.md5 (input => UTL_RAW.cast_to_raw(' . $this->helper->quoteColumnName($input) .')))'); } + + /** + * As per https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions060.htm + * Oracle uses the first value to cast the rest or the values. So when the + * first value is a literal, plain value or column, instead of doing the + * math, it will cast the expression to int and continue with a "0". So when + * the second parameter is a function or column, we have to put that as + * first parameter. + * + * @param string|ILiteral|IParameter|IQueryFunction $x + * @param string|ILiteral|IParameter|IQueryFunction $y + * @return IQueryFunction + */ + public function greatest($x, $y): IQueryFunction { + if (is_string($y) || $y instanceof IQueryFunction) { + return parent::greatest($y, $x); + } + + return parent::greatest($x, $y); + } + + /** + * As per https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions060.htm + * Oracle uses the first value to cast the rest or the values. So when the + * first value is a literal, plain value or column, instead of doing the + * math, it will cast the expression to int and continue with a "0". So when + * the second parameter is a function or column, we have to put that as + * first parameter. + * + * @param string|ILiteral|IParameter|IQueryFunction $x + * @param string|ILiteral|IParameter|IQueryFunction $y + * @return IQueryFunction + */ + public function least($x, $y): IQueryFunction { + if (is_string($y) || $y instanceof IQueryFunction) { + return parent::least($y, $x); + } + + return parent::least($x, $y); + } } diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php index 8753d26104f..a44b80bbaaf 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php @@ -25,9 +25,10 @@ namespace OC\DB\QueryBuilder\FunctionBuilder; use OC\DB\QueryBuilder\QueryFunction; +use OCP\DB\QueryBuilder\IQueryFunction; class PgSqlFunctionBuilder extends FunctionBuilder { - public function concat($x, $y) { + public function concat($x, $y): IQueryFunction { return new QueryFunction('(' . $this->helper->quoteColumnName($x) . ' || ' . $this->helper->quoteColumnName($y) . ')'); } } diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php index 6d8e947c407..8e490c15a3c 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php @@ -25,17 +25,18 @@ namespace OC\DB\QueryBuilder\FunctionBuilder; use OC\DB\QueryBuilder\QueryFunction; +use OCP\DB\QueryBuilder\IQueryFunction; class SqliteFunctionBuilder extends FunctionBuilder { - public function concat($x, $y) { + public function concat($x, $y): IQueryFunction { return new QueryFunction('(' . $this->helper->quoteColumnName($x) . ' || ' . $this->helper->quoteColumnName($y) . ')'); } - public function greatest($x, $y) { + public function greatest($x, $y): IQueryFunction { return new QueryFunction('MAX(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')'); } - public function least($x, $y) { + public function least($x, $y): IQueryFunction { return new QueryFunction('MIN(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')'); } } diff --git a/lib/private/DB/QueryBuilder/QueryBuilder.php b/lib/private/DB/QueryBuilder/QueryBuilder.php index a2941950d5c..2f8707326de 100644 --- a/lib/private/DB/QueryBuilder/QueryBuilder.php +++ b/lib/private/DB/QueryBuilder/QueryBuilder.php @@ -3,11 +3,13 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -30,6 +32,7 @@ namespace OC\DB\QueryBuilder; use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\DBAL\Platforms\PostgreSqlPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Query\QueryException; use OC\DB\OracleConnection; use OC\DB\QueryBuilder\ExpressionBuilder\ExpressionBuilder; use OC\DB\QueryBuilder\ExpressionBuilder\MySqlExpressionBuilder; @@ -41,6 +44,7 @@ use OC\DB\QueryBuilder\FunctionBuilder\OCIFunctionBuilder; use OC\DB\QueryBuilder\FunctionBuilder\PgSqlFunctionBuilder; use OC\DB\QueryBuilder\FunctionBuilder\SqliteFunctionBuilder; use OC\SystemConfig; +use OCP\DB\QueryBuilder\ILiteral; use OCP\DB\QueryBuilder\IParameter; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryFunction; @@ -191,23 +195,51 @@ class QueryBuilder implements IQueryBuilder { */ public function execute() { if ($this->systemConfig->getValue('log_query', false)) { - $params = []; - foreach ($this->getParameters() as $placeholder => $value) { - if (is_array($value)) { - $params[] = $placeholder . ' => (\'' . implode('\', \'', $value) . '\')'; + try { + $params = []; + foreach ($this->getParameters() as $placeholder => $value) { + if ($value instanceof \DateTime) { + $params[] = $placeholder . ' => DateTime:\'' . $value->format('c') . '\''; + } elseif (is_array($value)) { + $params[] = $placeholder . ' => (\'' . implode('\', \'', $value) . '\')'; + } else { + $params[] = $placeholder . ' => \'' . $value . '\''; + } + } + if (empty($params)) { + $this->logger->debug('DB QueryBuilder: \'{query}\'', [ + 'query' => $this->getSQL(), + 'app' => 'core', + ]); } else { - $params[] = $placeholder . ' => \'' . $value . '\''; + $this->logger->debug('DB QueryBuilder: \'{query}\' with parameters: {params}', [ + 'query' => $this->getSQL(), + 'params' => implode(', ', $params), + 'app' => 'core', + ]); } + } catch (\Error $e) { + // likely an error during conversion of $value to string + $this->logger->debug('DB QueryBuilder: error trying to log SQL query'); + $this->logger->logException($e); } - if (empty($params)) { - $this->logger->debug('DB QueryBuilder: \'{query}\'', [ - 'query' => $this->getSQL(), - 'app' => 'core', - ]); - } else { - $this->logger->debug('DB QueryBuilder: \'{query}\' with parameters: {params}', [ + } + + if (!empty($this->getQueryPart('select'))) { + $select = $this->getQueryPart('select'); + $hasSelectAll = array_filter($select, static function ($s) { + return $s === '*'; + }); + $hasSelectSpecific = array_filter($select, static function ($s) { + return $s !== '*'; + }); + + if (empty($hasSelectAll) === empty($hasSelectSpecific)) { + $exception = new QueryException('Query is selecting * and specific values in the same query. This is not supported in Oracle.'); + $this->logger->logException($exception, [ + 'message' => 'Query is selecting * and specific values in the same query. This is not supported in Oracle.', 'query' => $this->getSQL(), - 'params' => implode(', ', $params), + 'level' => ILogger::ERROR, 'app' => 'core', ]); } @@ -364,7 +396,7 @@ class QueryBuilder implements IQueryBuilder { * Gets the maximum number of results the query object was set to retrieve (the "limit"). * Returns NULL if {@link setMaxResults} was not applied to this query builder. * - * @return integer The maximum number of results. + * @return int|null The maximum number of results. */ public function getMaxResults() { return $this->queryBuilder->getMaxResults(); @@ -434,8 +466,14 @@ class QueryBuilder implements IQueryBuilder { * @return $this This QueryBuilder instance. */ public function selectDistinct($select) { + if (!is_array($select)) { + $select = [$select]; + } + + $quotedSelect = $this->helper->quoteColumnNames($select); + $this->queryBuilder->addSelect( - 'DISTINCT ' . $this->helper->quoteColumnName($select) + 'DISTINCT ' . implode(', ', $quotedSelect) ); return $this; @@ -694,7 +732,7 @@ class QueryBuilder implements IQueryBuilder { * </code> * * @param string $key The column to set. - * @param string $value The value, expression, placeholder, etc. + * @param ILiteral|IParameter|IQueryFunction|string $value The value, expression, placeholder, etc. * * @return $this This QueryBuilder instance. */ @@ -867,14 +905,14 @@ class QueryBuilder implements IQueryBuilder { * </code> * * @param string $column The column into which the value should be inserted. - * @param string $value The value that should be inserted into the column. + * @param IParameter|string $value The value that should be inserted into the column. * * @return $this This QueryBuilder instance. */ public function setValue($column, $value) { $this->queryBuilder->setValue( $this->helper->quoteColumnName($column), - $value + (string) $value ); return $this; diff --git a/lib/private/DB/SchemaWrapper.php b/lib/private/DB/SchemaWrapper.php index e42535d64ab..440008d35b3 100644 --- a/lib/private/DB/SchemaWrapper.php +++ b/lib/private/DB/SchemaWrapper.php @@ -111,6 +111,7 @@ class SchemaWrapper implements ISchemaWrapper { * @return \Doctrine\DBAL\Schema\Table */ public function createTable($tableName) { + unset($this->tablesToDelete[$tableName]); return $this->schema->createTable($this->connection->getPrefix() . $tableName); } diff --git a/lib/private/Dashboard/Manager.php b/lib/private/Dashboard/Manager.php index 0c285a8b53d..9f703bb14ad 100644 --- a/lib/private/Dashboard/Manager.php +++ b/lib/private/Dashboard/Manager.php @@ -5,6 +5,7 @@ declare(strict_types=1); /** * @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net> * + * @author Joas Schilling <coding@schilljs.com> * @author Julius Härtl <jus@bitgrid.net> * * @license GNU AGPL version 3 or any later version @@ -16,7 +17,7 @@ declare(strict_types=1); * * 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 + * 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 @@ -29,7 +30,7 @@ namespace OC\Dashboard; use InvalidArgumentException; use OCP\AppFramework\QueryException; use OCP\Dashboard\IManager; -use OCP\Dashboard\IPanel; +use OCP\Dashboard\IWidget; use OCP\ILogger; use OCP\IServerContainer; use Throwable; @@ -37,10 +38,10 @@ use Throwable; class Manager implements IManager { /** @var array */ - private $lazyPanels = []; + private $lazyWidgets = []; - /** @var IPanel[] */ - private $panels = []; + /** @var IWidget[] */ + private $widgets = []; /** @var IServerContainer */ private $serverContainer; @@ -49,31 +50,31 @@ class Manager implements IManager { $this->serverContainer = $serverContainer; } - private function registerPanel(IPanel $panel): void { - if (array_key_exists($panel->getId(), $this->panels)) { - throw new InvalidArgumentException('Dashboard panel with this id has already been registered'); + private function registerWidget(IWidget $widget): void { + if (array_key_exists($widget->getId(), $this->widgets)) { + throw new InvalidArgumentException('Dashboard widget with this id has already been registered'); } - $this->panels[$panel->getId()] = $panel; + $this->widgets[$widget->getId()] = $widget; } - public function lazyRegisterPanel(string $panelClass): void { - $this->lazyPanels[] = $panelClass; + public function lazyRegisterWidget(string $widgetClass): void { + $this->lazyWidgets[] = $widgetClass; } public function loadLazyPanels(): void { - $classes = $this->lazyPanels; + $classes = $this->lazyWidgets; foreach ($classes as $class) { try { - /** @var IPanel $panel */ - $panel = $this->serverContainer->query($class); + /** @var IWidget $widget */ + $widget = $this->serverContainer->query($class); } catch (QueryException $e) { /* * There is a circular dependency between the logger and the registry, so * we can not inject it. Thus the static call. */ \OC::$server->getLogger()->logException($e, [ - 'message' => 'Could not load lazy dashbaord panel: ' . $e->getMessage(), + 'message' => 'Could not load lazy dashbaord widget: ' . $e->getMessage(), 'level' => ILogger::FATAL, ]); } @@ -82,32 +83,41 @@ class Manager implements IManager { * type, so we might get a TypeError here that we should catch. */ try { - $this->registerPanel($panel); + $this->registerWidget($widget); } catch (Throwable $e) { /* * There is a circular dependency between the logger and the registry, so * we can not inject it. Thus the static call. */ \OC::$server->getLogger()->logException($e, [ - 'message' => 'Could not register lazy dashboard panel: ' . $e->getMessage(), + 'message' => 'Could not register lazy dashboard widget: ' . $e->getMessage(), 'level' => ILogger::FATAL, ]); } try { - $panel->load(); + $startTime = microtime(true); + $widget->load(); + $endTime = microtime(true); + $duration = $endTime - $startTime; + if ($duration > 1) { + \OC::$server->getLogger()->error('Dashboard widget {widget} took {duration} seconds to load.', [ + 'widget' => $widget->getId(), + 'duration' => round($duration, 2), + ]); + } } catch (Throwable $e) { \OC::$server->getLogger()->logException($e, [ - 'message' => 'Error during dashboard panel loading: ' . $e->getMessage(), + 'message' => 'Error during dashboard widget loading: ' . $e->getMessage(), 'level' => ILogger::FATAL, ]); } } - $this->lazyPanels = []; + $this->lazyWidgets = []; } - public function getPanels(): array { + public function getWidgets(): array { $this->loadLazyPanels(); - return $this->panels; + return $this->widgets; } } diff --git a/lib/private/DirectEditing/Manager.php b/lib/private/DirectEditing/Manager.php index c3098fb1a97..0e7e988eef2 100644 --- a/lib/private/DirectEditing/Manager.php +++ b/lib/private/DirectEditing/Manager.php @@ -35,6 +35,7 @@ use OCP\DirectEditing\ACreateFromTemplate; use OCP\DirectEditing\IEditor; use \OCP\DirectEditing\IManager; use OCP\DirectEditing\IToken; +use OCP\Encryption\IManager as EncryptionManager; use OCP\Files\File; use OCP\Files\IRootFolder; use OCP\Files\Node; @@ -45,6 +46,7 @@ use OCP\IUserSession; use OCP\L10N\IFactory; use OCP\Security\ISecureRandom; use OCP\Share\IShare; +use Throwable; use function array_key_exists; use function in_array; @@ -55,30 +57,33 @@ class Manager implements IManager { /** @var IEditor[] */ private $editors = []; - /** @var IDBConnection */ private $connection; - /** - * @var ISecureRandom - */ + /** @var ISecureRandom */ private $random; + /** @var string|null */ private $userId; + /** @var IRootFolder */ private $rootFolder; /** @var IL10N */ private $l10n; + /** @var EncryptionManager */ + private $encryptionManager; public function __construct( ISecureRandom $random, IDBConnection $connection, IUserSession $userSession, IRootFolder $rootFolder, - IFactory $l10nFactory + IFactory $l10nFactory, + EncryptionManager $encryptionManager ) { $this->random = $random; $this->connection = $connection; $this->userId = $userSession->getUser() ? $userSession->getUser()->getUID() : null; $this->rootFolder = $rootFolder; $this->l10n = $l10nFactory->get('core'); + $this->encryptionManager = $encryptionManager; } public function registerDirectEditor(IEditor $directEditor): void { @@ -116,7 +121,7 @@ class Manager implements IManager { } } $return = []; - $return['templates'] = $templates; + $return['templates'] = $templates; return $return; } @@ -171,7 +176,7 @@ class Manager implements IManager { } $editor = $this->getEditor($tokenObject->getEditor()); $this->accessToken($token); - } catch (\Throwable $throwable) { + } catch (Throwable $throwable) { $this->invalidateToken($token); return new NotFoundResponse(); } @@ -275,4 +280,22 @@ class Manager implements IManager { } return $files[0]; } + + public function isEnabled(): bool { + if (!$this->encryptionManager->isEnabled()) { + return true; + } + + try { + $moduleId = $this->encryptionManager->getDefaultEncryptionModuleId(); + $module = $this->encryptionManager->getEncryptionModule($moduleId); + /** @var \OCA\Encryption\Util $util */ + $util = \OC::$server->get(\OCA\Encryption\Util::class); + if ($module->isReadyForUser($this->userId) && $util->isMasterKeyEnabled()) { + return true; + } + } catch (Throwable $e) { + } + return false; + } } diff --git a/lib/private/Encryption/DecryptAll.php b/lib/private/Encryption/DecryptAll.php index 19bd2f81378..fa913d3655b 100644 --- a/lib/private/Encryption/DecryptAll.php +++ b/lib/private/Encryption/DecryptAll.php @@ -9,7 +9,7 @@ * @author Joas Schilling <coding@schilljs.com> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author sammo2828 <sammo2828@gmail.com> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Encryption/File.php b/lib/private/Encryption/File.php index b631e4fc63f..c0a58d1256e 100644 --- a/lib/private/Encryption/File.php +++ b/lib/private/Encryption/File.php @@ -5,9 +5,10 @@ * @author Björn Schießle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Joas Schilling <coding@schilljs.com> + * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Encryption/Keys/Storage.php b/lib/private/Encryption/Keys/Storage.php index cee32691261..a2b27de6b51 100644 --- a/lib/private/Encryption/Keys/Storage.php +++ b/lib/private/Encryption/Keys/Storage.php @@ -8,7 +8,7 @@ * @author Joas Schilling <coding@schilljs.com> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -31,8 +31,11 @@ namespace OC\Encryption\Keys; use OC\Encryption\Util; use OC\Files\Filesystem; use OC\Files\View; +use OC\ServerNotAvailableException; use OC\User\NoUserException; use OCP\Encryption\Keys\IStorage; +use OCP\IConfig; +use OCP\Security\ICrypto; class Storage implements IStorage { @@ -62,11 +65,17 @@ class Storage implements IStorage { /** @var array */ private $keyCache = []; + /** @var ICrypto */ + private $crypto; + + /** @var IConfig */ + private $config; + /** * @param View $view * @param Util $util */ - public function __construct(View $view, Util $util) { + public function __construct(View $view, Util $util, ICrypto $crypto, IConfig $config) { $this->view = $view; $this->util = $util; @@ -74,6 +83,8 @@ class Storage implements IStorage { $this->keys_base_dir = $this->encryption_base_dir .'/keys'; $this->backup_base_dir = $this->encryption_base_dir .'/backup'; $this->root_dir = $this->util->getKeyStorageRoot(); + $this->crypto = $crypto; + $this->config = $config; } /** @@ -81,7 +92,7 @@ class Storage implements IStorage { */ public function getUserKey($uid, $keyId, $encryptionModuleId) { $path = $this->constructUserKeyPath($encryptionModuleId, $keyId, $uid); - return $this->getKey($path); + return base64_decode($this->getKeyWithUid($path, $uid)); } /** @@ -90,17 +101,17 @@ class Storage implements IStorage { public function getFileKey($path, $keyId, $encryptionModuleId) { $realFile = $this->util->stripPartialFileExtension($path); $keyDir = $this->getFileKeyDir($encryptionModuleId, $realFile); - $key = $this->getKey($keyDir . $keyId); + $key = $this->getKey($keyDir . $keyId)['key']; if ($key === '' && $realFile !== $path) { // Check if the part file has keys and use them, if no normal keys // exist. This is required to fix copyBetweenStorage() when we // rename a .part file over storage borders. $keyDir = $this->getFileKeyDir($encryptionModuleId, $path); - $key = $this->getKey($keyDir . $keyId); + $key = $this->getKey($keyDir . $keyId)['key']; } - return $key; + return base64_decode($key); } /** @@ -108,7 +119,7 @@ class Storage implements IStorage { */ public function getSystemUserKey($keyId, $encryptionModuleId) { $path = $this->constructUserKeyPath($encryptionModuleId, $keyId, null); - return $this->getKey($path); + return base64_decode($this->getKeyWithUid($path, null)); } /** @@ -116,7 +127,10 @@ class Storage implements IStorage { */ public function setUserKey($uid, $keyId, $key, $encryptionModuleId) { $path = $this->constructUserKeyPath($encryptionModuleId, $keyId, $uid); - return $this->setKey($path, $key); + return $this->setKey($path, [ + 'key' => base64_encode($key), + 'uid' => $uid, + ]); } /** @@ -124,7 +138,9 @@ class Storage implements IStorage { */ public function setFileKey($path, $keyId, $key, $encryptionModuleId) { $keyDir = $this->getFileKeyDir($encryptionModuleId, $path); - return $this->setKey($keyDir . $keyId, $key); + return $this->setKey($keyDir . $keyId, [ + 'key' => base64_encode($key), + ]); } /** @@ -132,7 +148,10 @@ class Storage implements IStorage { */ public function setSystemUserKey($keyId, $key, $encryptionModuleId) { $path = $this->constructUserKeyPath($encryptionModuleId, $keyId, null); - return $this->setKey($path, $key); + return $this->setKey($path, [ + 'key' => base64_encode($key), + 'uid' => null, + ]); } /** @@ -200,19 +219,106 @@ class Storage implements IStorage { } /** + * @param string $path + * @param string|null $uid + * @return string + * @throws ServerNotAvailableException + * + * Small helper function to fetch the key and verify the value for user and system keys + */ + private function getKeyWithUid(string $path, ?string $uid): string { + $data = $this->getKey($path); + + if (!isset($data['key'])) { + throw new ServerNotAvailableException('Key is invalid'); + } + + if ($data['key'] === '') { + return ''; + } + + if (!array_key_exists('uid', $data) || $data['uid'] !== $uid) { + // If the migration is done we error out + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0.0'); + if (version_compare($versionFromBeforeUpdate, '20.0.0.1', '<=')) { + return $data['key']; + } + + if ($this->config->getSystemValueBool('encryption.key_storage_migrated', true)) { + throw new ServerNotAvailableException('Key has been modified'); + } else { + //Otherwise we migrate + $data['uid'] = $uid; + $this->setKey($path, $data); + } + } + + return $data['key']; + } + + /** * read key from hard disk * * @param string $path to key - * @return string + * @return array containing key as base64encoded key, and possible the uid */ - private function getKey($path) { - $key = ''; + private function getKey($path): array { + $key = [ + 'key' => '', + ]; if ($this->view->file_exists($path)) { if (isset($this->keyCache[$path])) { - $key = $this->keyCache[$path]; + $key = $this->keyCache[$path]; } else { - $key = $this->view->file_get_contents($path); + $data = $this->view->file_get_contents($path); + + // Version <20.0.0.1 doesn't have this + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0.0'); + if (version_compare($versionFromBeforeUpdate, '20.0.0.1', '<=')) { + $key = [ + 'key' => base64_encode($data), + ]; + } else { + if ($this->config->getSystemValueBool('encryption.key_storage_migrated', true)) { + try { + $clearData = $this->crypto->decrypt($data); + } catch (\Exception $e) { + throw new ServerNotAvailableException('Could not decrypt key', 0, $e); + } + + $dataArray = json_decode($clearData, true); + if ($dataArray === null) { + throw new ServerNotAvailableException('Invalid encryption key'); + } + + $key = $dataArray; + } else { + /* + * Even if not all keys are migrated we should still try to decrypt it (in case some have moved). + * However it is only a failure now if it is an array and decryption fails + */ + $fallback = false; + try { + $clearData = $this->crypto->decrypt($data); + } catch (\Exception $e) { + $fallback = true; + } + + if (!$fallback) { + $dataArray = json_decode($clearData, true); + if ($dataArray === null) { + throw new ServerNotAvailableException('Invalid encryption key'); + } + $key = $dataArray; + } else { + $key = [ + 'key' => base64_encode($data), + ]; + } + } + } + $this->keyCache[$path] = $key; } } @@ -225,13 +331,23 @@ class Storage implements IStorage { * * * @param string $path path to key directory - * @param string $key key + * @param array $key key * @return bool */ private function setKey($path, $key) { $this->keySetPreparation(dirname($path)); - $result = $this->view->file_put_contents($path, $key); + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0.0'); + if (version_compare($versionFromBeforeUpdate, '20.0.0.1', '<=')) { + // Only store old format if this happens during the migration. + // TODO: Remove for 21 + $data = base64_decode($key['key']); + } else { + // Wrap the data + $data = $this->crypto->encrypt(json_encode($key)); + } + + $result = $this->view->file_put_contents($path, $data); if (is_int($result) && $result > 0) { $this->keyCache[$path] = $key; diff --git a/lib/private/Encryption/Util.php b/lib/private/Encryption/Util.php index 0bda00a5cbc..f5107d2ec43 100644 --- a/lib/private/Encryption/Util.php +++ b/lib/private/Encryption/Util.php @@ -174,7 +174,7 @@ class Util { if ($c->getType() === 'dir') { $dirList[] = $c->getPath(); } else { - $result[] = $c->getPath(); + $result[] = $c->getPath(); } } } @@ -255,7 +255,7 @@ class Util { // if path also contains a transaction id, we remove it too $extension = pathinfo($fPath, PATHINFO_EXTENSION); if (substr($extension, 0, 12) === 'ocTransferId') { // 12 = strlen("ocTransferId") - $newLength = strlen($fPath) - strlen($extension) -1; + $newLength = strlen($fPath) - strlen($extension) - 1; $fPath = substr($fPath, 0, $newLength); } return $fPath; diff --git a/lib/private/EventDispatcher/EventDispatcher.php b/lib/private/EventDispatcher/EventDispatcher.php index f2a87cc4c70..8fe4bcbb942 100644 --- a/lib/private/EventDispatcher/EventDispatcher.php +++ b/lib/private/EventDispatcher/EventDispatcher.php @@ -28,6 +28,7 @@ declare(strict_types=1); namespace OC\EventDispatcher; +use Psr\Log\LoggerInterface; use function get_class; use OC\Broadcast\Events\BroadcastEvent; use OCP\Broadcast\Events\IBroadcastEvent; @@ -35,7 +36,6 @@ use OCP\EventDispatcher\ABroadcastedEvent; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventDispatcher; use OCP\IContainer; -use OCP\ILogger; use OCP\IServerContainer; use Symfony\Component\EventDispatcher\EventDispatcher as SymfonyDispatcher; @@ -47,12 +47,12 @@ class EventDispatcher implements IEventDispatcher { /** @var IContainer */ private $container; - /** @var ILogger */ + /** @var LoggerInterface */ private $logger; public function __construct(SymfonyDispatcher $dispatcher, IServerContainer $container, - ILogger $logger) { + LoggerInterface $logger) { $this->dispatcher = $dispatcher; $this->container = $container; $this->logger = $logger; diff --git a/lib/private/EventDispatcher/GenericEventWrapper.php b/lib/private/EventDispatcher/GenericEventWrapper.php index dc3e7553f0c..916297f4998 100644 --- a/lib/private/EventDispatcher/GenericEventWrapper.php +++ b/lib/private/EventDispatcher/GenericEventWrapper.php @@ -1,9 +1,11 @@ <?php declare(strict_types=1); + /** * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> * + * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version @@ -19,7 +21,7 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ diff --git a/lib/private/EventDispatcher/ServiceEventListener.php b/lib/private/EventDispatcher/ServiceEventListener.php index a648884d6f7..301531f8931 100644 --- a/lib/private/EventDispatcher/ServiceEventListener.php +++ b/lib/private/EventDispatcher/ServiceEventListener.php @@ -6,6 +6,7 @@ declare(strict_types=1); * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at> * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * @@ -30,7 +31,7 @@ use OCP\AppFramework\QueryException; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\IContainer; -use OCP\ILogger; +use Psr\Log\LoggerInterface; /** * Lazy service event listener @@ -46,7 +47,7 @@ final class ServiceEventListener { /** @var string */ private $class; - /** @var ILogger */ + /** @var LoggerInterface */ private $logger; /** @var null|IEventListener */ @@ -54,7 +55,7 @@ final class ServiceEventListener { public function __construct(IContainer $container, string $class, - ILogger $logger) { + LoggerInterface $logger) { $this->container = $container; $this->class = $class; $this->logger = $logger; @@ -65,9 +66,8 @@ final class ServiceEventListener { try { $this->service = $this->container->query($this->class); } catch (QueryException $e) { - $this->logger->logException($e, [ - 'level' => ILogger::ERROR, - 'message' => "Could not load event listener service " . $this->class, + $this->logger->error("Could not load event listener service " . $this->class, [ + 'exception' => $e, ]); return; } diff --git a/lib/private/EventDispatcher/SymfonyAdapter.php b/lib/private/EventDispatcher/SymfonyAdapter.php index 454f4ca4b01..9389e725405 100644 --- a/lib/private/EventDispatcher/SymfonyAdapter.php +++ b/lib/private/EventDispatcher/SymfonyAdapter.php @@ -7,6 +7,7 @@ declare(strict_types=1); * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version diff --git a/lib/private/Federation/CloudFederationProviderManager.php b/lib/private/Federation/CloudFederationProviderManager.php index 459d82c5bfb..597227533f9 100644 --- a/lib/private/Federation/CloudFederationProviderManager.php +++ b/lib/private/Federation/CloudFederationProviderManager.php @@ -3,6 +3,7 @@ * @copyright Copyright (c) 2018 Bjoern Schiessle <bjoern@schiessle.org> * * @author Bjoern Schiessle <bjoern@schiessle.org> + * @author Christoph Wurst <christoph@winzerhof-wurst.at> * * @license GNU AGPL version 3 or any later version * @@ -75,7 +76,7 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager IClientService $httpClientService, ICloudIdManager $cloudIdManager, ILogger $logger) { - $this->cloudFederationProvider= []; + $this->cloudFederationProvider = []; $this->appManager = $appManager; $this->httpClientService = $httpClientService; $this->cloudIdManager = $cloudIdManager; diff --git a/lib/private/Federation/CloudFederationShare.php b/lib/private/Federation/CloudFederationShare.php index 507a3cd061a..52acee5a24e 100644 --- a/lib/private/Federation/CloudFederationShare.php +++ b/lib/private/Federation/CloudFederationShare.php @@ -3,6 +3,7 @@ * @copyright Copyright (c) 2018 Bjoern Schiessle <bjoern@schiessle.org> * * @author Bjoern Schiessle <bjoern@schiessle.org> + * @author Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * diff --git a/lib/private/Federation/CloudId.php b/lib/private/Federation/CloudId.php index 80e174589b0..27a222c0583 100644 --- a/lib/private/Federation/CloudId.php +++ b/lib/private/Federation/CloudId.php @@ -5,6 +5,7 @@ declare(strict_types=1); /** * @copyright Copyright (c) 2017, Robin Appelman <robin@icewind.nl> * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @@ -36,6 +37,8 @@ class CloudId implements ICloudId { private $user; /** @var string */ private $remote; + /** @var string|null */ + private $displayName; /** * CloudId constructor. @@ -44,10 +47,11 @@ class CloudId implements ICloudId { * @param string $user * @param string $remote */ - public function __construct(string $id, string $user, string $remote) { + public function __construct(string $id, string $user, string $remote, ?string $displayName = null) { $this->id = $id; $this->user = $user; $this->remote = $remote; + $this->displayName = $displayName; } /** @@ -60,6 +64,11 @@ class CloudId implements ICloudId { } public function getDisplayId(): string { + if ($this->displayName) { + $atPos = strrpos($this->getId(), '@'); + $atHost = substr($this->getId(), $atPos); + return $this->displayName . $atHost; + } return str_replace('https://', '', str_replace('http://', '', $this->getId())); } diff --git a/lib/private/Federation/CloudIdManager.php b/lib/private/Federation/CloudIdManager.php index a5ebc98c1fd..0671a0bfa02 100644 --- a/lib/private/Federation/CloudIdManager.php +++ b/lib/private/Federation/CloudIdManager.php @@ -5,7 +5,9 @@ declare(strict_types=1); /** * @copyright Copyright (c) 2017, Robin Appelman <robin@icewind.nl> * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Guillaume Virlet <github@virlet.org> * @author Joas Schilling <coding@schilljs.com> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> @@ -29,10 +31,18 @@ declare(strict_types=1); namespace OC\Federation; +use OCP\Contacts\IManager; use OCP\Federation\ICloudId; use OCP\Federation\ICloudIdManager; class CloudIdManager implements ICloudIdManager { + /** @var IManager */ + private $contactsManager; + + public function __construct(IManager $contactsManager) { + $this->contactsManager = $contactsManager; + } + /** * @param string $cloudId * @return ICloudId @@ -60,23 +70,38 @@ class CloudIdManager implements ICloudIdManager { $invalidPos = min($posSlash, $posColon); } - // Find the last @ before $invalidPos - $pos = $lastAtPos = 0; - while ($lastAtPos !== false && $lastAtPos <= $invalidPos) { - $pos = $lastAtPos; - $lastAtPos = strpos($id, '@', $pos + 1); - } + $lastValidAtPos = strrpos($id, '@', $invalidPos - strlen($id)); - if ($pos !== false) { - $user = substr($id, 0, $pos); - $remote = substr($id, $pos + 1); + if ($lastValidAtPos !== false) { + $user = substr($id, 0, $lastValidAtPos); + $remote = substr($id, $lastValidAtPos + 1); if (!empty($user) && !empty($remote)) { - return new CloudId($id, $user, $remote); + return new CloudId($id, $user, $remote, $this->getDisplayNameFromContact($id)); } } throw new \InvalidArgumentException('Invalid cloud id'); } + protected function getDisplayNameFromContact(string $cloudId): ?string { + $addressBookEntries = $this->contactsManager->search($cloudId, ['CLOUD']); + foreach ($addressBookEntries as $entry) { + if (isset($entry['CLOUD'])) { + foreach ($entry['CLOUD'] as $cloudID) { + if ($cloudID === $cloudId) { + // Warning, if user decides to make his full name local only, + // no FN is found on federated servers + if (isset($entry['FN'])) { + return $entry['FN']; + } else { + return $cloudID; + } + } + } + } + } + return null; + } + /** * @param string $user * @param string $remote @@ -84,7 +109,17 @@ class CloudIdManager implements ICloudIdManager { */ public function getCloudId(string $user, string $remote): ICloudId { // TODO check what the correct url is for remote (asking the remote) - return new CloudId($user. '@' . $remote, $user, $remote); + $fixedRemote = $this->fixRemoteURL($remote); + if (strpos($fixedRemote, 'http://') === 0) { + $host = substr($fixedRemote, strlen('http://')); + } elseif (strpos($fixedRemote, 'https://') === 0) { + $host = substr($fixedRemote, strlen('https://')); + } else { + $host = $fixedRemote; + } + $id = $user . '@' . $remote; + $displayName = $this->getDisplayNameFromContact($user . '@' . $host); + return new CloudId($id, $user, $fixedRemote, $displayName); } /** diff --git a/lib/private/Files/Cache/AbstractCacheEvent.php b/lib/private/Files/Cache/AbstractCacheEvent.php index a4029476fa5..bb7ade386e0 100644 --- a/lib/private/Files/Cache/AbstractCacheEvent.php +++ b/lib/private/Files/Cache/AbstractCacheEvent.php @@ -36,6 +36,7 @@ class AbstractCacheEvent extends Event implements ICacheEvent { protected $storage; protected $path; protected $fileId; + protected $storageId; /** * @param IStorage $storage @@ -43,10 +44,11 @@ class AbstractCacheEvent extends Event implements ICacheEvent { * @param int $fileId * @since 16.0.0 */ - public function __construct(IStorage $storage, string $path, int $fileId) { + public function __construct(IStorage $storage, string $path, int $fileId, int $storageId) { $this->storage = $storage; $this->path = $path; $this->fileId = $fileId; + $this->storageId = $storageId; } /** @@ -80,4 +82,12 @@ class AbstractCacheEvent extends Event implements ICacheEvent { public function getFileId(): int { return $this->fileId; } + + /** + * @return int + * @since 21.0.0 + */ + public function getStorageId(): int { + return $this->storageId; + } } diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php index de807421d26..64cd2b25ee5 100644 --- a/lib/private/Files/Cache/Cache.php +++ b/lib/private/Files/Cache/Cache.php @@ -18,7 +18,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -41,7 +41,11 @@ namespace OC\Files\Cache; use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Cache\CacheEntryInsertedEvent; +use OCP\Files\Cache\CacheEntryUpdatedEvent; use OCP\Files\Cache\CacheInsertEvent; +use OCP\Files\Cache\CacheEntryRemovedEvent; use OCP\Files\Cache\CacheUpdateEvent; use OCP\Files\Cache\ICache; use OCP\Files\Cache\ICacheEntry; @@ -91,6 +95,9 @@ class Cache implements ICache { */ protected $connection; + /** + * @var IEventDispatcher + */ protected $eventDispatcher; /** @var QuerySearchHelper */ @@ -109,7 +116,7 @@ class Cache implements ICache { $this->storageCache = new Storage($storage); $this->mimetypeLoader = \OC::$server->getMimeTypeLoader(); $this->connection = \OC::$server->getDatabaseConnection(); - $this->eventDispatcher = \OC::$server->getEventDispatcher(); + $this->eventDispatcher = \OC::$server->get(IEventDispatcher::class); $this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader); } @@ -151,7 +158,9 @@ class Cache implements ICache { $query->whereFileId($file); } - $data = $query->execute()->fetch(); + $result = $query->execute(); + $data = $result->fetch(); + $result->closeCursor(); //merge partial data if (!$data and is_string($file) and isset($this->partial[$file])) { @@ -220,7 +229,10 @@ class Cache implements ICache { ->whereParent($fileId) ->orderBy('name', 'ASC'); - $files = $query->execute()->fetchAll(); + $result = $query->execute(); + $files = $result->fetchAll(); + $result->closeCursor(); + return array_map(function (array $data) { return self::cacheEntryFromData($data, $this->mimetypeLoader); }, $files); @@ -254,8 +266,6 @@ class Cache implements ICache { * * @return int file id * @throws \RuntimeException - * - * @suppress SqlInjectionChecker */ public function insert($file, array $data) { // normalize file @@ -281,7 +291,8 @@ class Cache implements ICache { $data['name'] = basename($file); [$values, $extensionValues] = $this->normalizeData($data); - $values['storage'] = $this->getNumericStorageId(); + $storageId = $this->getNumericStorageId(); + $values['storage'] = $storageId; try { $builder = $this->connection->getQueryBuilder(); @@ -305,7 +316,9 @@ class Cache implements ICache { $query->execute(); } - $this->eventDispatcher->dispatch(CacheInsertEvent::class, new CacheInsertEvent($this->storage, $file, $fileId)); + $event = new CacheEntryInsertedEvent($this->storage, $file, $fileId, $storageId); + $this->eventDispatcher->dispatch(CacheInsertEvent::class, $event); + $this->eventDispatcher->dispatchTyped($event); return $fileId; } } catch (UniqueConstraintViolationException $e) { @@ -396,7 +409,9 @@ class Cache implements ICache { $path = $this->getPathById($id); // path can still be null if the file doesn't exist if ($path !== null) { - $this->eventDispatcher->dispatch(CacheUpdateEvent::class, new CacheUpdateEvent($this->storage, $path, $id)); + $event = new CacheEntryUpdatedEvent($this->storage, $path, $id, $this->getNumericStorageId()); + $this->eventDispatcher->dispatch(CacheUpdateEvent::class, $event); + $this->eventDispatcher->dispatchTyped($event); } } @@ -469,7 +484,10 @@ class Cache implements ICache { ->whereStorageId() ->wherePath($file); - $id = $query->execute()->fetchColumn(); + $result = $query->execute(); + $id = $result->fetchColumn(); + $result->closeCursor(); + return $id === false ? -1 : (int)$id; } @@ -530,6 +548,8 @@ class Cache implements ICache { if ($entry->getMimeType() == FileInfo::MIMETYPE_FOLDER) { $this->removeChildren($entry); } + + $this->eventDispatcher->dispatchTyped(new CacheEntryRemovedEvent($this->storage, $entry->getPath(), $entry->getId(), $this->getNumericStorageId())); } } @@ -553,25 +573,35 @@ class Cache implements ICache { * @throws \OC\DatabaseException */ private function removeChildren(ICacheEntry $entry) { - $children = $this->getFolderContentsById($entry->getId()); - $childIds = array_map(function (ICacheEntry $cacheEntry) { - return $cacheEntry->getId(); - }, $children); - $childFolders = array_filter($children, function ($child) { - return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER; - }); - foreach ($childFolders as $folder) { - $this->removeChildren($folder); + $parentIds = [$entry->getId()]; + $queue = [$entry->getId()]; + + // we walk depth first trough the file tree, removing all filecache_extended attributes while we walk + // and collecting all folder ids to later use to delete the filecache entries + while ($entryId = array_pop($queue)) { + $children = $this->getFolderContentsById($entryId); + $childIds = array_map(function (ICacheEntry $cacheEntry) { + return $cacheEntry->getId(); + }, $children); + + $query = $this->getQueryBuilder(); + $query->delete('filecache_extended') + ->where($query->expr()->in('fileid', $query->createNamedParameter($childIds, IQueryBuilder::PARAM_INT_ARRAY))); + $query->execute(); + + /** @var ICacheEntry[] $childFolders */ + $childFolders = array_filter($children, function ($child) { + return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER; + }); + foreach ($childFolders as $folder) { + $parentIds[] = $folder->getId(); + $queue[] = $folder->getId(); + } } $query = $this->getQueryBuilder(); $query->delete('filecache') - ->whereParent($entry->getId()); - $query->execute(); - - $query = $this->getQueryBuilder(); - $query->delete('filecache_extended') - ->where($query->expr()->in('fileid', $query->createNamedParameter($childIds, IQueryBuilder::PARAM_INT_ARRAY))); + ->whereParentIn($parentIds); $query->execute(); } @@ -603,7 +633,6 @@ class Cache implements ICache { * @param string $targetPath * @throws \OC\DatabaseException * @throws \Exception if the given storages have an invalid id - * @suppress SqlInjectionChecker */ public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { if ($sourceCache instanceof Cache) { @@ -662,6 +691,17 @@ class Cache implements ICache { $query->execute(); $this->connection->commit(); + + if ($sourceCache->getNumericStorageId() !== $this->getNumericStorageId()) { + $this->eventDispatcher->dispatchTyped(new CacheEntryRemovedEvent($this->storage, $sourcePath, $sourceId, $sourceCache->getNumericStorageId())); + $event = new CacheEntryInsertedEvent($this->storage, $targetPath, $sourceId, $this->getNumericStorageId()); + $this->eventDispatcher->dispatch(CacheInsertEvent::class, $event); + $this->eventDispatcher->dispatchTyped($event); + } else { + $event = new CacheEntryUpdatedEvent($this->storage, $targetPath, $sourceId, $this->getNumericStorageId()); + $this->eventDispatcher->dispatch(CacheUpdateEvent::class, $event); + $this->eventDispatcher->dispatchTyped($event); + } } else { $this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath); } @@ -703,7 +743,11 @@ class Cache implements ICache { ->from('filecache') ->whereStorageId() ->wherePath($file); - $size = $query->execute()->fetchColumn(); + + $result = $query->execute(); + $size = $result->fetchColumn(); + $result->closeCursor(); + if ($size !== false) { if ((int)$size === -1) { return self::SHALLOW; @@ -738,9 +782,13 @@ class Cache implements ICache { ->whereStorageId() ->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern))); + $result = $query->execute(); + $files = $result->fetchAll(); + $result->closeCursor(); + return array_map(function (array $data) { return self::cacheEntryFromData($data, $this->mimetypeLoader); - }, $query->execute()->fetchAll()); + }, $files); } /** @@ -775,9 +823,13 @@ class Cache implements ICache { $query->andWhere($query->expr()->eq('mimepart', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT))); } + $result = $query->execute(); + $files = $result->fetchAll(); + $result->closeCursor(); + return array_map(function (array $data) { return self::cacheEntryFromData($data, $this->mimetypeLoader); - }, $query->execute()->fetchAll()); + }, $files); } public function searchQuery(ISearchQuery $searchQuery) { @@ -858,7 +910,11 @@ class Cache implements ICache { ->whereParent($fileId) ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))); - return (int)$query->execute()->fetchColumn(); + $result = $query->execute(); + $size = (int)$result->fetchColumn(); + $result->closeCursor(); + + return $size; } return -1; } @@ -885,7 +941,11 @@ class Cache implements ICache { ->whereStorageId() ->whereParent($id); - if ($row = $query->execute()->fetch()) { + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + if ($row) { [$sum, $min] = array_values($row); $sum = 0 + $sum; $min = 0 + $min; @@ -913,9 +973,13 @@ class Cache implements ICache { ->from('filecache') ->whereStorageId(); + $result = $query->execute(); + $files = $result->fetchAll(\PDO::FETCH_COLUMN); + $result->closeCursor(); + return array_map(function ($id) { return (int)$id; - }, $query->execute()->fetchAll(\PDO::FETCH_COLUMN)); + }, $files); } /** @@ -933,9 +997,14 @@ class Cache implements ICache { ->from('filecache') ->whereStorageId() ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))) - ->orderBy('fileid', 'DESC'); + ->orderBy('fileid', 'DESC') + ->setMaxResults(1); + + $result = $query->execute(); + $path = $result->fetchColumn(); + $result->closeCursor(); - return $query->execute()->fetchColumn(); + return $path; } /** @@ -951,8 +1020,15 @@ class Cache implements ICache { ->whereStorageId() ->whereFileId($id); - $path = $query->execute()->fetchColumn(); - return $path === false ? null : $path; + $result = $query->execute(); + $path = $result->fetchColumn(); + $result->closeCursor(); + + if ($path === false) { + return null; + } + + return (string) $path; } /** @@ -969,7 +1045,12 @@ class Cache implements ICache { $query->select('path', 'storage') ->from('filecache') ->where($query->expr()->eq('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT))); - if ($row = $query->execute()->fetch()) { + + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + if ($row) { $numericId = $row['storage']; $path = $row['path']; } else { diff --git a/lib/private/Files/Cache/CacheEntry.php b/lib/private/Files/Cache/CacheEntry.php index 744b88b5b51..a9d2a1acbf0 100644 --- a/lib/private/Files/Cache/CacheEntry.php +++ b/lib/private/Files/Cache/CacheEntry.php @@ -2,6 +2,7 @@ /** * @copyright Copyright (c) 2016, ownCloud, Inc. * + * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Robin Appelman <robin@icewind.nl> * * @license AGPL-3.0 @@ -27,7 +28,7 @@ use OCP\Files\Cache\ICacheEntry; /** * meta data for a file or folder */ -class CacheEntry implements ICacheEntry, \ArrayAccess { +class CacheEntry implements ICacheEntry { /** * @var array */ @@ -67,7 +68,7 @@ class CacheEntry implements ICacheEntry, \ArrayAccess { public function getPath() { - return $this->data['path']; + return (string)$this->data['path']; } diff --git a/lib/private/Files/Cache/CacheQueryBuilder.php b/lib/private/Files/Cache/CacheQueryBuilder.php index 332274eda2a..ac17cfaffb2 100644 --- a/lib/private/Files/Cache/CacheQueryBuilder.php +++ b/lib/private/Files/Cache/CacheQueryBuilder.php @@ -94,4 +94,17 @@ class CacheQueryBuilder extends QueryBuilder { return $this; } + + public function whereParentIn(array $parents) { + $alias = $this->alias; + if ($alias) { + $alias .= '.'; + } else { + $alias = ''; + } + + $this->andWhere($this->expr()->in("{$alias}parent", $this->createNamedParameter($parents, IQueryBuilder::PARAM_INT_ARRAY))); + + return $this; + } } diff --git a/lib/private/Files/Cache/HomeCache.php b/lib/private/Files/Cache/HomeCache.php index b86a31fe4d1..93906bfc93f 100644 --- a/lib/private/Files/Cache/HomeCache.php +++ b/lib/private/Files/Cache/HomeCache.php @@ -5,10 +5,11 @@ * @author Andreas Fischer <bantu@owncloud.com> * @author Björn Schießle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -52,12 +53,19 @@ class HomeCache extends Cache { } if ($entry && $entry['mimetype'] === 'httpd/unix-directory') { $id = $entry['fileid']; - $sql = 'SELECT SUM(`size`) AS f1 ' . - 'FROM `*PREFIX*filecache` ' . - 'WHERE `parent` = ? AND `storage` = ? AND `size` >= 0'; - $result = \OC_DB::executeAudited($sql, [$id, $this->getNumericStorageId()]); - if ($row = $result->fetchRow()) { - $result->closeCursor(); + + $query = $this->connection->getQueryBuilder(); + $query->selectAlias($query->func()->sum('size'), 'f1') + ->from('filecache') + ->where($query->expr()->eq('parent', $query->createNamedParameter($id))) + ->andWhere($query->expr()->eq('storage', $query->createNamedParameter($this->getNumericStorageId()))) + ->andWhere($query->expr()->gte('size', $query->createNamedParameter(0))); + + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + if ($row) { list($sum) = array_values($row); $totalSize = 0 + $sum; $entry['size'] += 0; @@ -65,6 +73,7 @@ class HomeCache extends Cache { $this->update($id, ['size' => $totalSize]); } } + $result->closeCursor(); } return $totalSize; } diff --git a/lib/private/Files/Cache/LocalRootScanner.php b/lib/private/Files/Cache/LocalRootScanner.php index 9ecc8c6611d..a10f51150e1 100644 --- a/lib/private/Files/Cache/LocalRootScanner.php +++ b/lib/private/Files/Cache/LocalRootScanner.php @@ -1,9 +1,12 @@ <?php declare(strict_types=1); + /** * @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl> * + * @author Robin Appelman <robin@icewind.nl> + * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify @@ -17,7 +20,7 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ diff --git a/lib/private/Files/Cache/NullWatcher.php b/lib/private/Files/Cache/NullWatcher.php new file mode 100644 index 00000000000..146961d1c51 --- /dev/null +++ b/lib/private/Files/Cache/NullWatcher.php @@ -0,0 +1,56 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl> + * + * @author Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Files\Cache; + +class NullWatcher extends Watcher { + private $policy; + + public function __construct() { + } + + public function setPolicy($policy) { + $this->policy = $policy; + } + + public function getPolicy() { + return $this->policy; + } + + public function checkUpdate($path, $cachedEntry = null) { + return false; + } + + public function update($path, $cachedData) { + } + + public function needsUpdate($path, $cachedData) { + return false; + } + + public function cleanFolder($path) { + } +} diff --git a/lib/private/Files/Cache/Propagator.php b/lib/private/Files/Cache/Propagator.php index 92fa6436548..cdbe8b60175 100644 --- a/lib/private/Files/Cache/Propagator.php +++ b/lib/private/Files/Cache/Propagator.php @@ -3,7 +3,6 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @@ -63,7 +62,6 @@ class Propagator implements IPropagator { * @param string $internalPath * @param int $time * @param int $sizeDifference number of bytes the file has grown - * @suppress SqlInjectionChecker */ public function propagateChange($internalPath, $time, $sizeDifference = 0) { // Do not propogate changes in ignored paths @@ -105,9 +103,9 @@ class Propagator implements IPropagator { $builder = $this->connection->getQueryBuilder(); $builder->update('filecache') ->set('size', $builder->func()->greatest( - $builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT), - $builder->func()->add('size', $builder->createNamedParameter($sizeDifference))) - ) + $builder->func()->add('size', $builder->createNamedParameter($sizeDifference)), + $builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT) + )) ->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT))) ->andWhere($builder->expr()->in('path_hash', $hashParams)) ->andWhere($builder->expr()->gt('size', $builder->expr()->literal(-1, IQueryBuilder::PARAM_INT))); @@ -156,7 +154,6 @@ class Propagator implements IPropagator { /** * Commit the active propagation batch - * @suppress SqlInjectionChecker */ public function commitBatch() { if (!$this->inBatch) { @@ -170,7 +167,7 @@ class Propagator implements IPropagator { $storageId = (int)$this->storage->getStorageCache()->getNumericId(); $query->update('filecache') - ->set('mtime', $query->createFunction('GREATEST(' . $query->getColumnName('mtime') . ', ' . $query->createParameter('time') . ')')) + ->set('mtime', $query->func()->greatest('mtime', $query->createParameter('time'))) ->set('etag', $query->expr()->literal(uniqid())) ->where($query->expr()->eq('storage', $query->expr()->literal($storageId, IQueryBuilder::PARAM_INT))) ->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash'))); diff --git a/lib/private/Files/Cache/Scanner.php b/lib/private/Files/Cache/Scanner.php index f895948574b..1b9637ac61e 100644 --- a/lib/private/Files/Cache/Scanner.php +++ b/lib/private/Files/Cache/Scanner.php @@ -16,7 +16,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Files/Cache/Storage.php b/lib/private/Files/Cache/Storage.php index 62228e16290..74f5df2a5b1 100644 --- a/lib/private/Files/Cache/Storage.php +++ b/lib/private/Files/Cache/Storage.php @@ -3,14 +3,13 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Joas Schilling <coding@schilljs.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -126,9 +125,14 @@ class Storage { * @return string|null either the storage id string or null if the numeric id is not known */ public static function getStorageId($numericId) { - $sql = 'SELECT `id` FROM `*PREFIX*storages` WHERE `numeric_id` = ?'; - $result = \OC_DB::executeAudited($sql, [$numericId]); - if ($row = $result->fetchRow()) { + $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + $query->select('id') + ->from('storages') + ->where($query->expr()->eq('numeric_id', $query->createNamedParameter($numericId))); + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + if ($row) { return $row['id']; } else { return null; @@ -170,9 +174,14 @@ class Storage { * @param int $delay amount of seconds to delay reconsidering that storage further */ public function setAvailability($isAvailable, int $delay = 0) { - $sql = 'UPDATE `*PREFIX*storages` SET `available` = ?, `last_checked` = ? WHERE `id` = ?'; $available = $isAvailable ? 1 : 0; - \OC_DB::executeAudited($sql, [$available, time() + $delay, $this->storageId]); + + $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + $query->update('storages') + ->set('available', $query->createNamedParameter($available)) + ->set('last_checked', $query->createNamedParameter(time() + $delay)) + ->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId))); + $query->execute(); } /** @@ -193,12 +202,17 @@ class Storage { public static function remove($storageId) { $storageId = self::adjustStorageId($storageId); $numericId = self::getNumericStorageId($storageId); - $sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?'; - \OC_DB::executeAudited($sql, [$storageId]); + + $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + $query->delete('storages') + ->where($query->expr()->eq('id', $query->createNamedParameter($storageId))); + $query->execute(); if (!is_null($numericId)) { - $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?'; - \OC_DB::executeAudited($sql, [$numericId]); + $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + $query->delete('filecache') + ->where($query->expr()->eq('storage', $query->createNamedParameter($numericId))); + $query->execute(); } } } diff --git a/lib/private/Files/Cache/StorageGlobal.php b/lib/private/Files/Cache/StorageGlobal.php index 26a7be24634..23d6035084d 100644 --- a/lib/private/Files/Cache/StorageGlobal.php +++ b/lib/private/Files/Cache/StorageGlobal.php @@ -2,6 +2,7 @@ /** * @copyright Robin Appelman <robin@icewind.nl> * + * @author Joas Schilling <coding@schilljs.com> * @author Robin Appelman <robin@icewind.nl> * * @license GNU AGPL version 3 or any later version @@ -61,6 +62,7 @@ class StorageGlobal { while ($row = $result->fetch()) { $this->cache[$row['id']] = $row; } + $result->closeCursor(); } /** @@ -74,7 +76,10 @@ class StorageGlobal { ->from('storages') ->where($builder->expr()->eq('id', $builder->createNamedParameter($storageId))); - $row = $query->execute()->fetch(); + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + if ($row) { $this->cache[$storageId] = $row; } diff --git a/lib/private/Files/Cache/Updater.php b/lib/private/Files/Cache/Updater.php index 79501d910e5..04179ba0aaf 100644 --- a/lib/private/Files/Cache/Updater.php +++ b/lib/private/Files/Cache/Updater.php @@ -8,7 +8,7 @@ * @author Michael Gapczynski <GapczynskiM@gmail.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Files/Cache/Watcher.php b/lib/private/Files/Cache/Watcher.php index d6291ca71e9..19e17e60959 100644 --- a/lib/private/Files/Cache/Watcher.php +++ b/lib/private/Files/Cache/Watcher.php @@ -7,7 +7,7 @@ * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Files/Cache/Wrapper/CacheJail.php b/lib/private/Files/Cache/Wrapper/CacheJail.php index 8d4f412868e..6c1c17be028 100644 --- a/lib/private/Files/Cache/Wrapper/CacheJail.php +++ b/lib/private/Files/Cache/Wrapper/CacheJail.php @@ -2,7 +2,6 @@ /** * @copyright Copyright (c) 2016, ownCloud, Inc. * - * @author Ari Selseng <ari@selseng.net> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Daniel Jagszent <daniel@jagszent.de> * @author Morris Jobke <hey@morrisjobke.de> @@ -254,9 +253,9 @@ class CacheJail extends CacheWrapper { * @param string|boolean $path * @param array $data (optional) meta data of the folder */ - public function correctFolderSize($path, $data = null, $isBackgroundSize = false) { + public function correctFolderSize($path, $data = null, $isBackgroundScan = false) { if ($this->getCache() instanceof Cache) { - $this->getCache()->correctFolderSize($this->getSourcePath($path), $data, $isBackgroundSize); + $this->getCache()->correctFolderSize($this->getSourcePath($path), $data, $isBackgroundScan); } } diff --git a/lib/private/Files/Cache/Wrapper/CacheWrapper.php b/lib/private/Files/Cache/Wrapper/CacheWrapper.php index 4901a530799..cac6cfed87e 100644 --- a/lib/private/Files/Cache/Wrapper/CacheWrapper.php +++ b/lib/private/Files/Cache/Wrapper/CacheWrapper.php @@ -10,7 +10,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Files/Config/MountProviderCollection.php b/lib/private/Files/Config/MountProviderCollection.php index 34db652290f..3900e9870bd 100644 --- a/lib/private/Files/Config/MountProviderCollection.php +++ b/lib/private/Files/Config/MountProviderCollection.php @@ -30,6 +30,7 @@ use OC\Hooks\EmitterTrait; use OCP\Files\Config\IHomeMountProvider; use OCP\Files\Config\IMountProvider; use OCP\Files\Config\IMountProviderCollection; +use OCP\Files\Config\IRootMountProvider; use OCP\Files\Config\IUserMountCache; use OCP\Files\Mount\IMountManager; use OCP\Files\Mount\IMountPoint; @@ -49,6 +50,9 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { */ private $providers = []; + /** @var \OCP\Files\Config\IRootMountProvider[] */ + private $rootProviders = []; + /** * @var \OCP\Files\Storage\IStorageFactory */ @@ -198,4 +202,25 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { public function getMountCache() { return $this->mountCache; } + + public function registerRootProvider(IRootMountProvider $provider) { + $this->rootProviders[] = $provider; + } + + /** + * Get all root mountpoints + * + * @return \OCP\Files\Mount\IMountPoint[] + * @since 20.0.0 + */ + public function getRootMounts(): array { + $loader = $this->loader; + $mounts = array_map(function (IRootMountProvider $provider) use ($loader) { + return $provider->getRootMounts($loader); + }, $this->rootProviders); + $mounts = array_reduce($mounts, function (array $mounts, array $providerMounts) { + return array_merge($mounts, $providerMounts); + }, []); + return $mounts; + } } diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php index 9cf3b43a431..46ea1394252 100644 --- a/lib/private/Files/Config/UserMountCache.php +++ b/lib/private/Files/Config/UserMountCache.php @@ -4,11 +4,12 @@ * * @author Dariusz Olszewski <starypatyk@users.noreply.github.com> * @author Joas Schilling <coding@schilljs.com> + * @author Julius Härtl <jus@bitgrid.net> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -234,7 +235,9 @@ class UserMountCache implements IUserMountCache { ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid')) ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID()))); - $rows = $query->execute()->fetchAll(); + $result = $query->execute(); + $rows = $result->fetchAll(); + $result->closeCursor(); $this->mountsForUsers[$user->getUID()] = array_filter(array_map([$this, 'dbRowToMountInfo'], $rows)); } @@ -257,7 +260,9 @@ class UserMountCache implements IUserMountCache { $query->andWhere($builder->expr()->eq('user_id', $builder->createPositionalParameter($user))); } - $rows = $query->execute()->fetchAll(); + $result = $query->execute(); + $rows = $result->fetchAll(); + $result->closeCursor(); return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows)); } @@ -273,7 +278,9 @@ class UserMountCache implements IUserMountCache { ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid')) ->where($builder->expr()->eq('root_id', $builder->createPositionalParameter($rootFileId, IQueryBuilder::PARAM_INT))); - $rows = $query->execute()->fetchAll(); + $result = $query->execute(); + $rows = $result->fetchAll(); + $result->closeCursor(); return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows)); } @@ -290,7 +297,10 @@ class UserMountCache implements IUserMountCache { ->from('filecache') ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); - $row = $query->execute()->fetch(); + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + if (is_array($row)) { $this->cacheInfoCache[$fileId] = [ (int)$row['storage'], @@ -374,7 +384,6 @@ class UserMountCache implements IUserMountCache { /** * @param array $users * @return array - * @suppress SqlInjectionChecker */ public function getUsedSpaceForUsers(array $users) { $builder = $this->connection->getQueryBuilder(); @@ -409,4 +418,9 @@ class UserMountCache implements IUserMountCache { $result->closeCursor(); return $results; } + + public function clear(): void { + $this->cacheInfoCache = new CappedMemoryCache(); + $this->mountsForUsers = new CappedMemoryCache(); + } } diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php index 3c6bdd3b5a0..35e69ae270f 100644 --- a/lib/private/Files/FileInfo.php +++ b/lib/private/Files/FileInfo.php @@ -13,7 +13,7 @@ * @author Roeland Jago Douma <roeland@famdouma.nl> * @author tbartenstein <tbartenstein@users.noreply.github.com> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -95,7 +95,7 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { * @param \OCP\Files\Mount\IMountPoint $mount * @param \OCP\IUser|null $owner */ - public function __construct($path, $storage, $internalPath, $data, $mount, $owner= null) { + public function __construct($path, $storage, $internalPath, $data, $mount, $owner = null) { $this->path = $path; $this->storage = $storage; $this->internalPath = $internalPath; diff --git a/lib/private/Files/Filesystem.php b/lib/private/Files/Filesystem.php index 9d534815cdc..bf94be273f2 100644 --- a/lib/private/Files/Filesystem.php +++ b/lib/private/Files/Filesystem.php @@ -9,6 +9,7 @@ * @author Florin Peter <github@florin-peter.de> * @author Joas Schilling <coding@schilljs.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author korelstar <korelstar@users.noreply.github.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Michael Gapczynski <GapczynskiM@gmail.com> * @author Morris Jobke <hey@morrisjobke.de> @@ -17,7 +18,7 @@ * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Sam Tuke <mail@samtuke.com> * @author Stephan Peijnik <speijnik@anexia-it.com> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -437,13 +438,13 @@ class Filesystem { // home mounts are handled seperate since we need to ensure this is mounted before we call the other mount providers $homeMount = $mountConfigManager->getHomeMountForUser($userObject); + self::getMountManager()->addMount($homeMount); + if ($homeMount->getStorageRootId() === -1) { $homeMount->getStorage()->mkdir(''); $homeMount->getStorage()->getScanner()->scan(''); } - self::getMountManager()->addMount($homeMount); - \OC\Files\Filesystem::getStorage($user); // Chance to mount for other storages @@ -813,7 +814,7 @@ class Filesystem { $cacheKey = json_encode([$path, $stripTrailingSlash, $isAbsolutePath, $keepUnicode]); - if (isset(self::$normalizedPathCache[$cacheKey])) { + if ($cacheKey && isset(self::$normalizedPathCache[$cacheKey])) { return self::$normalizedPathCache[$cacheKey]; } diff --git a/lib/private/Files/Mount/MountPoint.php b/lib/private/Files/Mount/MountPoint.php index f9cda6fbce8..cbf3785c409 100644 --- a/lib/private/Files/Mount/MountPoint.php +++ b/lib/private/Files/Mount/MountPoint.php @@ -11,7 +11,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -102,6 +102,7 @@ class MountPoint implements IMountPoint { $mountpoint = $this->formatPath($mountpoint); $this->mountPoint = $mountpoint; + $this->mountId = $mountId; if ($storage instanceof Storage) { $this->class = get_class($storage); $this->storage = $this->loader->wrap($this, $storage); @@ -113,7 +114,6 @@ class MountPoint implements IMountPoint { $this->class = $storage; $this->arguments = $arguments; } - $this->mountId = $mountId; } /** diff --git a/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php b/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php new file mode 100644 index 00000000000..4185c9b86a7 --- /dev/null +++ b/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php @@ -0,0 +1,151 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020, Morris Jobke <hey@morrisjobke.de> + * + * @author Morris Jobke <hey@morrisjobke.de> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Files\Mount; + +use OC\Files\ObjectStore\AppdataPreviewObjectStoreStorage; +use OC\Files\ObjectStore\ObjectStoreStorage; +use OC\Files\Storage\Wrapper\Jail; +use OCP\Files\Config\IRootMountProvider; +use OCP\Files\Storage\IStorageFactory; +use OCP\IConfig; +use OCP\ILogger; + +/** + * Mount provider for object store app data folder for previews + */ +class ObjectStorePreviewCacheMountProvider implements IRootMountProvider { + /** @var ILogger */ + private $logger; + /** @var IConfig */ + private $config; + + public function __construct(ILogger $logger, IConfig $config) { + $this->logger = $logger; + $this->config = $config; + } + + /** + * @return MountPoint[] + * @throws \Exception + */ + public function getRootMounts(IStorageFactory $loader): array { + if (!is_array($this->config->getSystemValue('objectstore_multibucket'))) { + return []; + } + if ($this->config->getSystemValue('objectstore.multibucket.preview-distribution', false) !== true) { + return []; + } + + $instanceId = $this->config->getSystemValueString('instanceid', ''); + $mountPoints = []; + $directoryRange = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + $i = 0; + foreach ($directoryRange as $parent) { + foreach ($directoryRange as $child) { + $mountPoints[] = new MountPoint( + AppdataPreviewObjectStoreStorage::class, + '/appdata_' . $instanceId . '/preview/' . $parent . '/' . $child, + $this->getMultiBucketObjectStore($i), + $loader + ); + $i++; + } + } + + $rootStorageArguments = $this->getMultiBucketObjectStoreForRoot(); + $fakeRootStorage = new ObjectStoreStorage($rootStorageArguments); + $fakeRootStorageJail = new Jail([ + 'storage' => $fakeRootStorage, + 'root' => '/appdata_' . $instanceId . '/preview', + ]); + + // add a fallback location to be able to fetch existing previews from the old bucket + $mountPoints[] = new MountPoint( + $fakeRootStorageJail, + '/appdata_' . $instanceId . '/preview/old-multibucket', + null, + $loader + ); + + return $mountPoints; + } + + protected function getMultiBucketObjectStore(int $number): array { + $config = $this->config->getSystemValue('objectstore_multibucket'); + + // sanity checks + if (empty($config['class'])) { + $this->logger->error('No class given for objectstore', ['app' => 'files']); + } + if (!isset($config['arguments'])) { + $config['arguments'] = []; + } + + /* + * Use any provided bucket argument as prefix + * and add the mapping from parent/child => bucket + */ + if (!isset($config['arguments']['bucket'])) { + $config['arguments']['bucket'] = ''; + } + + $config['arguments']['bucket'] .= "-preview-$number"; + + // instantiate object store implementation + $config['arguments']['objectstore'] = new $config['class']($config['arguments']); + + $config['arguments']['internal-id'] = $number; + + return $config['arguments']; + } + + protected function getMultiBucketObjectStoreForRoot(): array { + $config = $this->config->getSystemValue('objectstore_multibucket'); + + // sanity checks + if (empty($config['class'])) { + $this->logger->error('No class given for objectstore', ['app' => 'files']); + } + if (!isset($config['arguments'])) { + $config['arguments'] = []; + } + + /* + * Use any provided bucket argument as prefix + * and add the mapping from parent/child => bucket + */ + if (!isset($config['arguments']['bucket'])) { + $config['arguments']['bucket'] = ''; + } + $config['arguments']['bucket'] .= '0'; + + // instantiate object store implementation + $config['arguments']['objectstore'] = new $config['class']($config['arguments']); + + return $config['arguments']; + } +} diff --git a/lib/private/Files/Node/File.php b/lib/private/Files/Node/File.php index 915e336b996..c140c3e977b 100644 --- a/lib/private/Files/Node/File.php +++ b/lib/private/Files/Node/File.php @@ -9,7 +9,7 @@ * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index d9639e9f1ab..40499949110 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -4,13 +4,14 @@ * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Georg Ehrke <oc.list@georgehrke.com> * @author Joas Schilling <coding@schilljs.com> * @author Julius Härtl <jus@bitgrid.net> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -31,6 +32,7 @@ namespace OC\Files\Node; use OC\DB\QueryBuilder\Literal; +use OC\Files\Storage\Wrapper\Jail; use OCA\Files_Sharing\SharedStorage; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Config\ICachedMountInfo; @@ -188,7 +190,7 @@ class Folder extends Node implements \OCP\Files\Folder { } else { $result = $this->view->touch($fullPath); } - if (!$result) { + if ($result === false) { throw new NotPermittedException('Could not create path'); } $node = new File($this->root, $this->view, $fullPath); @@ -438,13 +440,33 @@ class Folder extends Node implements \OCP\Files\Folder { $mountMap = array_combine($storageIds, $mounts); $folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER); + /* + * Construct an array of the storage id with their prefix path + * This helps us to filter in the final query + */ + $filters = array_map(function (IMountPoint $mount) { + $storage = $mount->getStorage(); + + $storageId = $storage->getCache()->getNumericStorageId(); + $prefix = ''; + + if ($storage->instanceOfStorage(Jail::class)) { + $prefix = $storage->getUnJailedPath(''); + } + + return [ + 'storageId' => $storageId, + 'pathPrefix' => $prefix, + ]; + }, $mounts); + // Search in batches of 500 entries $searchLimit = 500; $results = []; $searchResultCount = 0; $count = 0; do { - $searchResult = $this->recentSearch($searchLimit, $offset, $storageIds, $folderMimetype); + $searchResult = $this->recentSearch($searchLimit, $offset, $folderMimetype, $filters); // Exit condition if there are no more results if (count($searchResult) === 0) { @@ -466,13 +488,36 @@ class Folder extends Node implements \OCP\Files\Folder { return array_slice($results, 0, $limit); } - private function recentSearch($limit, $offset, $storageIds, $folderMimetype) { - $builder = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + private function recentSearch($limit, $offset, $folderMimetype, $filters) { + $dbconn = \OC::$server->getDatabaseConnection(); + $builder = $dbconn->getQueryBuilder(); $query = $builder ->select('f.*') - ->from('filecache', 'f') - ->andWhere($builder->expr()->in('f.storage', $builder->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY))) - ->andWhere($builder->expr()->orX( + ->from('filecache', 'f'); + + /* + * Here is where we construct the filtering. + * Note that this is expensive filtering as it is a lot of like queries. + * However the alternative is we do this filtering and parsing later in php with the risk of looping endlessly + */ + $storageFilters = $builder->expr()->orX(); + foreach ($filters as $filter) { + $storageFilter = $builder->expr()->andX( + $builder->expr()->eq('f.storage', $builder->createNamedParameter($filter['storageId'])) + ); + + if ($filter['pathPrefix'] !== '') { + $storageFilter->add( + $builder->expr()->like('f.path', $builder->createNamedParameter($dbconn->escapeLikeParameter($filter['pathPrefix']) . '/%')) + ); + } + + $storageFilters->add($storageFilter); + } + + $query->andWhere($storageFilters); + + $query->andWhere($builder->expr()->orX( // handle non empty folders separate $builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)), $builder->expr()->eq('f.size', new Literal(0)) @@ -482,7 +527,12 @@ class Folder extends Node implements \OCP\Files\Folder { ->orderBy('f.mtime', 'DESC') ->setMaxResults($limit) ->setFirstResult($offset); - return $query->execute()->fetchAll(); + + $result = $query->execute(); + $rows = $result->fetchAll(); + $result->closeCursor(); + + return $rows; } private function recentParse($result, $mountMap, $mimetypeLoader) { diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php index 50cd0f90110..52dfb3c20a4 100644 --- a/lib/private/Files/Node/LazyFolder.php +++ b/lib/private/Files/Node/LazyFolder.php @@ -1,9 +1,12 @@ <?php declare(strict_types=1); + /** * @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl> * + * @author Robin Appelman <robin@icewind.nl> + * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify @@ -17,7 +20,7 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ diff --git a/lib/private/Files/Node/LazyRoot.php b/lib/private/Files/Node/LazyRoot.php index e17640373fe..899fd130655 100644 --- a/lib/private/Files/Node/LazyRoot.php +++ b/lib/private/Files/Node/LazyRoot.php @@ -2,7 +2,6 @@ /** * @copyright Copyright (c) 2016, ownCloud, Inc. * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php index 70821e685f8..92635a55baa 100644 --- a/lib/private/Files/Node/Node.php +++ b/lib/private/Files/Node/Node.php @@ -10,7 +10,7 @@ * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Files/Node/NonExistingFolder.php b/lib/private/Files/Node/NonExistingFolder.php index 65af837da43..eaf7963eb9b 100644 --- a/lib/private/Files/Node/NonExistingFolder.php +++ b/lib/private/Files/Node/NonExistingFolder.php @@ -4,7 +4,7 @@ * * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php index f24082851e0..fba8a24b40f 100644 --- a/lib/private/Files/Node/Root.php +++ b/lib/private/Files/Node/Root.php @@ -12,7 +12,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Stefan Weil <sw@weilnetz.de> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php b/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php new file mode 100644 index 00000000000..bdc41f9ed95 --- /dev/null +++ b/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php @@ -0,0 +1,45 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020, Morris Jobke <hey@morrisjobke.de> + * + * @author Morris Jobke <hey@morrisjobke.de> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Files\ObjectStore; + +class AppdataPreviewObjectStoreStorage extends ObjectStoreStorage { + + /** @var string */ + private $internalId; + + public function __construct($params) { + if (!isset($params['internal-id'])) { + throw new \Exception('missing id in parameters'); + } + $this->internalId = (string)$params['internal-id']; + parent::__construct($params); + } + + public function getId() { + return 'object::appdata::preview:' . $this->internalId; + } +} diff --git a/lib/private/Files/ObjectStore/Azure.php b/lib/private/Files/ObjectStore/Azure.php index 0b65a6b80e5..2ef13d60c56 100644 --- a/lib/private/Files/ObjectStore/Azure.php +++ b/lib/private/Files/ObjectStore/Azure.php @@ -130,4 +130,8 @@ class Azure implements IObjectStore { } } } + + public function copyObject($from, $to) { + $this->getBlobClient()->copyBlob($this->containerName, $to, $this->containerName, $from); + } } diff --git a/lib/private/Files/ObjectStore/NoopScanner.php b/lib/private/Files/ObjectStore/NoopScanner.php index 25b52416efd..9195e7f8d9f 100644 --- a/lib/private/Files/ObjectStore/NoopScanner.php +++ b/lib/private/Files/ObjectStore/NoopScanner.php @@ -69,7 +69,7 @@ class NoopScanner extends Scanner { * @param array $folderData existing cache data for the folder to be scanned * @return int the size of the scanned folder or -1 if the size is unknown at this stage */ - protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderData = null, $lock = true) { + protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderId = null, $lock = true) { return 0; } diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php index a5112bcbba6..9815fe6a245 100644 --- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php +++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php @@ -33,10 +33,15 @@ use Icewind\Streams\CallbackWrapper; use Icewind\Streams\CountWrapper; use Icewind\Streams\IteratorDirectory; use OC\Files\Cache\CacheEntry; +use OC\Files\Storage\PolyFill\CopyDirectory; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\FileInfo; use OCP\Files\NotFoundException; use OCP\Files\ObjectStore\IObjectStore; class ObjectStoreStorage extends \OC\Files\Storage\Common { + use CopyDirectory; + /** * @var \OCP\Files\ObjectStore\IObjectStore $objectStore */ @@ -227,6 +232,16 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common { } } + public function getPermissions($path) { + $stat = $this->stat($path); + + if (is_array($stat) && isset($stat['permissions'])) { + return $stat['permissions']; + } + + return parent::getPermissions($path); + } + /** * Override this method if you need a different unique resource identifier for your object storage implementation. * The default implementations just appends the fileId to 'urn:oid:'. Make sure the URN is unique over all users. @@ -286,6 +301,11 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common { case 'rb': $stat = $this->stat($path); if (is_array($stat)) { + // Reading 0 sized files is a waste of time + if (isset($stat['size']) && $stat['size'] === 0) { + return fopen('php://memory', $mode); + } + try { return $this->objectStore->readObject($this->getURN($stat['fileid'])); } catch (NotFoundException $e) { @@ -304,7 +324,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common { } else { return false; } - // no break + // no break case 'w': case 'wb': case 'w+': @@ -419,9 +439,9 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common { public function file_put_contents($path, $data) { $handle = $this->fopen($path, 'w+'); - fwrite($handle, $data); + $result = fwrite($handle, $data); fclose($handle); - return true; + return $result; } public function writeStream(string $path, $stream, int $size = null): int { @@ -446,14 +466,20 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common { $exists = $this->getCache()->inCache($path); $uploadPath = $exists ? $path : $path . '.part'; - $fileId = $this->getCache()->put($uploadPath, $stat); + + if ($exists) { + $fileId = $stat['fileid']; + } else { + $fileId = $this->getCache()->put($uploadPath, $stat); + } + $urn = $this->getURN($fileId); try { //upload to object storage if ($size === null) { $countStream = CountWrapper::wrap($stream, function ($writtenSize) use ($fileId, &$size) { $this->getCache()->update($fileId, [ - 'size' => $writtenSize + 'size' => $writtenSize, ]); $size = $writtenSize; }); @@ -461,19 +487,33 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common { if (is_resource($countStream)) { fclose($countStream); } + $stat['size'] = $size; } else { $this->objectStore->writeObject($urn, $stream); } } catch (\Exception $ex) { - $this->getCache()->remove($uploadPath); - $this->logger->logException($ex, [ - 'app' => 'objectstore', - 'message' => 'Could not create object ' . $urn . ' for ' . $path, - ]); + if (!$exists) { + /* + * Only remove the entry if we are dealing with a new file. + * Else people lose access to existing files + */ + $this->getCache()->remove($uploadPath); + $this->logger->logException($ex, [ + 'app' => 'objectstore', + 'message' => 'Could not create object ' . $urn . ' for ' . $path, + ]); + } else { + $this->logger->logException($ex, [ + 'app' => 'objectstore', + 'message' => 'Could not update object ' . $urn . ' for ' . $path, + ]); + } throw $ex; // make this bubble up } - if (!$exists) { + if ($exists) { + $this->getCache()->update($fileId, $stat); + } else { if ($this->objectStore->objectExists($urn)) { $this->getCache()->move($uploadPath, $path); } else { @@ -488,4 +528,59 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common { public function getObjectStore(): IObjectStore { return $this->objectStore; } + + public function copy($path1, $path2) { + $path1 = $this->normalizePath($path1); + $path2 = $this->normalizePath($path2); + + $cache = $this->getCache(); + $sourceEntry = $cache->get($path1); + if (!$sourceEntry) { + throw new NotFoundException('Source object not found'); + } + + $this->copyInner($sourceEntry, $path2); + + return true; + } + + private function copyInner(ICacheEntry $sourceEntry, string $to) { + $cache = $this->getCache(); + + if ($sourceEntry->getMimeType() === FileInfo::MIMETYPE_FOLDER) { + if ($cache->inCache($to)) { + $cache->remove($to); + } + $this->mkdir($to); + + foreach ($cache->getFolderContentsById($sourceEntry->getId()) as $child) { + $this->copyInner($child, $to . '/' . $child->getName()); + } + } else { + $this->copyFile($sourceEntry, $to); + } + } + + private function copyFile(ICacheEntry $sourceEntry, string $to) { + $cache = $this->getCache(); + + $sourceUrn = $this->getURN($sourceEntry->getId()); + + $cache->copyFromCache($cache, $sourceEntry, $to); + $targetEntry = $cache->get($to); + + if (!$targetEntry) { + throw new \Exception('Target not in cache after copy'); + } + + $targetUrn = $this->getURN($targetEntry->getId()); + + try { + $this->objectStore->copyObject($sourceUrn, $targetUrn); + } catch (\Exception $e) { + $cache->remove($to); + + throw $e; + } + } } diff --git a/lib/private/Files/ObjectStore/S3ConnectionTrait.php b/lib/private/Files/ObjectStore/S3ConnectionTrait.php index b96a11833cd..6b469860de5 100644 --- a/lib/private/Files/ObjectStore/S3ConnectionTrait.php +++ b/lib/private/Files/ObjectStore/S3ConnectionTrait.php @@ -4,9 +4,12 @@ * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Florent <florent@coppint.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> + * @author Roeland Jago Douma <roeland@famdouma.nl> * @author S. Cat <33800996+sparrowjack63@users.noreply.github.com> + * @author Stephen Cuppett <steve@cuppett.com> * * @license GNU AGPL version 3 or any later version * @@ -28,8 +31,13 @@ namespace OC\Files\ObjectStore; use Aws\ClientResolver; +use Aws\Credentials\CredentialProvider; +use Aws\Credentials\Credentials; +use Aws\Exception\CredentialsException; use Aws\S3\Exception\S3Exception; use Aws\S3\S3Client; +use GuzzleHttp\Promise; +use GuzzleHttp\Promise\RejectedPromise; use OCP\ILogger; trait S3ConnectionTrait { @@ -54,8 +62,8 @@ trait S3ConnectionTrait { protected $test; protected function parseParams($params) { - if (empty($params['key']) || empty($params['secret']) || empty($params['bucket'])) { - throw new \Exception("Access Key, Secret and Bucket have to be configured."); + if (empty($params['bucket'])) { + throw new \Exception("Bucket has to be configured."); } $this->id = 'amazon::' . $params['bucket']; @@ -69,6 +77,7 @@ trait S3ConnectionTrait { if (!isset($params['port']) || $params['port'] === '') { $params['port'] = (isset($params['use_ssl']) && $params['use_ssl'] === false) ? 80 : 443; } + $params['verify_bucket_exists'] = empty($params['verify_bucket_exists']) ? true : $params['verify_bucket_exists']; $this->params = $params; } @@ -90,12 +99,19 @@ trait S3ConnectionTrait { $scheme = (isset($this->params['use_ssl']) && $this->params['use_ssl'] === false) ? 'http' : 'https'; $base_url = $scheme . '://' . $this->params['hostname'] . ':' . $this->params['port'] . '/'; + // Adding explicit credential provider to the beginning chain. + // Including environment variables and IAM instance profiles. + $provider = CredentialProvider::memoize( + CredentialProvider::chain( + $this->paramCredentialProvider(), + CredentialProvider::env(), + CredentialProvider::instanceProfile() + ) + ); + $options = [ 'version' => isset($this->params['version']) ? $this->params['version'] : 'latest', - 'credentials' => [ - 'key' => $this->params['key'], - 'secret' => $this->params['secret'], - ], + 'credentials' => $provider, 'endpoint' => $base_url, 'region' => $this->params['region'], 'use_path_style_endpoint' => isset($this->params['use_path_style']) ? $this->params['use_path_style'] : false, @@ -116,7 +132,7 @@ trait S3ConnectionTrait { ['app' => 'objectstore']); } - if (!$this->connection->doesBucketExist($this->bucket)) { + if ($this->params['verify_bucket_exists'] && !$this->connection->doesBucketExist($this->bucket)) { $logger = \OC::$server->getLogger(); try { $logger->info('Bucket "' . $this->bucket . '" does not exist - creating it.', ['app' => 'objectstore']); @@ -161,4 +177,23 @@ trait S3ConnectionTrait { return null; } } + + /** + * This function creates a credential provider based on user parameter file + */ + protected function paramCredentialProvider() : callable { + return function () { + $key = empty($this->params['key']) ? null : $this->params['key']; + $secret = empty($this->params['secret']) ? null : $this->params['secret']; + + if ($key && $secret) { + return Promise\promise_for( + new Credentials($key, $secret) + ); + } + + $msg = 'Could not find parameters set for credentials in config file.'; + return new RejectedPromise(new CredentialsException($msg)); + }; + } } diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php index d7c878178d2..80b8a6f132d 100644 --- a/lib/private/Files/ObjectStore/S3ObjectTrait.php +++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php @@ -3,6 +3,7 @@ * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Florent <florent@coppint.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> @@ -123,4 +124,8 @@ trait S3ObjectTrait { public function objectExists($urn) { return $this->getConnection()->doesObjectExist($this->bucket, $urn); } + + public function copyObject($from, $to) { + $this->getConnection()->copy($this->getBucket(), $from, $this->getBucket(), $to); + } } diff --git a/lib/private/Files/ObjectStore/S3Signature.php b/lib/private/Files/ObjectStore/S3Signature.php index bcd1eef7b2a..ab8854849fa 100644 --- a/lib/private/Files/ObjectStore/S3Signature.php +++ b/lib/private/Files/ObjectStore/S3Signature.php @@ -3,6 +3,7 @@ * * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Robin Appelman <robin@icewind.nl> * * @license GNU AGPL version 3 or any later version @@ -128,7 +129,7 @@ class S3Signature implements SignatureInterface { ) { $modify = [ 'remove_headers' => ['X-Amz-Date'], - 'set_headers' => ['Date' => gmdate(\DateTime::RFC2822)] + 'set_headers' => ['Date' => gmdate(\DateTime::RFC2822)] ]; // Add the security token header if one is being used by the credentials diff --git a/lib/private/Files/ObjectStore/StorageObjectStore.php b/lib/private/Files/ObjectStore/StorageObjectStore.php index a7551385b34..acf46758956 100644 --- a/lib/private/Files/ObjectStore/StorageObjectStore.php +++ b/lib/private/Files/ObjectStore/StorageObjectStore.php @@ -93,4 +93,8 @@ class StorageObjectStore implements IObjectStore { public function objectExists($urn) { return $this->storage->file_exists($urn); } + + public function copyObject($from, $to) { + $this->storage->copy($from, $to); + } } diff --git a/lib/private/Files/ObjectStore/Swift.php b/lib/private/Files/ObjectStore/Swift.php index 87347c3f71b..1b0888b0700 100644 --- a/lib/private/Files/ObjectStore/Swift.php +++ b/lib/private/Files/ObjectStore/Swift.php @@ -2,6 +2,7 @@ /** * @copyright Copyright (c) 2016, ownCloud, Inc. * + * @author Adrian Brzezinski <adrian.brzezinski@eo.pl> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> @@ -86,13 +87,13 @@ class Swift implements IObjectStore { if (filesize($tmpFile) < SWIFT_SEGMENT_SIZE) { $this->getContainer()->createObject([ 'name' => $urn, - 'stream' => stream_for($handle) + 'stream' => stream_for($handle), ]); } else { $this->getContainer()->createLargeObject([ 'name' => $urn, 'stream' => stream_for($handle), - 'segmentSize' => SWIFT_SEGMENT_SIZE + 'segmentSize' => SWIFT_SEGMENT_SIZE, ]); } } @@ -113,7 +114,7 @@ class Swift implements IObjectStore { 'stream' => true, 'headers' => [ 'X-Auth-Token' => $tokenId, - 'Cache-Control' => 'no-cache' + 'Cache-Control' => 'no-cache', ], ] ); @@ -148,4 +149,10 @@ class Swift implements IObjectStore { public function objectExists($urn) { return $this->getContainer()->objectExists($urn); } + + public function copyObject($from, $to) { + $this->getContainer()->getObject($from)->copy([ + 'destination' => $this->getContainer()->name . '/' . $to + ]); + } } diff --git a/lib/private/Files/ObjectStore/SwiftFactory.php b/lib/private/Files/ObjectStore/SwiftFactory.php index 0354fba638f..54975e8d021 100644 --- a/lib/private/Files/ObjectStore/SwiftFactory.php +++ b/lib/private/Files/ObjectStore/SwiftFactory.php @@ -5,6 +5,7 @@ declare(strict_types=1); /** * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> * + * @author Adrian Brzezinski <adrian.brzezinski@eo.pl> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Julien Lutran <julien.lutran@corp.ovh.com> * @author Morris Jobke <hey@morrisjobke.de> diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php index 958d09832c4..6a530877f43 100644 --- a/lib/private/Files/Storage/Common.php +++ b/lib/private/Files/Storage/Common.php @@ -4,7 +4,6 @@ * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Bart Visscher <bartv@thisnet.nl> - * @author Björn Schießle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Greta Doci <gretadoci@gmail.com> * @author hkjolhede <hkjolhede@gmail.com> @@ -22,7 +21,7 @@ * @author scambra <sergio@entrecables.com> * @author Stefan Weil <sw@weilnetz.de> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * @author Vinicius Cubas Brand <vinicius@eita.org.br> * * @license AGPL-3.0 diff --git a/lib/private/Files/Storage/CommonTest.php b/lib/private/Files/Storage/CommonTest.php index b59090893d4..43a87f8d704 100644 --- a/lib/private/Files/Storage/CommonTest.php +++ b/lib/private/Files/Storage/CommonTest.php @@ -41,7 +41,7 @@ class CommonTest extends \OC\Files\Storage\Common { private $storage; public function __construct($params) { - $this->storage=new \OC\Files\Storage\Local($params); + $this->storage = new \OC\Files\Storage\Local($params); } public function getId() { @@ -80,7 +80,7 @@ class CommonTest extends \OC\Files\Storage\Common { public function free_space($path) { return $this->storage->free_space($path); } - public function touch($path, $mtime=null) { + public function touch($path, $mtime = null) { return $this->storage->touch($path, $mtime); } } diff --git a/lib/private/Files/Storage/DAV.php b/lib/private/Files/Storage/DAV.php index 269cd49b4e1..8f5001c8143 100644 --- a/lib/private/Files/Storage/DAV.php +++ b/lib/private/Files/Storage/DAV.php @@ -7,6 +7,7 @@ * @author Björn Schießle <bjoern@schiessle.org> * @author Carlos Cerrillo <ccerrillo@gmail.com> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Joas Schilling <coding@schilljs.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Lukas Reschke <lukas@statuscode.ch> @@ -16,7 +17,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -121,9 +122,6 @@ class DAV extends Common { if ($this->secure === true) { // inject mock for testing $this->certManager = \OC::$server->getCertificateManager(); - if (is_null($this->certManager)) { //no user - $this->certManager = \OC::$server->getCertificateManager(null); - } } $this->root = $params['root'] ?? '/'; $this->root = '/' . ltrim($this->root, '/'); @@ -488,8 +486,8 @@ class DAV extends Common { /** * @param string $path - * @param string $data - * @return int + * @param mixed $data + * @return int|false */ public function file_put_contents($path, $data) { $path = $this->cleanPath($path); diff --git a/lib/private/Files/Storage/FailedStorage.php b/lib/private/Files/Storage/FailedStorage.php index 50e2f255430..bb97fe73875 100644 --- a/lib/private/Files/Storage/FailedStorage.php +++ b/lib/private/Files/Storage/FailedStorage.php @@ -6,7 +6,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Files/Storage/Flysystem.php b/lib/private/Files/Storage/Flysystem.php index a7747823acc..9b26516bef3 100644 --- a/lib/private/Files/Storage/Flysystem.php +++ b/lib/private/Files/Storage/Flysystem.php @@ -73,7 +73,11 @@ abstract class Flysystem extends Common { * {@inheritdoc} */ public function file_put_contents($path, $data) { - return $this->flysystem->put($this->buildPath($path), $data); + $result = $this->flysystem->put($this->buildPath($path), $data); + if ($result === true) { + return strlen($data); + } + return $result; } /** diff --git a/lib/private/Files/Storage/Home.php b/lib/private/Files/Storage/Home.php index c725076893f..5c35c93bfc8 100644 --- a/lib/private/Files/Storage/Home.php +++ b/lib/private/Files/Storage/Home.php @@ -6,7 +6,7 @@ * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php index 0b636d06bde..5d0ce596b11 100644 --- a/lib/private/Files/Storage/Local.php +++ b/lib/private/Files/Storage/Local.php @@ -2,11 +2,13 @@ /** * @copyright Copyright (c) 2016, ownCloud, Inc. * + * @author aler9 <46489434+aler9@users.noreply.github.com> * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Bart Visscher <bartv@thisnet.nl> * @author Boris Rybalkin <ribalkin@gmail.com> * @author Brice Maron <brice@bmaron.net> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author J0WI <J0WI@users.noreply.github.com> * @author Jakob Sack <mail@jakobsack.de> * @author Joas Schilling <coding@schilljs.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> @@ -20,7 +22,7 @@ * @author Stefan Weil <sw@weilnetz.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Tigran Mkrtchyan <tigran.mkrtchyan@desy.de> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -109,6 +111,7 @@ class Local extends \OC\Files\Storage\Common { * @var \SplFileInfo $file */ $file = $it->current(); + clearstatcache(true, $this->getSourcePath($file)); if (in_array($file->getBasename(), ['.', '..'])) { $it->next(); continue; @@ -119,6 +122,7 @@ class Local extends \OC\Files\Storage\Common { } $it->next(); } + clearstatcache(true, $this->getSourcePath($path)); return rmdir($this->getSourcePath($path)); } catch (\UnexpectedValueException $e) { return false; @@ -141,10 +145,10 @@ class Local extends \OC\Files\Storage\Common { } public function stat($path) { - clearstatcache(); $fullPath = $this->getSourcePath($path); - $statResult = stat($fullPath); - if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) { + clearstatcache(true, $fullPath); + $statResult = @stat($fullPath); + if (PHP_INT_SIZE === 4 && $statResult && !$this->is_dir($path)) { $filesize = $this->filesize($path); $statResult['size'] = $filesize; $statResult[7] = $filesize; @@ -156,9 +160,7 @@ class Local extends \OC\Files\Storage\Common { * @inheritdoc */ public function getMetaData($path) { - $fullPath = $this->getSourcePath($path); - clearstatcache(); - $stat = @stat($fullPath); + $stat = $this->stat($path); if (!$stat) { return null; } @@ -177,6 +179,7 @@ class Local extends \OC\Files\Storage\Common { } if (!($path === '' || $path === '/')) { // deletable depends on the parents unix permissions + $fullPath = $this->getSourcePath($path); $parent = dirname($fullPath); if (is_writable($parent)) { $permissions += Constants::PERMISSION_DELETE; @@ -554,7 +557,7 @@ class Local extends \OC\Files\Storage\Common { } public function writeStream(string $path, $stream, int $size = null): int { - $result = file_put_contents($this->getSourcePath($path), $stream); + $result = $this->file_put_contents($path, $stream); if ($result === false) { throw new GenericFileException("Failed write steam to $path"); } else { diff --git a/lib/private/Files/Storage/LocalRootStorage.php b/lib/private/Files/Storage/LocalRootStorage.php index 0ad9c18b640..6f954212484 100644 --- a/lib/private/Files/Storage/LocalRootStorage.php +++ b/lib/private/Files/Storage/LocalRootStorage.php @@ -1,9 +1,12 @@ <?php declare(strict_types=1); + /** * @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl> * + * @author Robin Appelman <robin@icewind.nl> + * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify @@ -17,7 +20,7 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ diff --git a/lib/private/Files/Storage/StorageFactory.php b/lib/private/Files/Storage/StorageFactory.php index 52ddbfb2ed5..2e7dd732edd 100644 --- a/lib/private/Files/Storage/StorageFactory.php +++ b/lib/private/Files/Storage/StorageFactory.php @@ -5,7 +5,7 @@ * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Files/Storage/Temporary.php b/lib/private/Files/Storage/Temporary.php index 5e4024286c5..686600e5d21 100644 --- a/lib/private/Files/Storage/Temporary.php +++ b/lib/private/Files/Storage/Temporary.php @@ -6,7 +6,7 @@ * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Files/Storage/Wrapper/Encoding.php b/lib/private/Files/Storage/Wrapper/Encoding.php index a2ef1780d6c..b837f77fbbc 100644 --- a/lib/private/Files/Storage/Wrapper/Encoding.php +++ b/lib/private/Files/Storage/Wrapper/Encoding.php @@ -5,7 +5,7 @@ * @author Lukas Reschke <lukas@statuscode.ch> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -309,8 +309,8 @@ class Encoding extends Wrapper { * see http://php.net/manual/en/function.file_put_contents.php * * @param string $path - * @param string $data - * @return bool + * @param mixed $data + * @return int|false */ public function file_put_contents($path, $data) { return $this->storage->file_put_contents($this->findPathToUse($path), $data); diff --git a/lib/private/Files/Storage/Wrapper/Encryption.php b/lib/private/Files/Storage/Wrapper/Encryption.php index 897624ff6ae..8efb1c20980 100644 --- a/lib/private/Files/Storage/Wrapper/Encryption.php +++ b/lib/private/Files/Storage/Wrapper/Encryption.php @@ -13,7 +13,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -234,8 +234,8 @@ class Encryption extends Wrapper { * see http://php.net/manual/en/function.file_put_contents.php * * @param string $path - * @param string $data - * @return bool + * @param mixed $data + * @return int|false */ public function file_put_contents($path, $data) { // file put content will always be translated to a stream write @@ -263,7 +263,7 @@ class Encryption extends Wrapper { $encryptionModule = $this->getEncryptionModule($path); if ($encryptionModule) { - $this->keyStorage->deleteAllFileKeys($this->getFullPath($path)); + $this->keyStorage->deleteAllFileKeys($fullPath); } return $this->storage->unlink($path); @@ -818,6 +818,7 @@ class Encryption extends Wrapper { $fileSize = $this->filesize($path); $stat['size'] = $fileSize; $stat[7] = $fileSize; + $stat['hasHeader'] = $this->getHeaderSize($path) > 0; return $stat; } diff --git a/lib/private/Files/Storage/Wrapper/Jail.php b/lib/private/Files/Storage/Wrapper/Jail.php index 7350c104ba8..2396c07a0f1 100644 --- a/lib/private/Files/Storage/Wrapper/Jail.php +++ b/lib/private/Files/Storage/Wrapper/Jail.php @@ -56,11 +56,7 @@ class Jail extends Wrapper { } public function getUnjailedPath($path) { - if ($path === '') { - return $this->rootPath; - } else { - return Filesystem::normalizePath($this->rootPath . '/' . $path); - } + return trim(Filesystem::normalizePath($this->rootPath . '/' . $path), '/'); } /** @@ -263,8 +259,8 @@ class Jail extends Wrapper { * see http://php.net/manual/en/function.file_put_contents.php * * @param string $path - * @param string $data - * @return bool + * @param mixed $data + * @return int|false */ public function file_put_contents($path, $data) { return $this->getWrapperStorage()->file_put_contents($this->getUnjailedPath($path), $data); diff --git a/lib/private/Files/Storage/Wrapper/Quota.php b/lib/private/Files/Storage/Wrapper/Quota.php index 62d3335987c..4de1c091a97 100644 --- a/lib/private/Files/Storage/Wrapper/Quota.php +++ b/lib/private/Files/Storage/Wrapper/Quota.php @@ -2,6 +2,7 @@ /** * @copyright Copyright (c) 2016, ownCloud, Inc. * + * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Julius Härtl <jus@bitgrid.net> * @author Lukas Reschke <lukas@statuscode.ch> @@ -9,7 +10,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -71,7 +72,10 @@ class Quota extends Wrapper { protected function getSize($path, $storage = null) { if ($this->config->getValue('quota_include_external_storage', false)) { $rootInfo = Filesystem::getFileInfo('', 'ext'); - return $rootInfo->getSize(true); + if ($rootInfo) { + return $rootInfo->getSize(true); + } + return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED; } else { if (is_null($storage)) { $cache = $this->getCache(); @@ -118,8 +122,8 @@ class Quota extends Wrapper { * see http://php.net/manual/en/function.file_put_contents.php * * @param string $path - * @param string $data - * @return bool + * @param mixed $data + * @return int|false */ public function file_put_contents($path, $data) { $free = $this->free_space($path); @@ -161,7 +165,7 @@ class Quota extends Wrapper { $free = $this->free_space($path); if ($source && $free >= 0 && $mode !== 'r' && $mode !== 'rb') { // only apply quota for files, not metadata, trash or others - if (strpos(ltrim($path, '/'), 'files/') === 0) { + if ($this->shouldApplyQuota($path)) { return \OC\Files\Stream\Quota::wrap($source, $free); } } @@ -183,6 +187,13 @@ class Quota extends Wrapper { } /** + * Only apply quota for files, not metadata, trash or others + */ + private function shouldApplyQuota(string $path): bool { + return strpos(ltrim($path, '/'), 'files/') === 0; + } + + /** * @param IStorage $sourceStorage * @param string $sourceInternalPath * @param string $targetInternalPath @@ -214,7 +225,7 @@ class Quota extends Wrapper { public function mkdir($path) { $free = $this->free_space($path); - if ($free === 0.0) { + if ($this->shouldApplyQuota($path) && $free === 0.0) { return false; } diff --git a/lib/private/Files/Storage/Wrapper/Wrapper.php b/lib/private/Files/Storage/Wrapper/Wrapper.php index 4584bebe076..e94f3714c9e 100644 --- a/lib/private/Files/Storage/Wrapper/Wrapper.php +++ b/lib/private/Files/Storage/Wrapper/Wrapper.php @@ -9,7 +9,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * @author Vinicius Cubas Brand <vinicius@eita.org.br> * * @license AGPL-3.0 @@ -250,8 +250,8 @@ class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStrea * see http://php.net/manual/en/function.file_put_contents.php * * @param string $path - * @param string $data - * @return bool + * @param mixed $data + * @return int|false */ public function file_put_contents($path, $data) { return $this->getWrapperStorage()->file_put_contents($path, $data); diff --git a/lib/private/Files/Stream/Encryption.php b/lib/private/Files/Stream/Encryption.php index 1fc14daacbd..577d1554101 100644 --- a/lib/private/Files/Stream/Encryption.php +++ b/lib/private/Files/Stream/Encryption.php @@ -12,7 +12,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -416,7 +416,7 @@ class Encryption extends Wrapper { return $return; } - $newFilePosition = floor($newPosition / $this->unencryptedBlockSize) + $newFilePosition = (int)floor($newPosition / $this->unencryptedBlockSize) * $this->util->getBlockSize() + $this->headerSize; $oldFilePosition = parent::stream_tell(); @@ -432,7 +432,7 @@ class Encryption extends Wrapper { public function stream_close() { $this->flush('end'); - $position = (int)floor($this->position/$this->unencryptedBlockSize); + $position = (int)floor($this->position / $this->unencryptedBlockSize); $remainingData = $this->encryptionModule->end($this->fullPath, $position . 'end'); if ($this->readOnly === false) { if (!empty($remainingData)) { @@ -465,7 +465,7 @@ class Encryption extends Wrapper { // automatically attempted when the file is written to disk - // we are handling that separately here and we don't want to // get into an infinite loop - $position = (int)floor($this->position/$this->unencryptedBlockSize); + $position = (int)floor($this->position / $this->unencryptedBlockSize); $encrypted = $this->encryptionModule->encrypt($this->cache, $position . $positionPrefix); $bytesWritten = parent::stream_write($encrypted); $this->writeFlag = false; @@ -473,8 +473,8 @@ class Encryption extends Wrapper { // If so then update the encrypted filesize // Note that the unencrypted pointer and filesize are NOT yet updated when flush() is called // We recalculate the encrypted filesize as we do not know the context of calling flush() - $completeBlocksInFile=(int)floor($this->unencryptedSize/$this->unencryptedBlockSize); - if ($completeBlocksInFile === (int)floor($this->position/$this->unencryptedBlockSize)) { + $completeBlocksInFile = (int)floor($this->unencryptedSize / $this->unencryptedBlockSize); + if ($completeBlocksInFile === (int)floor($this->position / $this->unencryptedBlockSize)) { $this->size = $this->util->getBlockSize() * $completeBlocksInFile; $this->size += $bytesWritten; $this->size += $this->headerSize; @@ -493,7 +493,7 @@ class Encryption extends Wrapper { if ($this->cache === '' && !($this->position === $this->unencryptedSize && ($this->position % $this->unencryptedBlockSize) === 0)) { // Get the data from the file handle $data = $this->stream_read_block($this->util->getBlockSize()); - $position = (int)floor($this->position/$this->unencryptedBlockSize); + $position = (int)floor($this->position / $this->unencryptedBlockSize); $numberOfChunks = (int)($this->unencryptedSize / $this->unencryptedBlockSize); if ($numberOfChunks === $position) { $position .= 'end'; diff --git a/lib/private/Files/Stream/Quota.php b/lib/private/Files/Stream/Quota.php index 3bf46264b83..db5691187ce 100644 --- a/lib/private/Files/Stream/Quota.php +++ b/lib/private/Files/Stream/Quota.php @@ -6,7 +6,7 @@ * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Files/Type/Detection.php b/lib/private/Files/Type/Detection.php index bfca6dabd2f..247f6005e57 100644 --- a/lib/private/Files/Type/Detection.php +++ b/lib/private/Files/Type/Detection.php @@ -6,11 +6,14 @@ declare(strict_types=1); * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Andreas Fischer <bantu@owncloud.com> + * @author bladewing <lukas@ifflaender-family.de> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Hendrik Leppelsack <hendrik@leppelsack.de> * @author Jens-Christian Fischer <jens-christian.fischer@switch.ch> * @author Joas Schilling <coding@schilljs.com> + * @author Julius Härtl <jus@bitgrid.net> + * @author lui87kw <lukas.ifflaender@uni-wuerzburg.de> * @author Lukas Reschke <lukas@statuscode.ch> * @author Magnus Walbeck <mw@mwalbeck.org> * @author Morris Jobke <hey@morrisjobke.de> @@ -18,7 +21,7 @@ declare(strict_types=1); * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Tanghus <thomas@tanghus.net> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * @author Xheni Myrtaj <myrtajxheni@gmail.com> * * @license AGPL-3.0 @@ -120,8 +123,14 @@ class Detection implements IMimeTypeDetector { $this->mimetypes = array_merge($this->mimetypes, $types); // Update the alternative mimetypes to avoid having to look them up each time. - foreach ($this->mimetypes as $mimeType) { + foreach ($this->mimetypes as $extension => $mimeType) { + if (strpos($extension, '_comment') === 0) { + continue; + } $this->secureMimeTypes[$mimeType[0]] = $mimeType[1] ?? $mimeType[0]; + if (isset($mimeType[1])) { + $this->secureMimeTypes[$mimeType[1]] = $mimeType[1]; + } } } diff --git a/lib/private/Files/Type/Loader.php b/lib/private/Files/Type/Loader.php index d128bc724b6..489ed65c228 100644 --- a/lib/private/Files/Type/Loader.php +++ b/lib/private/Files/Type/Loader.php @@ -123,7 +123,10 @@ class Loader implements IMimeTypeLoader { ->where( $fetch->expr()->eq('mimetype', $fetch->createNamedParameter($mimetype) )); - $row = $fetch->execute()->fetch(); + + $result = $fetch->execute(); + $row = $result->fetch(); + $result->closeCursor(); if (!$row) { throw new \Exception("Failed to get mimetype id for $mimetype after trying to store it"); @@ -141,7 +144,10 @@ class Loader implements IMimeTypeLoader { $qb = $this->dbConnection->getQueryBuilder(); $qb->select('id', 'mimetype') ->from('mimetypes'); - $results = $qb->execute()->fetchAll(); + + $result = $qb->execute(); + $results = $result->fetchAll(); + $result->closeCursor(); foreach ($results as $row) { $this->mimetypes[$row['id']] = $row['mimetype']; diff --git a/lib/private/Files/Utils/Scanner.php b/lib/private/Files/Utils/Scanner.php index 996380037b7..5509a49941b 100644 --- a/lib/private/Files/Utils/Scanner.php +++ b/lib/private/Files/Utils/Scanner.php @@ -10,7 +10,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index ef8656258d1..1bee09e3659 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -3,6 +3,7 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * @author Ashod Nakashian <ashod.nakashian@collabora.co.uk> * @author Bart Visscher <bartv@thisnet.nl> * @author Björn Schießle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> @@ -26,7 +27,7 @@ * @author Scott Dutton <exussum12@users.noreply.github.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Thomas Tanghus <thomas@tanghus.net> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -239,7 +240,7 @@ class View { public function getLocalFile($path) { $parent = substr($path, 0, strrpos($path, '/')); $path = $this->getAbsolutePath($path); - list($storage, $internalPath) = Filesystem::resolvePath($path); + [$storage, $internalPath] = Filesystem::resolvePath($path); if (Filesystem::isValidPath($parent) and $storage) { return $storage->getLocalFile($internalPath); } else { @@ -254,7 +255,7 @@ class View { public function getLocalFolder($path) { $parent = substr($path, 0, strrpos($path, '/')); $path = $this->getAbsolutePath($path); - list($storage, $internalPath) = Filesystem::resolvePath($path); + [$storage, $internalPath] = Filesystem::resolvePath($path); if (Filesystem::isValidPath($parent) and $storage) { return $storage->getLocalFolder($internalPath); } else { @@ -665,13 +666,19 @@ class View { return false; } - $this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE); + try { + $this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE); + } catch (\Exception $e) { + // Release the shared lock before throwing. + $this->unlockFile($path, ILockingProvider::LOCK_SHARED); + throw $e; + } /** @var \OC\Files\Storage\Storage $storage */ - list($storage, $internalPath) = $this->resolvePath($path); + [$storage, $internalPath] = $this->resolvePath($path); $target = $storage->fopen($internalPath, 'w'); if ($target) { - list(, $result) = \OC_Helper::streamCopy($data, $target); + [, $result] = \OC_Helper::streamCopy($data, $target); fclose($target); fclose($data); @@ -1089,7 +1096,7 @@ class View { [Filesystem::signal_param_path => $this->getHookPath($path)] ); } - list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix); + [$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix); if ($storage) { return $storage->hash($type, $internalPath, $raw); } @@ -1143,7 +1150,7 @@ class View { $run = $this->runHooks($hooks, $path); /** @var \OC\Files\Storage\Storage $storage */ - list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix); + [$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix); if ($run and $storage) { if (in_array('write', $hooks) || in_array('delete', $hooks)) { try { @@ -1568,7 +1575,7 @@ class View { * @var \OC\Files\Storage\Storage $storage * @var string $internalPath */ - list($storage, $internalPath) = Filesystem::resolvePath($path); + [$storage, $internalPath] = Filesystem::resolvePath($path); if ($storage) { $cache = $storage->getCache($path); @@ -1705,7 +1712,7 @@ class View { * @var Storage\Storage $storage * @var string $internalPath */ - list($storage, $internalPath) = $this->resolvePath($path); + [$storage, $internalPath] = $this->resolvePath($path); if ($storage) { return $storage->getETag($internalPath); } else { @@ -1719,10 +1726,11 @@ class View { * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file * * @param int $id - * @throws NotFoundException + * @param int|null $storageId * @return string + * @throws NotFoundException */ - public function getPath($id) { + public function getPath($id, int $storageId = null) { $id = (int)$id; $manager = Filesystem::getMountManager(); $mounts = $manager->findIn($this->fakeRoot); @@ -1737,6 +1745,12 @@ class View { return $a instanceof SharedMount && (!$b instanceof SharedMount) ? 1 : -1; }); + if (!is_null($storageId)) { + $mounts = array_filter($mounts, function (IMountPoint $mount) use ($storageId) { + return $mount->getNumericStorageId() === $storageId; + }); + } + foreach ($mounts as $mount) { /** * @var \OC\Files\Mount\MountPoint $mount @@ -1844,7 +1858,7 @@ class View { public function verifyPath($path, $fileName) { try { /** @type \OCP\Files\Storage $storage */ - list($storage, $internalPath) = $this->resolvePath($path); + [$storage, $internalPath] = $this->resolvePath($path); $storage->verifyPath($internalPath, $fileName); } catch (ReservedWordException $ex) { $l = \OC::$server->getL10N('lib'); diff --git a/lib/private/ForbiddenException.php b/lib/private/ForbiddenException.php index 1ba38e39924..8a00b78291a 100644 --- a/lib/private/ForbiddenException.php +++ b/lib/private/ForbiddenException.php @@ -3,7 +3,7 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Morris Jobke <hey@morrisjobke.de> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/FullTextSearch/Model/SearchRequestSimpleQuery.php b/lib/private/FullTextSearch/Model/SearchRequestSimpleQuery.php index c015c4c1579..f6241938f82 100644 --- a/lib/private/FullTextSearch/Model/SearchRequestSimpleQuery.php +++ b/lib/private/FullTextSearch/Model/SearchRequestSimpleQuery.php @@ -5,6 +5,7 @@ declare(strict_types=1); /** * @copyright 2018 * + * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Maxence Lange <maxence@artificial-owl.com> * * @license GNU AGPL version 3 or any later version @@ -173,8 +174,8 @@ final class SearchRequestSimpleQuery implements ISearchRequestSimpleQuery, JsonS */ public function jsonSerialize() { return [ - 'type' => $this->getType(), - 'field' => $this->getField(), + 'type' => $this->getType(), + 'field' => $this->getField(), 'values' => $this->getValues() ]; } diff --git a/lib/private/Group/Backend.php b/lib/private/Group/Backend.php index ebd8d3620d4..e041254bd5a 100644 --- a/lib/private/Group/Backend.php +++ b/lib/private/Group/Backend.php @@ -5,7 +5,7 @@ * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Knut Ahlers <knut@ahlers.me> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Group/Database.php b/lib/private/Group/Database.php index 8e6181a56cd..97094c67728 100644 --- a/lib/private/Group/Database.php +++ b/lib/private/Group/Database.php @@ -348,15 +348,27 @@ class Database extends ABackend implements $this->fixDI(); $query = $this->dbConn->getQueryBuilder(); - $query->select('uid') - ->from('group_user') + $query->select('g.uid') + ->from('group_user', 'g') ->where($query->expr()->eq('gid', $query->createNamedParameter($gid))) - ->orderBy('uid', 'ASC'); + ->orderBy('g.uid', 'ASC'); if ($search !== '') { - $query->andWhere($query->expr()->like('uid', $query->createNamedParameter( - '%' . $this->dbConn->escapeLikeParameter($search) . '%' - ))); + $query->leftJoin('g', 'users', 'u', $query->expr()->eq('g.uid', 'u.uid')) + ->leftJoin('u', 'preferences', 'p', $query->expr()->andX( + $query->expr()->eq('p.userid', 'u.uid'), + $query->expr()->eq('p.appid', $query->expr()->literal('settings')), + $query->expr()->eq('p.configkey', $query->expr()->literal('email'))) + ) + // sqlite doesn't like re-using a single named parameter here + ->andWhere( + $query->expr()->orX( + $query->expr()->ilike('g.uid', $query->createNamedParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')), + $query->expr()->ilike('u.displayname', $query->createNamedParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')), + $query->expr()->ilike('p.configvalue', $query->createNamedParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')) + ) + ) + ->orderBy('u.uid_lower', 'ASC'); } if ($limit !== -1) { diff --git a/lib/private/Group/Group.php b/lib/private/Group/Group.php index 2e16d5f1242..d3f8c603121 100644 --- a/lib/private/Group/Group.php +++ b/lib/private/Group/Group.php @@ -12,7 +12,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Group/Manager.php b/lib/private/Group/Manager.php index 6056bcdb3e3..4c731a9f488 100644 --- a/lib/private/Group/Manager.php +++ b/lib/private/Group/Manager.php @@ -18,7 +18,7 @@ * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Roman Kreisel <mail@romankreisel.de> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * @author Vinicius Cubas Brand <vinicius@eita.org.br> * @author voxsim "Simon Vocella" * @@ -41,6 +41,7 @@ namespace OC\Group; use OC\Hooks\PublicEmitter; +use OCP\EventDispatcher\IEventDispatcher; use OCP\GroupInterface; use OCP\IGroup; use OCP\IGroupManager; @@ -416,7 +417,8 @@ class Manager extends PublicEmitter implements IGroupManager { $this->subAdmin = new \OC\SubAdmin( $this->userManager, $this, - \OC::$server->getDatabaseConnection() + \OC::$server->getDatabaseConnection(), + \OC::$server->get(IEventDispatcher::class) ); } diff --git a/lib/private/Group/MetaData.php b/lib/private/Group/MetaData.php index 21857b6c8f0..5449cdbe29a 100644 --- a/lib/private/Group/MetaData.php +++ b/lib/private/Group/MetaData.php @@ -4,6 +4,7 @@ * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Joas Schilling <coding@schilljs.com> * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> * @author Lukas Reschke <lukas@statuscode.ch> @@ -30,6 +31,7 @@ namespace OC\Group; +use OC\Group\Manager as GroupManager; use OCP\IGroupManager; use OCP\IUserSession; @@ -44,7 +46,7 @@ class MetaData { protected $isAdmin; /** @var array */ protected $metaData = []; - /** @var IGroupManager */ + /** @var GroupManager */ protected $groupManager; /** @var bool */ protected $sorting = false; @@ -55,7 +57,6 @@ class MetaData { * @param string $user the uid of the current user * @param bool $isAdmin whether the current users is an admin * @param IGroupManager $groupManager - * @param IUserManager $userManager * @param IUserSession $userSession */ public function __construct( diff --git a/lib/private/Hooks/Emitter.php b/lib/private/Hooks/Emitter.php index cd4793ef3f9..4240cef520b 100644 --- a/lib/private/Hooks/Emitter.php +++ b/lib/private/Hooks/Emitter.php @@ -39,6 +39,7 @@ interface Emitter { * @param string $method * @param callable $callback * @return void + * @deprecated 18.0.0 use \OCP\EventDispatcher\IEventDispatcher::addListener */ public function listen($scope, $method, callable $callback); @@ -47,6 +48,7 @@ interface Emitter { * @param string $method optional * @param callable $callback optional * @return void + * @deprecated 18.0.0 use \OCP\EventDispatcher\IEventDispatcher::removeListener */ public function removeListener($scope = null, $method = null, callable $callback = null); } diff --git a/lib/private/Hooks/EmitterTrait.php b/lib/private/Hooks/EmitterTrait.php index 85efa218f64..1cbfe0f5a06 100644 --- a/lib/private/Hooks/EmitterTrait.php +++ b/lib/private/Hooks/EmitterTrait.php @@ -24,6 +24,9 @@ namespace OC\Hooks; +/** + * @deprecated 18.0.0 use events and the \OCP\EventDispatcher\IEventDispatcher service + */ trait EmitterTrait { /** @@ -35,6 +38,7 @@ trait EmitterTrait { * @param string $scope * @param string $method * @param callable $callback + * @deprecated 18.0.0 use \OCP\EventDispatcher\IEventDispatcher::addListener */ public function listen($scope, $method, callable $callback) { $eventName = $scope . '::' . $method; @@ -50,6 +54,7 @@ trait EmitterTrait { * @param string $scope optional * @param string $method optional * @param callable $callback optional + * @deprecated 18.0.0 use \OCP\EventDispatcher\IEventDispatcher::removeListener */ public function removeListener($scope = null, $method = null, callable $callback = null) { $names = []; @@ -93,6 +98,7 @@ trait EmitterTrait { * @param string $scope * @param string $method * @param array $arguments optional + * @deprecated 18.0.0 use \OCP\EventDispatcher\IEventDispatcher::dispatchTyped */ protected function emit($scope, $method, array $arguments = []) { $eventName = $scope . '::' . $method; diff --git a/lib/private/Hooks/ForwardingEmitter.php b/lib/private/Hooks/ForwardingEmitter.php deleted file mode 100644 index 3ac6cca096a..00000000000 --- a/lib/private/Hooks/ForwardingEmitter.php +++ /dev/null @@ -1,66 +0,0 @@ -<?php -/** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ - -namespace OC\Hooks; - -/** - * Class ForwardingEmitter - * - * allows forwarding all listen calls to other emitters - * - * @package OC\Hooks - */ -abstract class ForwardingEmitter extends BasicEmitter { - /** - * @var \OC\Hooks\Emitter[] array - */ - private $forwardEmitters = []; - - /** - * @param string $scope - * @param string $method - * @param callable $callback - */ - public function listen($scope, $method, callable $callback) { - parent::listen($scope, $method, $callback); - foreach ($this->forwardEmitters as $emitter) { - $emitter->listen($scope, $method, $callback); - } - } - - /** - * @param \OC\Hooks\Emitter $emitter - */ - protected function forward(Emitter $emitter) { - $this->forwardEmitters[] = $emitter; - - //forward all previously connected hooks - foreach ($this->listeners as $key => $listeners) { - list($scope, $method) = explode('::', $key, 2); - foreach ($listeners as $listener) { - $emitter->listen($scope, $method, $listener); - } - } - } -} diff --git a/lib/private/Hooks/LegacyEmitter.php b/lib/private/Hooks/LegacyEmitter.php deleted file mode 100644 index 470c5e0b11d..00000000000 --- a/lib/private/Hooks/LegacyEmitter.php +++ /dev/null @@ -1,40 +0,0 @@ -<?php -/** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ - -namespace OC\Hooks; - -abstract class LegacyEmitter extends BasicEmitter { - /** - * @param string $scope - * @param string $method - * @param array $arguments - * - * @suppress PhanAccessMethodProtected - */ - protected function emit($scope, $method, array $arguments = []) { - \OC_Hook::emit($scope, $method, $arguments); - parent::emit($scope, $method, $arguments); - } -} diff --git a/lib/private/Hooks/PublicEmitter.php b/lib/private/Hooks/PublicEmitter.php index dbccc34f2e9..e698b322180 100644 --- a/lib/private/Hooks/PublicEmitter.php +++ b/lib/private/Hooks/PublicEmitter.php @@ -33,6 +33,7 @@ class PublicEmitter extends BasicEmitter { * @param string $scope * @param string $method * @param array $arguments optional + * @deprecated 18.0.0 use \OCP\EventDispatcher\IEventDispatcher::dispatchTyped * * @suppress PhanAccessMethodProtected */ diff --git a/lib/private/Http/Client/Client.php b/lib/private/Http/Client/Client.php index 4c03d386476..3f616df0b28 100644 --- a/lib/private/Http/Client/Client.php +++ b/lib/private/Http/Client/Client.php @@ -5,11 +5,13 @@ declare(strict_types=1); /** * @copyright Copyright (c) 2016, ownCloud, Inc. * + * @author Carlos Ferreira <carlos@reendex.com> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Mohammed Abdellatif <m.latief@gmail.com> + * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Scott Shambarger <devel@shambarger.net> @@ -98,16 +100,12 @@ class Client implements IClient { private function getCertBundle(): string { // If the instance is not yet setup we need to use the static path as - // $this->certificateManager->getAbsoluteBundlePath() tries to instantiiate + // $this->certificateManager->getAbsoluteBundlePath() tries to instantiate // a view if ($this->config->getSystemValue('installed', false) === false) { return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; } - if ($this->certificateManager->listCertificates() === []) { - return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; - } - return $this->certificateManager->getAbsoluteBundlePath(); } @@ -164,7 +162,7 @@ class Client implements IClient { } $host = strtolower($host); - // remove brackets from IPv6 addresses + // Remove brackets from IPv6 addresses if (strpos($host, '[') === 0 && substr($host, -1) === ']') { $host = substr($host, 1, -1); } diff --git a/lib/private/Http/WellKnown/RequestManager.php b/lib/private/Http/WellKnown/RequestManager.php new file mode 100644 index 00000000000..d17ea5c671b --- /dev/null +++ b/lib/private/Http/WellKnown/RequestManager.php @@ -0,0 +1,124 @@ +<?php + +declare(strict_types=1); + +/* + * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Http\WellKnown; + +use OC\AppFramework\Bootstrap\Coordinator; +use OCP\AppFramework\QueryException; +use OCP\Http\WellKnown\IHandler; +use OCP\Http\WellKnown\IRequestContext; +use OCP\Http\WellKnown\IResponse; +use OCP\Http\WellKnown\JrdResponse; +use OCP\IRequest; +use OCP\IServerContainer; +use Psr\Log\LoggerInterface; +use RuntimeException; +use function array_reduce; + +class RequestManager { + + /** @var Coordinator */ + private $coordinator; + + /** @var IServerContainer */ + private $container; + + /** @var LoggerInterface */ + private $logger; + + public function __construct(Coordinator $coordinator, + IServerContainer $container, + LoggerInterface $logger) { + $this->coordinator = $coordinator; + $this->container = $container; + $this->logger = $logger; + } + + public function process(string $service, IRequest $request): ?IResponse { + $handlers = $this->loadHandlers(); + $context = new class($request) implements IRequestContext { + /** @var IRequest */ + private $request; + + public function __construct(IRequest $request) { + $this->request = $request; + } + + public function getHttpRequest(): IRequest { + return $this->request; + } + }; + + $subject = $request->getParam('resource'); + $initialResponse = new JrdResponse($subject ?? ''); + $finalResponse = array_reduce($handlers, function (?IResponse $previousResponse, IHandler $handler) use ($context, $service) { + return $handler->handle($service, $context, $previousResponse); + }, $initialResponse); + + if ($finalResponse instanceof JrdResponse && $finalResponse->isEmpty()) { + return null; + } + + return $finalResponse; + } + + /** + * @return IHandler[] + */ + private function loadHandlers(): array { + $context = $this->coordinator->getRegistrationContext(); + + if ($context === null) { + throw new RuntimeException("Well known handlers requested before the apps had been fully registered"); + } + + $registrations = $context->getWellKnownHandlers(); + $this->logger->debug(count($registrations) . " well known handlers registered"); + + return array_filter( + array_map(function (array $registration) { + $class = $registration['class']; + + try { + $handler = $this->container->get($class); + + if (!($handler) instanceof IHandler) { + $this->logger->error("Well known handler $class is invalid"); + + return null; + } + + return $handler; + } catch (QueryException $e) { + $this->logger->error("Could not load well known handler $class", [ + 'exception' => $e, + ]); + + return null; + } + }, $registrations) + ); + } +} diff --git a/lib/private/InitialStateService.php b/lib/private/InitialStateService.php index 55ce9c41726..7f9a084ef70 100644 --- a/lib/private/InitialStateService.php +++ b/lib/private/InitialStateService.php @@ -6,6 +6,7 @@ declare(strict_types=1); * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl> * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version @@ -28,8 +29,12 @@ declare(strict_types=1); namespace OC; use Closure; +use OC\AppFramework\Bootstrap\Coordinator; +use OCP\AppFramework\QueryException; +use OCP\AppFramework\Services\InitialStateProvider; use OCP\IInitialStateService; use OCP\ILogger; +use OCP\IServerContainer; class InitialStateService implements IInitialStateService { @@ -42,8 +47,16 @@ class InitialStateService implements IInitialStateService { /** @var Closure[][] */ private $lazyStates = []; - public function __construct(ILogger $logger) { + /** @var Coordinator */ + private $bootstrapCoordinator; + + /** @var IServerContainer */ + private $container; + + public function __construct(ILogger $logger, Coordinator $bootstrapCoordinator, IServerContainer $container) { $this->logger = $logger; + $this->bootstrapCoordinator = $bootstrapCoordinator; + $this->container = $container; } public function provideInitialState(string $appName, string $key, $data): void { @@ -72,14 +85,61 @@ class InitialStateService implements IInitialStateService { private function invokeLazyStateCallbacks(): void { foreach ($this->lazyStates as $app => $lazyStates) { foreach ($lazyStates as $key => $lazyState) { + $startTime = microtime(true); $this->provideInitialState($app, $key, $lazyState()); + $endTime = microtime(true); + $duration = $endTime - $startTime; + if ($duration > 1) { + $this->logger->warning('Lazy initial state provider for {key} took {duration} seconds.', [ + 'app' => $app, + 'key' => $key, + 'duration' => round($duration, 2), + ]); + } } } $this->lazyStates = []; } + /** + * Load the lazy states via the IBootstrap mechanism + */ + private function loadLazyStates(): void { + $context = $this->bootstrapCoordinator->getRegistrationContext(); + + if ($context === null) { + // To early, nothing to do yet + return; + } + + $initialStates = $context->getInitialStates(); + foreach ($initialStates as $initialState) { + try { + $provider = $this->container->query($initialState['class']); + } catch (QueryException $e) { + // Log an continue. We can be fault tolerant here. + $this->logger->logException($e, [ + 'message' => 'Could not load initial state provider dynamically: ' . $e->getMessage(), + 'level' => ILogger::ERROR, + 'app' => $initialState['appId'], + ]); + continue; + } + + if (!($provider instanceof InitialStateProvider)) { + // Log an continue. We can be fault tolerant here. + $this->logger->error('Initial state provider is not an InitialStateProvider instance: ' . $initialState['class'], [ + 'app' => $initialState['appId'], + ]); + } + + $this->provideInitialState($initialState['appId'], $provider->getKey(), $provider); + } + } + public function getInitialStates(): array { $this->invokeLazyStateCallbacks(); + $this->loadLazyStates(); $appStates = []; foreach ($this->states as $app => $states) { diff --git a/lib/private/Installer.php b/lib/private/Installer.php index d5c9d076eda..0b020aed569 100644 --- a/lib/private/Installer.php +++ b/lib/private/Installer.php @@ -6,6 +6,7 @@ * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Brice Maron <brice@bmaron.net> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Frank Karlitschek <frank@karlitschek.de> * @author Georg Ehrke <oc.list@georgehrke.com> * @author Joas Schilling <coding@schilljs.com> @@ -42,6 +43,7 @@ namespace OC; use Doctrine\DBAL\Exception\TableExistsException; use OC\App\AppStore\Bundles\Bundle; use OC\App\AppStore\Fetcher\AppFetcher; +use OC\AppFramework\Bootstrap\Coordinator; use OC\Archive\TAR; use OC_App; use OC_DB; @@ -138,6 +140,9 @@ class Installer { // check for required dependencies \OC_App::checkAppDependencies($this->config, $l, $info, $ignoreMax); + /** @var Coordinator $coordinator */ + $coordinator = \OC::$server->get(Coordinator::class); + $coordinator->runLazyRegistration($appId); \OC_App::registerAutoloading($appId, $basedir); $previousVersion = $this->config->getAppValue($info['id'], 'installed_version', false); @@ -154,7 +159,7 @@ class Installer { } } else { $ms = new \OC\DB\MigrationService($info['id'], \OC::$server->getDatabaseConnection()); - $ms->migrate(); + $ms->migrate('latest', true); } if ($previousVersion) { OC_App::executeRepairSteps($appId, $info['repair-steps']['post-migration']); @@ -173,10 +178,10 @@ class Installer { \OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no'); //set remote/public handlers - foreach ($info['remote'] as $name=>$path) { + foreach ($info['remote'] as $name => $path) { \OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path); } - foreach ($info['public'] as $name=>$path) { + foreach ($info['public'] as $name => $path) { \OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path); } @@ -189,12 +194,13 @@ class Installer { * Updates the specified app from the appstore * * @param string $appId + * @param bool [$allowUnstable] Allow unstable releases * @return bool */ - public function updateAppstoreApp($appId) { - if ($this->isUpdateAvailable($appId)) { + public function updateAppstoreApp($appId, $allowUnstable = false) { + if ($this->isUpdateAvailable($appId, $allowUnstable)) { try { - $this->downloadApp($appId); + $this->downloadApp($appId, $allowUnstable); } catch (\Exception $e) { $this->logger->logException($e, [ 'level' => ILogger::ERROR, @@ -212,13 +218,14 @@ class Installer { * Downloads an app and puts it into the app directory * * @param string $appId + * @param bool [$allowUnstable] * * @throws \Exception If the installation was not successful */ - public function downloadApp($appId) { + public function downloadApp($appId, $allowUnstable = false) { $appId = strtolower($appId); - $apps = $this->appFetcher->get(); + $apps = $this->appFetcher->get($allowUnstable); foreach ($apps as $app) { if ($app['id'] === $appId) { // Load the certificate @@ -292,12 +299,14 @@ class Installer { if ($archive) { if (!$archive->extract($extractDir)) { - throw new \Exception( - sprintf( - 'Could not extract app %s', - $appId - ) - ); + $errorMessage = 'Could not extract app ' . $appId; + + $archiveError = $archive->getError(); + if ($archiveError instanceof \PEAR_Error) { + $errorMessage .= ': ' . $archiveError->getMessage(); + } + + throw new \Exception($errorMessage); } $allFiles = scandir($extractDir); $folders = array_diff($allFiles, ['.', '..']); @@ -384,9 +393,10 @@ class Installer { * Check if an update for the app is available * * @param string $appId + * @param bool $allowUnstable * @return string|false false or the version number of the update */ - public function isUpdateAvailable($appId) { + public function isUpdateAvailable($appId, $allowUnstable = false) { if ($this->isInstanceReadyForUpdates === null) { $installPath = OC_App::getInstallPath(); if ($installPath === false || $installPath === null) { @@ -405,7 +415,7 @@ class Installer { } if ($this->apps === null) { - $this->apps = $this->appFetcher->get(); + $this->apps = $this->appFetcher->get($allowUnstable); } foreach ($this->apps as $app) { @@ -452,7 +462,7 @@ class Installer { */ public function isDownloaded($name) { foreach (\OC::$APPSROOTS as $dir) { - $dirToTest = $dir['path']; + $dirToTest = $dir['path']; $dirToTest .= '/'; $dirToTest .= $name; $dirToTest .= '/'; @@ -532,7 +542,7 @@ class Installer { if ($filename[0] !== '.' and is_dir($app_dir['path']."/$filename")) { if (file_exists($app_dir['path']."/$filename/appinfo/info.xml")) { if ($config->getAppValue($filename, "installed_version", null) === null) { - $info=OC_App::getAppInfo($filename); + $info = OC_App::getAppInfo($filename); $enabled = isset($info['default_enable']); if (($enabled || in_array($filename, $appManager->getAlwaysEnabledApps())) && $config->getAppValue($filename, 'enabled') !== 'no') { @@ -584,7 +594,7 @@ class Installer { } } else { $ms = new \OC\DB\MigrationService($app, \OC::$server->getDatabaseConnection()); - $ms->migrate(); + $ms->migrate('latest', true); } //run appinfo/install.php @@ -606,10 +616,10 @@ class Installer { } //set remote/public handlers - foreach ($info['remote'] as $name=>$path) { + foreach ($info['remote'] as $name => $path) { $config->setAppValue('core', 'remote_'.$name, $app.'/'.$path); } - foreach ($info['public'] as $name=>$path) { + foreach ($info['public'] as $name => $path) { $config->setAppValue('core', 'public_'.$name, $app.'/'.$path); } diff --git a/lib/private/IntegrityCheck/Checker.php b/lib/private/IntegrityCheck/Checker.php index f5c4d6bb016..504cd391c42 100644 --- a/lib/private/IntegrityCheck/Checker.php +++ b/lib/private/IntegrityCheck/Checker.php @@ -11,7 +11,7 @@ declare(strict_types=1); * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Victor Dubiniuk <dubiniuk@owncloud.com> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * @author Xheni Myrtaj <myrtajxheni@gmail.com> * * @license AGPL-3.0 diff --git a/lib/private/L10N/Factory.php b/lib/private/L10N/Factory.php index 3ab62de4b97..9c9a3cc951e 100644 --- a/lib/private/L10N/Factory.php +++ b/lib/private/L10N/Factory.php @@ -9,6 +9,7 @@ * @author Bjoern Schiessle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Georg Ehrke <oc.list@georgehrke.com> + * @author GretaD <gretadoci@gmail.com> * @author Joas Schilling <coding@schilljs.com> * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> * @author Lukas Reschke <lukas@statuscode.ch> @@ -65,6 +66,11 @@ class Factory implements IFactory { /** * @var array */ + protected $localeCache = []; + + /** + * @var array + */ protected $availableLocales = []; /** @@ -390,12 +396,14 @@ class Factory implements IFactory { return true; } - $locales = $this->findAvailableLocales(); - $userLocale = array_filter($locales, function ($value) use ($locale) { - return $locale === $value['code']; - }); + if ($this->localeCache === []) { + $locales = $this->findAvailableLocales(); + foreach ($locales as $l) { + $this->localeCache[$l['code']] = true; + } + } - return !empty($userLocale); + return isset($this->localeCache[$locale]); } /** diff --git a/lib/private/LargeFileHelper.php b/lib/private/LargeFileHelper.php index 2a6a6714eb3..b07d77a02fa 100755 --- a/lib/private/LargeFileHelper.php +++ b/lib/private/LargeFileHelper.php @@ -5,6 +5,7 @@ * * @author Andreas Fischer <bantu@owncloud.com> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author marco44 <cousinmarc@gmail.com> * @author Michael Roitzsch <reactorcontrol@icloud.com> @@ -29,6 +30,8 @@ namespace OC; +use bantu\IniGetWrapper\IniGetWrapper; + /** * Helper class for large files on 32-bit platforms. */ @@ -117,7 +120,7 @@ class LargeFileHelper { * null on failure. */ public function getFileSizeViaCurl($fileName) { - if (\OC::$server->getIniWrapper()->getString('open_basedir') === '') { + if (\OC::$server->get(IniGetWrapper::class)->getString('open_basedir') === '') { $encodedFileName = rawurlencode($fileName); $ch = curl_init("file:///$encodedFileName"); curl_setopt($ch, CURLOPT_NOBODY, true); @@ -190,7 +193,7 @@ class LargeFileHelper { try { $result = filemtime($fullPath); } catch (\Exception $e) { - $result =- 1; + $result = - 1; } if ($result < 0) { if (\OC_Helper::is_function_enabled('exec')) { diff --git a/lib/private/Lock/DBLockingProvider.php b/lib/private/Lock/DBLockingProvider.php index f48bf57028f..c6b9cf63780 100644 --- a/lib/private/Lock/DBLockingProvider.php +++ b/lib/private/Lock/DBLockingProvider.php @@ -5,7 +5,6 @@ * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Individual IT Services <info@individual-it.net> * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Ole Ostergaard <ole.c.ostergaard@gmail.com> * @author Robin Appelman <robin@icewind.nl> @@ -216,8 +215,6 @@ class DBLockingProvider extends AbstractLockingProvider { /** * @param string $path * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE - * - * @suppress SqlInjectionChecker */ public function releaseLock(string $path, int $type) { $this->markRelease($path, $type); @@ -288,8 +285,6 @@ class DBLockingProvider extends AbstractLockingProvider { /** * release all lock acquired by this instance which were marked using the mark* methods - * - * @suppress SqlInjectionChecker */ public function releaseAll() { parent::releaseAll(); diff --git a/lib/private/Lock/MemcacheLockingProvider.php b/lib/private/Lock/MemcacheLockingProvider.php index b8b4cef275d..439894e901f 100644 --- a/lib/private/Lock/MemcacheLockingProvider.php +++ b/lib/private/Lock/MemcacheLockingProvider.php @@ -6,6 +6,7 @@ declare(strict_types=1); * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Jaakko Salo <jaakkos@gmail.com> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @@ -60,7 +61,7 @@ class MemcacheLockingProvider extends AbstractLockingProvider { public function isLocked(string $path, int $type): bool { $lockValue = $this->memcache->get($path); if ($type === self::LOCK_SHARED) { - return $lockValue > 0; + return is_int($lockValue) && $lockValue > 0; } elseif ($type === self::LOCK_EXCLUSIVE) { return $lockValue === 'exclusive'; } else { diff --git a/lib/private/Lock/NoopLockingProvider.php b/lib/private/Lock/NoopLockingProvider.php index 4f38d5159b9..728ad26de68 100644 --- a/lib/private/Lock/NoopLockingProvider.php +++ b/lib/private/Lock/NoopLockingProvider.php @@ -7,7 +7,7 @@ declare(strict_types=1); * * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Log.php b/lib/private/Log.php index 2048d60a53b..83b92ecd1ab 100644 --- a/lib/private/Log.php +++ b/lib/private/Log.php @@ -36,10 +36,9 @@ declare(strict_types=1); namespace OC; +use Nextcloud\LogNormalizer\Normalizer; use OCP\Log\IDataLogger; use function array_merge; -use InterfaSys\LogNormalizer\Normalizer; - use OC\Log\ExceptionSerializer; use OCP\ILogger; use OCP\Log\IFileBased; diff --git a/lib/private/Log/ErrorHandler.php b/lib/private/Log/ErrorHandler.php index d37af8212a0..3f4c9ee64b1 100644 --- a/lib/private/Log/ErrorHandler.php +++ b/lib/private/Log/ErrorHandler.php @@ -5,6 +5,7 @@ * @author Bart Visscher <bartv@thisnet.nl> * @author Björn Schießle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Julius Härtl <jus@bitgrid.net> * @author Morris Jobke <hey@morrisjobke.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * @@ -41,7 +42,7 @@ class ErrorHandler { return preg_replace('/\/\/(.*):(.*)@/', '//xxx:xxx@', $msg); } - public static function register($debug=false) { + public static function register($debug = false) { $handler = new ErrorHandler(); if ($debug) { @@ -88,12 +89,14 @@ class ErrorHandler { return; } $msg = $message . ' at ' . $file . '#' . $line; - self::$logger->error(self::removePassword($msg), ['app' => 'PHP']); + $e = new \Error(self::removePassword($msg)); + self::$logger->logException($e, ['app' => 'PHP']); } //Recoverable handler which catch all errors, warnings and notices public static function onAll($number, $message, $file, $line) { $msg = $message . ' at ' . $file . '#' . $line; - self::$logger->debug(self::removePassword($msg), ['app' => 'PHP']); + $e = new \Error(self::removePassword($msg)); + self::$logger->logException($e, ['app' => 'PHP', 'level' => 0]); } } diff --git a/lib/private/Log/ExceptionSerializer.php b/lib/private/Log/ExceptionSerializer.php index a2bc1963003..0cb68f08914 100644 --- a/lib/private/Log/ExceptionSerializer.php +++ b/lib/private/Log/ExceptionSerializer.php @@ -8,6 +8,7 @@ * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Vincent Petry <vincent@nextcloud.com> * * @license GNU AGPL version 3 or any later version * @@ -86,6 +87,9 @@ class ExceptionSerializer { // files_external: UserStoragesController 'update', + + // Preview providers, don't log big data strings + 'imagecreatefromstring', ]; public const methodsWithSensitiveParametersByClass = [ diff --git a/lib/private/Log/File.php b/lib/private/Log/File.php index 9e9abb11484..289afed9179 100644 --- a/lib/private/Log/File.php +++ b/lib/private/Log/File.php @@ -7,6 +7,7 @@ * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author duritong <peter.meier+github@immerda.ch> * @author Georg Ehrke <oc.list@georgehrke.com> + * @author J0WI <J0WI@users.noreply.github.com> * @author Joas Schilling <coding@schilljs.com> * @author Julius Härtl <jus@bitgrid.net> * @author Lukas Reschke <lukas@statuscode.ch> @@ -106,7 +107,7 @@ class File extends LogDetails implements IWriter, IFileBased { * @param int $offset * @return array */ - public function getEntries(int $limit=50, int $offset=0):array { + public function getEntries(int $limit = 50, int $offset = 0):array { $minLevel = $this->config->getValue("loglevel", ILogger::WARN); $entries = []; $handle = @fopen($this->logFile, 'rb'); @@ -117,7 +118,7 @@ class File extends LogDetails implements IWriter, IFileBased { $entriesCount = 0; $lines = 0; // Loop through each character of the file looking for new lines - while ($pos >= 0 && ($limit === null ||$entriesCount < $limit)) { + while ($pos >= 0 && ($limit === null || $entriesCount < $limit)) { fseek($handle, $pos); $ch = fgetc($handle); if ($ch == "\n" || $pos == 0) { diff --git a/lib/private/Mail/EMailTemplate.php b/lib/private/Mail/EMailTemplate.php index 2c8efa7e010..d5bd27007df 100644 --- a/lib/private/Mail/EMailTemplate.php +++ b/lib/private/Mail/EMailTemplate.php @@ -8,15 +8,17 @@ declare(strict_types=1); * * @author Bjoern Schiessle <bjoern@schiessle.org> * @author brad2014 <brad2014@users.noreply.github.com> + * @author Brad Rubenstein <brad@wbr.tech> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Jan-Christoph Borchardt <hey@jancborchardt.net> * @author Joas Schilling <coding@schilljs.com> * @author Julius Härtl <jus@bitgrid.net> * @author Liam JACK <liamjack@users.noreply.github.com> * @author Lukas Reschke <lukas@statuscode.ch> + * @author medcloud <42641918+medcloud@users.noreply.github.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Tomasz Paluszkiewicz <tomasz.paluszkiewicz@gmail.com> - * @author Simon Spannagel <simonspa@kth.se> * * @license GNU AGPL version 3 or any later version * @@ -118,8 +120,8 @@ EOF; <table class="row collapse" style="border-collapse:collapse;border-spacing:0;display:table;padding:0;position:relative;text-align:left;vertical-align:top;width:100%%"> <tbody> <tr style="padding:0;text-align:left;vertical-align:top"> - <center data-parsed="" style="background-color:%s;width:150px;height:150px;padding:0px;position:relative;border-radius:200px"> - <img class="logo float-center" src="%s" alt="%s" align="center" style="-ms-interpolation-mode:bicubic;clear:both;display:block;float:none;outline:0;text-align:center;text-decoration:none;position:absolute;max-height:100px;max-width:100px;width:auto;height:auto;position:absolute;top:0;bottom:0;left:0;right:0;margin:auto;"> + <center data-parsed="" style="background-color:%s;min-width:175px;max-height:175px; padding:35px 0px;border-radius:200px"> + <img class="logo float-center" src="%s" alt="%s" align="center" style="-ms-interpolation-mode:bicubic;clear:both;display:block;float:none;margin:0 auto;outline:0;text-align:center;text-decoration:none;max-height:105px;max-width:105px;width:auto;height:auto"> </center> </tr> </tbody> @@ -291,7 +293,7 @@ EOF; <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%"> <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid %2\$;border-collapse:collapse!important;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid %2\$s;border-collapse:collapse!important;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> <a href="%3\$s" style="Margin:0;border:0 solid %4\$s;border-radius:2px;color:%5\$s;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:1.3;margin:0;padding:10px 25px 10px 25px;text-align:left;outline:1px solid %5\$s;text-decoration:none">%7\$s</a> </td> </tr> @@ -447,19 +449,21 @@ EOF; * @param string $metaInfo Note: When $plainMetaInfo falls back to this, HTML is automatically escaped in the HTML email * @param string $icon Absolute path, must be 16*16 pixels * @param string|bool $plainText Text that is used in the plain text email - * if empty the $text is used, if false none will be used + * if empty or true the $text is used, if false none will be used * @param string|bool $plainMetaInfo Meta info that is used in the plain text email - * if empty the $metaInfo is used, if false none will be used + * if empty or true the $metaInfo is used, if false none will be used + * @param integer plainIndent If > 0, Indent plainText by this amount. * @since 12.0.0 */ - public function addBodyListItem(string $text, string $metaInfo = '', string $icon = '', $plainText = '', $plainMetaInfo = '') { + public function addBodyListItem(string $text, string $metaInfo = '', string $icon = '', $plainText = '', $plainMetaInfo = '', $plainIndent = 0) { $this->ensureBodyListOpened(); - if ($plainText === '') { + if ($plainText === '' || $plainText === true) { $plainText = $text; $text = htmlspecialchars($text); + $text = str_replace("\n", "<br/>", $text); // convert newlines to HTML breaks } - if ($plainMetaInfo === '') { + if ($plainMetaInfo === '' || $plainMetaInfo === true) { $plainMetaInfo = $metaInfo; $metaInfo = htmlspecialchars($metaInfo); } @@ -475,11 +479,29 @@ EOF; } $this->htmlBody .= vsprintf($this->listItem, [$icon, $htmlText]); if ($plainText !== false) { - $this->plainBody .= ' * ' . $plainText; - if ($plainMetaInfo !== false) { - $this->plainBody .= ' (' . $plainMetaInfo . ')'; + if ($plainIndent === 0) { + /* + * If plainIndent is not set by caller, this is the old NC17 layout code. + */ + $this->plainBody .= ' * ' . $plainText; + if ($plainMetaInfo !== false) { + $this->plainBody .= ' (' . $plainMetaInfo . ')'; + } + $this->plainBody .= PHP_EOL; + } else { + /* + * Caller can set plainIndent > 0 to format plainText in tabular fashion. + * with plainMetaInfo in column 1, and plainText in column 2. + * The plainMetaInfo label is right justified in a field of width + * "plainIndent". Multilines after the first are indented plainIndent+1 + * (to account for space after label). Fixes: #12391 + */ + /** @var string $label */ + $label = ($plainMetaInfo !== false)? $plainMetaInfo : ''; + $this->plainBody .= sprintf("%${plainIndent}s %s\n", + $label, + str_replace("\n", "\n" . str_repeat(' ', $plainIndent + 1), $plainText)); } - $this->plainBody .= PHP_EOL; } } @@ -538,7 +560,7 @@ EOF; $textColor = $this->themingDefaults->getTextColorPrimary(); $this->htmlBody .= vsprintf($this->buttonGroup, [$color, $color, $urlLeft, $color, $textColor, $textColor, $textLeft, $urlRight, $textRight]); - $this->plainBody .= $plainTextLeft . ': ' . $urlLeft . PHP_EOL; + $this->plainBody .= PHP_EOL . $plainTextLeft . ': ' . $urlLeft . PHP_EOL; $this->plainBody .= $plainTextRight . ': ' . $urlRight . PHP_EOL . PHP_EOL; } @@ -573,7 +595,7 @@ EOF; $this->plainBody .= $plainText . ': '; } - $this->plainBody .= $url . PHP_EOL; + $this->plainBody .= $url . PHP_EOL; } /** diff --git a/lib/private/Mail/Mailer.php b/lib/private/Mail/Mailer.php index fc8de9ccd0b..f8dc739428a 100644 --- a/lib/private/Mail/Mailer.php +++ b/lib/private/Mail/Mailer.php @@ -12,6 +12,7 @@ declare(strict_types=1); * @author Jared Boone <jared.boone@gmail.com> * @author Joas Schilling <coding@schilljs.com> * @author Julius Härtl <jus@bitgrid.net> + * @author kevin147147 <kevintamool@gmail.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> @@ -59,9 +60,9 @@ use OCP\Mail\Events\BeforeMessageSent; * $mailer = \OC::$server->getMailer(); * $message = $mailer->createMessage(); * $message->setSubject('Your Subject'); - * $message->setFrom(array('cloud@domain.org' => 'ownCloud Notifier'); - * $message->setTo(array('recipient@domain.org' => 'Recipient'); - * $message->setBody('The message text'); + * $message->setFrom(array('cloud@domain.org' => 'ownCloud Notifier')); + * $message->setTo(array('recipient@domain.org' => 'Recipient')); + * $message->setBody('The message text', 'text/html'); * $mailer->send($message); * * This message can then be passed to send() of \OC\Mail\Mailer diff --git a/lib/private/Memcache/APCu.php b/lib/private/Memcache/APCu.php index 87d72ec1968..ed3c81b70d7 100644 --- a/lib/private/Memcache/APCu.php +++ b/lib/private/Memcache/APCu.php @@ -5,7 +5,7 @@ * @author Andreas Fischer <bantu@owncloud.com> * @author Bart Visscher <bartv@thisnet.nl> * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Clark Tomlinson <fallen013@gmail.com> + * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> @@ -28,6 +28,7 @@ namespace OC\Memcache; +use bantu\IniGetWrapper\IniGetWrapper; use OCP\IMemcache; class APCu extends Cache implements IMemcache { @@ -154,9 +155,9 @@ class APCu extends Cache implements IMemcache { public static function isAvailable() { if (!extension_loaded('apcu')) { return false; - } elseif (!\OC::$server->getIniWrapper()->getBool('apc.enabled')) { + } elseif (!\OC::$server->get(IniGetWrapper::class)->getBool('apc.enabled')) { return false; - } elseif (!\OC::$server->getIniWrapper()->getBool('apc.enable_cli') && \OC::$CLI) { + } elseif (!\OC::$server->get(IniGetWrapper::class)->getBool('apc.enable_cli') && \OC::$CLI) { return false; } elseif ( version_compare(phpversion('apc') ?: '0.0.0', '4.0.6') === -1 && diff --git a/lib/private/Memcache/Factory.php b/lib/private/Memcache/Factory.php index 030769b5557..6db9b1007ac 100644 --- a/lib/private/Memcache/Factory.php +++ b/lib/private/Memcache/Factory.php @@ -11,7 +11,7 @@ * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Stefan Weil <sw@weilnetz.de> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Memcache/Memcached.php b/lib/private/Memcache/Memcached.php index eff76a41c45..7b852a418e1 100644 --- a/lib/private/Memcache/Memcached.php +++ b/lib/private/Memcache/Memcached.php @@ -52,13 +52,13 @@ class Memcached extends Cache implements IMemcache { $defaultOptions = [ \Memcached::OPT_CONNECT_TIMEOUT => 50, - \Memcached::OPT_RETRY_TIMEOUT => 50, - \Memcached::OPT_SEND_TIMEOUT => 50, - \Memcached::OPT_RECV_TIMEOUT => 50, - \Memcached::OPT_POLL_TIMEOUT => 50, + \Memcached::OPT_RETRY_TIMEOUT => 50, + \Memcached::OPT_SEND_TIMEOUT => 50, + \Memcached::OPT_RECV_TIMEOUT => 50, + \Memcached::OPT_POLL_TIMEOUT => 50, // Enable compression - \Memcached::OPT_COMPRESSION => true, + \Memcached::OPT_COMPRESSION => true, // Turn on consistent hashing \Memcached::OPT_LIBKETAMA_COMPATIBLE => true, @@ -110,7 +110,7 @@ class Memcached extends Cache implements IMemcache { public function set($key, $value, $ttl = 0) { if ($ttl > 0) { - $result = self::$cache->set($this->getNameSpace() . $key, $value, $ttl); + $result = self::$cache->set($this->getNameSpace() . $key, $value, $ttl); } else { $result = self::$cache->set($this->getNameSpace() . $key, $value); } @@ -126,7 +126,7 @@ class Memcached extends Cache implements IMemcache { } public function remove($key) { - $result= self::$cache->delete($this->getNameSpace() . $key); + $result = self::$cache->delete($this->getNameSpace() . $key); if (self::$cache->getResultCode() !== \Memcached::RES_NOTFOUND) { $this->verifyReturnCode(); } diff --git a/lib/private/Memcache/NullCache.php b/lib/private/Memcache/NullCache.php index bdb6406b24c..5ad3cb22201 100644 --- a/lib/private/Memcache/NullCache.php +++ b/lib/private/Memcache/NullCache.php @@ -7,7 +7,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Memcache/Redis.php b/lib/private/Memcache/Redis.php index dfbdd029565..56470afa0c5 100644 --- a/lib/private/Memcache/Redis.php +++ b/lib/private/Memcache/Redis.php @@ -101,7 +101,13 @@ class Redis extends Cache implements IMemcacheTTL { if (!is_int($value)) { $value = json_encode($value); } - return self::$cache->setnx($this->getPrefix() . $key, $value); + + $args = ['nx']; + if ($ttl !== 0 && is_int($ttl)) { + $args['ex'] = $ttl; + } + + return self::$cache->set($this->getPrefix() . $key, $value, $args); } /** diff --git a/lib/private/Migration/BackgroundRepair.php b/lib/private/Migration/BackgroundRepair.php index fd616d505e0..5b8c7875ab7 100644 --- a/lib/private/Migration/BackgroundRepair.php +++ b/lib/private/Migration/BackgroundRepair.php @@ -2,6 +2,7 @@ /** * @copyright Copyright (c) 2016, ownCloud, Inc. * + * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Lukas Reschke <lukas@statuscode.ch> * @author Roeland Jago Douma <roeland@famdouma.nl> @@ -62,7 +63,7 @@ class BackgroundRepair extends TimedJob { */ public function execute($jobList, ILogger $logger = null) { // add an interval of 15 mins - $this->setInterval(15*60); + $this->setInterval(15 * 60); $this->jobList = $jobList; $this->logger = $logger; diff --git a/lib/private/NaturalSort.php b/lib/private/NaturalSort.php index 31829a84e6a..d328ad0f359 100644 --- a/lib/private/NaturalSort.php +++ b/lib/private/NaturalSort.php @@ -8,7 +8,7 @@ * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/NavigationManager.php b/lib/private/NavigationManager.php index 81642fac234..e250523b98f 100644 --- a/lib/private/NavigationManager.php +++ b/lib/private/NavigationManager.php @@ -6,6 +6,7 @@ * @author Bart Visscher <bartv@thisnet.nl> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Daniel Kesselberg <mail@danielkesselberg.de> + * @author Georg Ehrke <oc.list@georgehrke.com> * @author Joas Schilling <coding@schilljs.com> * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> * @author Julius Härtl <jus@bitgrid.net> @@ -79,12 +80,7 @@ class NavigationManager implements INavigationManager { } /** - * Creates a new navigation entry - * - * @param array|\Closure $entry Array containing: id, name, order, icon and href key - * The use of a closure is preferred, because it will avoid - * loading the routing of your app, unless required. - * @return void + * @inheritDoc */ public function add($entry) { if ($entry instanceof \Closure) { @@ -106,10 +102,7 @@ class NavigationManager implements INavigationManager { } /** - * Get a list of navigation entries - * - * @param string $type type of the navigation entries - * @return array + * @inheritDoc */ public function getAll(string $type = 'link'): array { $this->init(); @@ -171,19 +164,14 @@ class NavigationManager implements INavigationManager { } /** - * Sets the current navigation entry of the currently running app - * @param string $id of the app entry to activate (from added $entry) + * @inheritDoc */ public function setActiveEntry($id) { $this->activeEntry = $id; } /** - * gets the active Menu entry - * @return string id or empty string - * - * This function returns the id of the active navigation entry (set by - * setActiveEntry + * @inheritDoc */ public function getActiveEntry() { return $this->activeEntry; diff --git a/lib/private/OCS/DiscoveryService.php b/lib/private/OCS/DiscoveryService.php index 1c69d1ecc9a..0a28b09fda1 100644 --- a/lib/private/OCS/DiscoveryService.php +++ b/lib/private/OCS/DiscoveryService.php @@ -97,7 +97,7 @@ class DiscoveryService implements IDiscoveryService { } // Write into cache - $this->cache->set($remote . '#' . $service, json_encode($discoveredServices), 60*60*24); + $this->cache->set($remote . '#' . $service, json_encode($discoveredServices), 60 * 60 * 24); return $discoveredServices; } diff --git a/lib/private/Preview/Generator.php b/lib/private/Preview/Generator.php index f7aed987d89..2be08d1b2c4 100644 --- a/lib/private/Preview/Generator.php +++ b/lib/private/Preview/Generator.php @@ -3,10 +3,12 @@ * @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl> * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Elijah Martin-Merrill <elijah@nyp-itsours.com> * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Scott Dutton <scott@exussum.co.uk> * * @license GNU AGPL version 3 or any later version * @@ -126,9 +128,6 @@ class Generator { if ($mimeType === null) { $mimeType = $file->getMimeType(); } - if (!$this->previewManager->isMimeSupported($mimeType)) { - throw new NotFoundException(); - } $previewFolder = $this->getPreviewFolder($file); @@ -155,7 +154,7 @@ class Generator { $crop = $specification['crop'] ?? false; $mode = $specification['mode'] ?? IPreview::MODE_FILL; - // If both width and heigth are -1 we just want the max preview + // If both width and height are -1 we just want the max preview if ($width === -1 && $height === -1) { $width = $maxWidth; $height = $maxHeight; @@ -176,6 +175,10 @@ class Generator { try { $preview = $this->getCachedPreview($previewFolder, $width, $height, $crop, $maxPreview->getMimeType(), $previewVersion); } catch (NotFoundException $e) { + if (!$this->previewManager->isMimeSupported($mimeType)) { + throw new NotFoundException(); + } + if ($maxPreviewImage === null) { $maxPreviewImage = $this->helper->getImage($maxPreview); } @@ -192,6 +195,12 @@ class Generator { } } + // Free memory being used by the embedded image resource. Without this the image is kept in memory indefinitely. + // Garbage Collection does NOT free this memory. We have to do it ourselves. + if ($maxPreviewImage instanceof \OC_Image) { + $maxPreviewImage->destroy(); + } + return $preview; } diff --git a/lib/private/Preview/Storage/Root.php b/lib/private/Preview/Storage/Root.php index a9a72026a51..675263fdd50 100644 --- a/lib/private/Preview/Storage/Root.php +++ b/lib/private/Preview/Storage/Root.php @@ -5,6 +5,7 @@ declare(strict_types=1); /** * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> * + * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version @@ -33,13 +34,16 @@ use OCP\Files\NotFoundException; use OCP\Files\SimpleFS\ISimpleFolder; class Root extends AppData { + private $isMultibucketPreviewDistributionEnabled = false; public function __construct(IRootFolder $rootFolder, SystemConfig $systemConfig) { parent::__construct($rootFolder, $systemConfig, 'preview'); + + $this->isMultibucketPreviewDistributionEnabled = $systemConfig->getValue('objectstore.multibucket.preview-distribution', false) === true; } public function getFolder(string $name): ISimpleFolder { - $internalFolder = $this->getInternalFolder($name); + $internalFolder = self::getInternalFolder($name); try { return parent::getFolder($internalFolder); @@ -50,11 +54,24 @@ class Root extends AppData { */ } - return parent::getFolder($name); + try { + return parent::getFolder($name); + } catch (NotFoundException $e) { + /* + * The old folder structure is not found. + * Lets try the multibucket fallback if available + */ + if ($this->isMultibucketPreviewDistributionEnabled) { + return parent::getFolder('old-multibucket/' . $internalFolder); + } + + // when there is no further fallback just throw the exception + throw $e; + } } public function newFolder(string $name): ISimpleFolder { - $internalFolder = $this->getInternalFolder($name); + $internalFolder = self::getInternalFolder($name); return parent::newFolder($internalFolder); } @@ -66,7 +83,7 @@ class Root extends AppData { return []; } - private function getInternalFolder(string $name): string { + public static function getInternalFolder(string $name): string { return implode('/', str_split(substr(md5($name), 0, 7))) . '/' . $name; } } diff --git a/lib/private/Preview/TXT.php b/lib/private/Preview/TXT.php index 4004b0a0647..968a15a5683 100644 --- a/lib/private/Preview/TXT.php +++ b/lib/private/Preview/TXT.php @@ -76,7 +76,7 @@ class TXT extends ProviderV2 { imagecolorallocate($image, 255, 255, 255); $textColor = imagecolorallocate($image, 0, 0, 0); - $fontFile = __DIR__; + $fontFile = __DIR__; $fontFile .= '/../../../core'; $fontFile .= '/fonts/NotoSans-Regular.ttf'; diff --git a/lib/private/PreviewManager.php b/lib/private/PreviewManager.php index 546d495b47d..8fa0fc92da5 100644 --- a/lib/private/PreviewManager.php +++ b/lib/private/PreviewManager.php @@ -261,7 +261,6 @@ class PreviewManager implements IPreview { continue; } - /** @var $provider IProvider */ if ($provider->isAvailable($file)) { return true; } @@ -370,14 +369,14 @@ class PreviewManager implements IPreview { $checkImagick = new \Imagick(); $imagickProviders = [ - 'SVG' => ['mimetype' => '/image\/svg\+xml/', 'class' => Preview\SVG::class], - 'TIFF' => ['mimetype' => '/image\/tiff/', 'class' => Preview\TIFF::class], - 'PDF' => ['mimetype' => '/application\/pdf/', 'class' => Preview\PDF::class], - 'AI' => ['mimetype' => '/application\/illustrator/', 'class' => Preview\Illustrator::class], - 'PSD' => ['mimetype' => '/application\/x-photoshop/', 'class' => Preview\Photoshop::class], - 'EPS' => ['mimetype' => '/application\/postscript/', 'class' => Preview\Postscript::class], - 'TTF' => ['mimetype' => '/application\/(?:font-sfnt|x-font$)/', 'class' => Preview\Font::class], - 'HEIC' => ['mimetype' => '/image\/hei(f|c)/', 'class' => Preview\HEIC::class], + 'SVG' => ['mimetype' => '/image\/svg\+xml/', 'class' => Preview\SVG::class], + 'TIFF' => ['mimetype' => '/image\/tiff/', 'class' => Preview\TIFF::class], + 'PDF' => ['mimetype' => '/application\/pdf/', 'class' => Preview\PDF::class], + 'AI' => ['mimetype' => '/application\/illustrator/', 'class' => Preview\Illustrator::class], + 'PSD' => ['mimetype' => '/application\/x-photoshop/', 'class' => Preview\Photoshop::class], + 'EPS' => ['mimetype' => '/application\/postscript/', 'class' => Preview\Postscript::class], + 'TTF' => ['mimetype' => '/application\/(?:font-sfnt|x-font$)/', 'class' => Preview\Font::class], + 'HEIC' => ['mimetype' => '/image\/hei(f|c)/', 'class' => Preview\HEIC::class], ]; foreach ($imagickProviders as $queryFormat => $provider) { diff --git a/lib/private/Repair.php b/lib/private/Repair.php index 60609de4170..80a8a8a87c1 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -14,7 +14,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -34,20 +34,31 @@ namespace OC; +use OC\App\AppStore\Bundles\BundleFetcher; use OC\Avatar\AvatarManager; +use OC\Repair\AddBruteForceCleanupJob; use OC\Repair\AddCleanupUpdaterBackupsJob; use OC\Repair\CleanTags; use OC\Repair\ClearFrontendCaches; use OC\Repair\ClearGeneratedAvatarCache; use OC\Repair\Collation; use OC\Repair\MoveUpdaterStepFile; +use OC\Repair\Owncloud\CleanPreviews; use OC\Repair\NC11\FixMountStorages; +use OC\Repair\Owncloud\MoveAvatars; +use OC\Repair\Owncloud\InstallCoreBundle; +use OC\Repair\Owncloud\UpdateLanguageCodes; use OC\Repair\NC13\AddLogRotateJob; use OC\Repair\NC14\AddPreviewBackgroundCleanupJob; use OC\Repair\NC16\AddClenupLoginFlowV2BackgroundJob; use OC\Repair\NC16\CleanupCardDAVPhotoCache; use OC\Repair\NC16\ClearCollectionsAccessCache; use OC\Repair\NC18\ResetGeneratedAvatarFlag; +use OC\Repair\NC20\EncryptionLegacyCipher; +use OC\Repair\NC20\EncryptionMigration; +use OC\Repair\NC20\ShippedDashboardEnable; +use OC\Repair\NC21\AddCheckForUserCertificatesJob; +use OC\Repair\NC21\ValidatePhoneNumber; use OC\Repair\OldGroupMembershipShares; use OC\Repair\Owncloud\DropAccountTermsTable; use OC\Repair\Owncloud\SaveAccountsTableData; @@ -84,7 +95,7 @@ class Repair implements IOutput { */ public function __construct(array $repairSteps, EventDispatcherInterface $dispatcher) { $this->repairSteps = $repairSteps; - $this->dispatcher = $dispatcher; + $this->dispatcher = $dispatcher; } /** @@ -141,11 +152,26 @@ class Repair implements IOutput { public static function getRepairSteps() { return [ new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), \OC::$server->getDatabaseConnection(), false), - new RepairMimeTypes(\OC::$server->getConfig()), + new RepairMimeTypes(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()), new CleanTags(\OC::$server->getDatabaseConnection(), \OC::$server->getUserManager()), new RepairInvalidShares(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()), new MoveUpdaterStepFile(\OC::$server->getConfig()), + new MoveAvatars( + \OC::$server->getJobList(), + \OC::$server->getConfig() + ), + new CleanPreviews( + \OC::$server->getJobList(), + \OC::$server->getUserManager(), + \OC::$server->getConfig() + ), new FixMountStorages(\OC::$server->getDatabaseConnection()), + new UpdateLanguageCodes(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig()), + new InstallCoreBundle( + \OC::$server->query(BundleFetcher::class), + \OC::$server->getConfig(), + \OC::$server->query(Installer::class) + ), new AddLogRotateJob(\OC::$server->getJobList()), new ClearFrontendCaches(\OC::$server->getMemCacheFactory(), \OC::$server->query(SCSSCacher::class), \OC::$server->query(JSCombiner::class)), new ClearGeneratedAvatarCache(\OC::$server->getConfig(), \OC::$server->query(AvatarManager::class)), @@ -156,6 +182,11 @@ class Repair implements IOutput { new RemoveLinkShares(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig(), \OC::$server->getGroupManager(), \OC::$server->getNotificationManager(), \OC::$server->query(ITimeFactory::class)), new ClearCollectionsAccessCache(\OC::$server->getConfig(), \OC::$server->query(IManager::class)), \OC::$server->query(ResetGeneratedAvatarFlag::class), + \OC::$server->query(EncryptionLegacyCipher::class), + \OC::$server->query(EncryptionMigration::class), + \OC::$server->get(ShippedDashboardEnable::class), + \OC::$server->get(AddBruteForceCleanupJob::class), + \OC::$server->get(AddCheckForUserCertificatesJob::class), ]; } @@ -167,7 +198,8 @@ class Repair implements IOutput { */ public static function getExpensiveRepairSteps() { return [ - new OldGroupMembershipShares(\OC::$server->getDatabaseConnection(), \OC::$server->getGroupManager()) + new OldGroupMembershipShares(\OC::$server->getDatabaseConnection(), \OC::$server->getGroupManager()), + \OC::$server->get(ValidatePhoneNumber::class), ]; } @@ -179,8 +211,8 @@ class Repair implements IOutput { */ public static function getBeforeUpgradeRepairSteps() { $connection = \OC::$server->getDatabaseConnection(); - $config = \OC::$server->getConfig(); - $steps = [ + $config = \OC::$server->getConfig(); + $steps = [ new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), $connection, true), new SqliteAutoincrement($connection), new SaveAccountsTableData($connection, $config), diff --git a/lib/private/Repair/AddBruteForceCleanupJob.php b/lib/private/Repair/AddBruteForceCleanupJob.php new file mode 100644 index 00000000000..b287b4baa2f --- /dev/null +++ b/lib/private/Repair/AddBruteForceCleanupJob.php @@ -0,0 +1,50 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Repair; + +use OC\Security\Bruteforce\CleanupJob; +use OCP\BackgroundJob\IJobList; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class AddBruteForceCleanupJob implements IRepairStep { + + /** @var IJobList */ + protected $jobList; + + public function __construct(IJobList $jobList) { + $this->jobList = $jobList; + } + + public function getName() { + return 'Add job to cleanup the bruteforce entries'; + } + + public function run(IOutput $output) { + $this->jobList->add(CleanupJob::class); + } +} diff --git a/lib/private/Repair/CleanTags.php b/lib/private/Repair/CleanTags.php index 959eb50e5a7..5b8e44f37b6 100644 --- a/lib/private/Repair/CleanTags.php +++ b/lib/private/Repair/CleanTags.php @@ -4,7 +4,6 @@ * * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> @@ -171,7 +170,6 @@ class CleanTags implements IRepairStep { * @param string $sourceId * @param string $sourceNullColumn If this column is null in the source table, * the entry is deleted in the $deleteTable - * @suppress SqlInjectionChecker */ protected function deleteOrphanEntries(IOutput $output, $repairInfo, $deleteTable, $deleteId, $sourceTable, $sourceId, $sourceNullColumn) { $qb = $this->connection->getQueryBuilder(); diff --git a/lib/private/Repair/ClearFrontendCaches.php b/lib/private/Repair/ClearFrontendCaches.php index 4f48d9623b1..523a1fc6be5 100644 --- a/lib/private/Repair/ClearFrontendCaches.php +++ b/lib/private/Repair/ClearFrontendCaches.php @@ -2,7 +2,7 @@ /** * @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net> * - * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> + * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Julius Härtl <jus@bitgrid.net> * @author Roeland Jago Douma <roeland@famdouma.nl> * @@ -46,8 +46,8 @@ class ClearFrontendCaches implements IRepairStep { SCSSCacher $SCSSCacher, JSCombiner $JSCombiner) { $this->cacheFactory = $cacheFactory; - $this->scssCacher = $SCSSCacher; - $this->jsCombiner = $JSCombiner; + $this->scssCacher = $SCSSCacher; + $this->jsCombiner = $JSCombiner; } public function getName() { diff --git a/lib/private/Repair/ClearGeneratedAvatarCache.php b/lib/private/Repair/ClearGeneratedAvatarCache.php index 44a390d66a1..26d436451bf 100644 --- a/lib/private/Repair/ClearGeneratedAvatarCache.php +++ b/lib/private/Repair/ClearGeneratedAvatarCache.php @@ -2,6 +2,7 @@ /** * @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> * + * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> * @author Michael Weimann <mail@michael-weimann.eu> * @@ -38,7 +39,7 @@ class ClearGeneratedAvatarCache implements IRepairStep { private $config; public function __construct(IConfig $config, AvatarManager $avatarManager) { - $this->config = $config; + $this->config = $config; $this->avatarManager = $avatarManager; } diff --git a/lib/private/Repair/NC20/EncryptionLegacyCipher.php b/lib/private/Repair/NC20/EncryptionLegacyCipher.php new file mode 100644 index 00000000000..e60a2435349 --- /dev/null +++ b/lib/private/Repair/NC20/EncryptionLegacyCipher.php @@ -0,0 +1,69 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Julius Härtl <jus@bitgrid.net> + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Repair\NC20; + +use OCP\Encryption\IManager; +use OCP\IConfig; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class EncryptionLegacyCipher implements IRepairStep { + + /** @var IConfig */ + private $config; + /** @var IManager */ + private $manager; + + public function __construct(IConfig $config, + IManager $manager) { + $this->config = $config; + $this->manager = $manager; + } + + public function getName(): string { + return 'Keep legacy encryption enabled'; + } + + private function shouldRun(): bool { + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0.0'); + return version_compare($versionFromBeforeUpdate, '20.0.0.0', '<='); + } + + public function run(IOutput $output): void { + if (!$this->shouldRun()) { + return; + } + + $masterKeyId = $this->config->getAppValue('encryption', 'masterKeyId'); + if ($this->manager->isEnabled() || !empty($masterKeyId)) { + if ($this->config->getSystemValue('encryption.legacy_format_support', '') === '') { + $this->config->setSystemValue('encryption.legacy_format_support', true); + } + } + } +} diff --git a/lib/private/Repair/NC20/EncryptionMigration.php b/lib/private/Repair/NC20/EncryptionMigration.php new file mode 100644 index 00000000000..45789d2317b --- /dev/null +++ b/lib/private/Repair/NC20/EncryptionMigration.php @@ -0,0 +1,69 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Julius Härtl <jus@bitgrid.net> + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Repair\NC20; + +use OCP\Encryption\IManager; +use OCP\IConfig; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class EncryptionMigration implements IRepairStep { + + /** @var IConfig */ + private $config; + /** @var IManager */ + private $manager; + + public function __construct(IConfig $config, + IManager $manager) { + $this->config = $config; + $this->manager = $manager; + } + + public function getName(): string { + return 'Check encryption key format'; + } + + private function shouldRun(): bool { + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0.0'); + return version_compare($versionFromBeforeUpdate, '20.0.0.1', '<='); + } + + public function run(IOutput $output): void { + if (!$this->shouldRun()) { + return; + } + + $masterKeyId = $this->config->getAppValue('encryption', 'masterKeyId'); + if ($this->manager->isEnabled() || !empty($masterKeyId)) { + if ($this->config->getSystemValue('encryption.key_storage_migrated', '') === '') { + $this->config->setSystemValue('encryption.key_storage_migrated', false); + } + } + } +} diff --git a/lib/private/Repair/NC20/ShippedDashboardEnable.php b/lib/private/Repair/NC20/ShippedDashboardEnable.php new file mode 100644 index 00000000000..b8900373b2a --- /dev/null +++ b/lib/private/Repair/NC20/ShippedDashboardEnable.php @@ -0,0 +1,53 @@ +<?php +/** + * @copyright Copyright (c) 2020 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/>. + * + */ + +declare(strict_types=1); + + +namespace OC\Repair\NC20; + +use OCP\IConfig; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class ShippedDashboardEnable implements IRepairStep { + + /** @var IConfig */ + private $config; + + public function __construct(IConfig $config) { + $this->config = $config; + } + + public function getName() { + return 'Remove old dashboard app config data'; + } + + public function run(IOutput $output) { + $version = $this->config->getAppValue('dashboard', 'version', '7.0.0'); + if (version_compare($version, '7.0.0', '<')) { + $this->config->deleteAppValues('dashboard'); + $output->info('Removed old dashboard app config'); + } + } +} diff --git a/lib/private/Repair/NC21/AddCheckForUserCertificatesJob.php b/lib/private/Repair/NC21/AddCheckForUserCertificatesJob.php new file mode 100644 index 00000000000..df6637e3948 --- /dev/null +++ b/lib/private/Repair/NC21/AddCheckForUserCertificatesJob.php @@ -0,0 +1,61 @@ +<?php +/** + * @copyright Copyright (c) 2020 Morris Jobke <hey@morrisjobke.de> + * + * @author Morris Jobke <hey@morrisjobke.de> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Repair\NC21; + +use OC\Core\BackgroundJobs\CheckForUserCertificates; +use OCP\BackgroundJob\IJobList; +use OCP\IConfig; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class AddCheckForUserCertificatesJob implements IRepairStep { + + /** @var IJobList */ + protected $jobList; + /** @var IConfig */ + private $config; + + public function __construct(IConfig $config, IJobList $jobList) { + $this->jobList = $jobList; + $this->config = $config; + } + + public function getName() { + return 'Queue a one-time job to check for user uploaded certificates'; + } + + private function shouldRun() { + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0.0'); + + // was added to 21.0.0.2 + return version_compare($versionFromBeforeUpdate, '21.0.0.2', '<'); + } + + public function run(IOutput $output) { + if ($this->shouldRun()) { + $this->config->setAppValue('files_external', 'user_certificate_scan', 'not-run-yet'); + $this->jobList->add(CheckForUserCertificates::class); + } + } +} diff --git a/lib/private/Repair/NC21/ValidatePhoneNumber.php b/lib/private/Repair/NC21/ValidatePhoneNumber.php new file mode 100644 index 00000000000..995c38602f3 --- /dev/null +++ b/lib/private/Repair/NC21/ValidatePhoneNumber.php @@ -0,0 +1,90 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020 Joas Schilling <coding@schilljs.com> + * + * @author Joas Schilling <coding@schilljs.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Repair\NC21; + +use OC\Accounts\AccountManager; +use OCP\Accounts\IAccountManager; +use OCP\IConfig; +use OCP\IUser; +use OCP\IUserManager; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class ValidatePhoneNumber implements IRepairStep { + + /** @var IConfig */ + protected $config; + /** @var IUserManager */ + protected $userManager; + /** @var AccountManager */ + private $accountManager; + + public function __construct(IUserManager $userManager, + AccountManager $accountManager, + IConfig $config) { + $this->config = $config; + $this->userManager = $userManager; + $this->accountManager = $accountManager; + } + + public function getName(): string { + return 'Validate the phone number and store it in a known format for search'; + } + + private function shouldRun(): bool { + return true; + } + + 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'); + } + + $numUpdated = 0; + $numRemoved = 0; + + $this->userManager->callForSeenUsers(function (IUser $user) use (&$numUpdated, &$numRemoved) { + $account = $this->accountManager->getUser($user); + + if ($account[IAccountManager::PROPERTY_PHONE]['value'] !== '') { + $updated = $this->accountManager->updateUser($user, $account); + + if ($account[IAccountManager::PROPERTY_PHONE]['value'] !== $updated[IAccountManager::PROPERTY_PHONE]['value']) { + if ($updated[IAccountManager::PROPERTY_PHONE]['value'] === '') { + $numRemoved++; + } else { + $numUpdated++; + } + } + } + }); + + if ($numRemoved > 0 || $numUpdated > 0) { + $output->info('Updated ' . $numUpdated . ' entries and cleaned ' . $numRemoved . ' invalid phone numbers'); + } + } +} diff --git a/lib/private/Repair/OldGroupMembershipShares.php b/lib/private/Repair/OldGroupMembershipShares.php index 2b2fd9cd470..d991ef82ac9 100644 --- a/lib/private/Repair/OldGroupMembershipShares.php +++ b/lib/private/Repair/OldGroupMembershipShares.php @@ -3,7 +3,6 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 @@ -66,7 +65,6 @@ class OldGroupMembershipShares implements IRepairStep { * Must throw exception on error. * * @throws \Exception in case of failure - * @suppress SqlInjectionChecker */ public function run(IOutput $output) { $deletedEntries = 0; diff --git a/lib/private/Repair/Owncloud/CleanPreviews.php b/lib/private/Repair/Owncloud/CleanPreviews.php new file mode 100644 index 00000000000..267a1be7985 --- /dev/null +++ b/lib/private/Repair/Owncloud/CleanPreviews.php @@ -0,0 +1,73 @@ +<?php +/** + * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl> + * + * @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\Repair\Owncloud; + +use OCP\BackgroundJob\IJobList; +use OCP\IConfig; +use OCP\IUser; +use OCP\IUserManager; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class CleanPreviews implements IRepairStep { + + /** @var IJobList */ + private $jobList; + + /** @var IUserManager */ + private $userManager; + + /** @var IConfig */ + private $config; + + /** + * MoveAvatars constructor. + * + * @param IJobList $jobList + * @param IUserManager $userManager + * @param IConfig $config + */ + public function __construct(IJobList $jobList, + IUserManager $userManager, + IConfig $config) { + $this->jobList = $jobList; + $this->userManager = $userManager; + $this->config = $config; + } + + /** + * @return string + */ + public function getName() { + return 'Add preview cleanup background jobs'; + } + + public function run(IOutput $output) { + if (!$this->config->getAppValue('core', 'previewsCleanedUp', false)) { + $this->userManager->callForSeenUsers(function (IUser $user) { + $this->jobList->add(CleanPreviewsBackgroundJob::class, ['uid' => $user->getUID()]); + }); + $this->config->setAppValue('core', 'previewsCleanedUp', '1'); + } + } +} diff --git a/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php b/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php new file mode 100644 index 00000000000..90aa59b2950 --- /dev/null +++ b/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php @@ -0,0 +1,132 @@ +<?php +/** + * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl> + * + * @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\Repair\Owncloud; + +use OC\BackgroundJob\QueuedJob; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\IJobList; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\ILogger; +use OCP\IUserManager; + +class CleanPreviewsBackgroundJob extends QueuedJob { + /** @var IRootFolder */ + private $rootFolder; + + /** @var ILogger */ + private $logger; + + /** @var IJobList */ + private $jobList; + + /** @var ITimeFactory */ + private $timeFactory; + + /** @var IUserManager */ + private $userManager; + + /** + * CleanPreviewsBackgroundJob constructor. + * + * @param IRootFolder $rootFolder + * @param ILogger $logger + * @param IJobList $jobList + * @param ITimeFactory $timeFactory + * @param IUserManager $userManager + */ + public function __construct(IRootFolder $rootFolder, + ILogger $logger, + IJobList $jobList, + ITimeFactory $timeFactory, + IUserManager $userManager) { + $this->rootFolder = $rootFolder; + $this->logger = $logger; + $this->jobList = $jobList; + $this->timeFactory = $timeFactory; + $this->userManager = $userManager; + } + + public function run($arguments) { + $uid = $arguments['uid']; + if (!$this->userManager->userExists($uid)) { + $this->logger->info('User no longer exists, skip user ' . $uid); + return; + } + $this->logger->info('Started preview cleanup for ' . $uid); + $empty = $this->cleanupPreviews($uid); + + if (!$empty) { + $this->jobList->add(self::class, ['uid' => $uid]); + $this->logger->info('New preview cleanup scheduled for ' . $uid); + } else { + $this->logger->info('Preview cleanup done for ' . $uid); + } + } + + /** + * @param $uid + * @return bool + */ + private function cleanupPreviews($uid) { + try { + $userFolder = $this->rootFolder->getUserFolder($uid); + } catch (NotFoundException $e) { + return true; + } + + $userRoot = $userFolder->getParent(); + + try { + /** @var Folder $thumbnailFolder */ + $thumbnailFolder = $userRoot->get('thumbnails'); + } catch (NotFoundException $e) { + return true; + } + + $thumbnails = $thumbnailFolder->getDirectoryListing(); + + $start = $this->timeFactory->getTime(); + foreach ($thumbnails as $thumbnail) { + try { + $thumbnail->delete(); + } catch (NotPermittedException $e) { + // Ignore + } + + if (($this->timeFactory->getTime() - $start) > 15) { + return false; + } + } + + try { + $thumbnailFolder->delete(); + } catch (NotPermittedException $e) { + // Ignore + } + + return true; + } +} diff --git a/lib/private/Repair/Owncloud/InstallCoreBundle.php b/lib/private/Repair/Owncloud/InstallCoreBundle.php new file mode 100644 index 00000000000..10d5357e5d8 --- /dev/null +++ b/lib/private/Repair/Owncloud/InstallCoreBundle.php @@ -0,0 +1,80 @@ +<?php +/** + * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> + * + * @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\Repair\Owncloud; + +use OC\App\AppStore\Bundles\BundleFetcher; +use OC\Installer; +use OCP\IConfig; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class InstallCoreBundle implements IRepairStep { + /** @var BundleFetcher */ + private $bundleFetcher; + /** @var IConfig */ + private $config; + /** @var Installer */ + private $installer; + + /** + * @param BundleFetcher $bundleFetcher + * @param IConfig $config + * @param Installer $installer + */ + public function __construct(BundleFetcher $bundleFetcher, + IConfig $config, + Installer $installer) { + $this->bundleFetcher = $bundleFetcher; + $this->config = $config; + $this->installer = $installer; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'Install new core bundle components'; + } + + /** + * {@inheritdoc} + */ + public function run(IOutput $output) { + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0'); + + if (version_compare($versionFromBeforeUpdate, '12.0.0.14', '>')) { + return; + } + + $defaultBundle = $this->bundleFetcher->getDefaultInstallationBundle(); + foreach ($defaultBundle as $bundle) { + try { + $this->installer->installAppBundle($bundle); + $output->info('Successfully installed core app bundle.'); + } catch (\Exception $e) { + $output->warning('Could not install core app bundle: ' . $e->getMessage()); + } + } + } +} diff --git a/lib/private/Repair/Owncloud/MoveAvatars.php b/lib/private/Repair/Owncloud/MoveAvatars.php new file mode 100644 index 00000000000..37c79de22ab --- /dev/null +++ b/lib/private/Repair/Owncloud/MoveAvatars.php @@ -0,0 +1,72 @@ +<?php +/** + * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl> + * + * @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\Repair\Owncloud; + +use OCP\BackgroundJob\IJobList; +use OCP\IConfig; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class MoveAvatars implements IRepairStep { + + /** @var IJobList */ + private $jobList; + + /** @var IConfig */ + private $config; + + /** + * MoveAvatars constructor. + * + * @param IJobList $jobList + * @param IConfig $config + */ + public function __construct(IJobList $jobList, + IConfig $config) { + $this->jobList = $jobList; + $this->config = $config; + } + + /** + * @return string + */ + public function getName() { + return 'Add move avatar background job'; + } + + public function run(IOutput $output) { + // only run once + if ($this->config->getAppValue('core', 'moveavatarsdone') === 'yes') { + $output->info('Repair step already executed'); + return; + } + if ($this->config->getSystemValue('enable_avatars', true) === false) { + $output->info('Avatars are disabled'); + } else { + $output->info('Add background job'); + $this->jobList->add(MoveAvatarsBackgroundJob::class); + // if all were done, no need to redo the repair during next upgrade + $this->config->setAppValue('core', 'moveavatarsdone', 'yes'); + } + } +} diff --git a/lib/private/Repair/Owncloud/MoveAvatarsBackgroundJob.php b/lib/private/Repair/Owncloud/MoveAvatarsBackgroundJob.php new file mode 100644 index 00000000000..e69beae7d6e --- /dev/null +++ b/lib/private/Repair/Owncloud/MoveAvatarsBackgroundJob.php @@ -0,0 +1,114 @@ +<?php +/** + * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl> + * + * @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\Repair\Owncloud; + +use OC\BackgroundJob\QueuedJob; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\Files\Storage; +use OCP\IAvatarManager; +use OCP\IUser; +use OCP\IUserManager; +use Psr\Log\LoggerInterface; + +class MoveAvatarsBackgroundJob extends QueuedJob { + + /** @var IUserManager */ + private $userManager; + + /** @var LoggerInterface */ + private $logger; + + /** @var IAvatarManager */ + private $avatarManager; + + /** @var Storage */ + private $owncloudAvatarStorage; + + public function __construct(IUserManager $userManager, LoggerInterface $logger, IAvatarManager $avatarManager, IRootFolder $rootFolder) { + $this->userManager = $userManager; + $this->logger = $logger; + $this->avatarManager = $avatarManager; + try { + $this->owncloudAvatarStorage = $rootFolder->get('avatars')->getStorage(); + } catch (\Exception $e) { + } + } + + public function run($arguments) { + $this->logger->info('Started migrating avatars to AppData folder'); + $this->moveAvatars(); + $this->logger->info('All avatars migrated to AppData folder'); + } + + private function moveAvatars(): void { + if (!$this->owncloudAvatarStorage) { + $this->logger->info('No legacy avatars available, skipping migration'); + return; + } + + $counter = 0; + $this->userManager->callForSeenUsers(function (IUser $user) use ($counter) { + $uid = $user->getUID(); + + $path = 'avatars/' . $this->buildOwnCloudAvatarPath($uid); + $avatar = $this->avatarManager->getAvatar($uid); + try { + $avatarPath = $path . '/avatar.' . $this->getExtension($path); + $resource = $this->owncloudAvatarStorage->fopen($avatarPath, 'r'); + if ($resource) { + $avatar->set($resource); + fclose($resource); + } else { + throw new \Exception('Failed to open old avatar file for reading'); + } + } catch (NotFoundException $e) { + // In case there is no avatar we can just skip + } catch (\Throwable $e) { + $this->logger->error('Failed to migrate avatar for user ' . $uid, ['exception' => $e]); + } + + $counter++; + if ($counter % 100 === 0) { + $this->logger->info('{amount} avatars migrated', ['amount' => $counter]); + } + }); + } + + /** + * @throws NotFoundException + */ + private function getExtension(string $path): string { + if ($this->owncloudAvatarStorage->file_exists("{$path}/avatar.jpg")) { + return 'jpg'; + } + if ($this->owncloudAvatarStorage->file_exists("{$path}/avatar.png")) { + return 'png'; + } + throw new NotFoundException("{$path}/avatar.jpg|png"); + } + + protected function buildOwnCloudAvatarPath(string $userId): string { + return substr_replace(substr_replace(md5($userId), '/', 4, 0), '/', 2, 0); + } +} diff --git a/lib/private/Repair/Owncloud/SaveAccountsTableData.php b/lib/private/Repair/Owncloud/SaveAccountsTableData.php index 6ca46934d71..f11397d7ae5 100644 --- a/lib/private/Repair/Owncloud/SaveAccountsTableData.php +++ b/lib/private/Repair/Owncloud/SaveAccountsTableData.php @@ -4,6 +4,7 @@ * * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Joas Schilling <coding@schilljs.com> + * @author Julius Härtl <jus@bitgrid.net> * * @license GNU AGPL version 3 or any later version * @@ -78,6 +79,15 @@ class SaveAccountsTableData implements IRepairStep { $numUsers = $this->runStep($offset); } + // oc_persistent_locks will be removed later on anyways so we can just drop and ignore any foreign key constraints here + $tableName = $this->config->getSystemValue('dbtableprefix', 'oc_') . 'persistent_locks'; + $schema = $this->db->createSchema(); + $table = $schema->getTable($tableName); + foreach ($table->getForeignKeys() as $foreignKey) { + $table->removeForeignKey($foreignKey->getName()); + } + $this->db->migrateToSchema($schema); + // Remove the table if ($this->hasForeignKeyOnPersistentLocks) { $this->db->dropTable('persistent_locks'); diff --git a/lib/private/Repair/Owncloud/UpdateLanguageCodes.php b/lib/private/Repair/Owncloud/UpdateLanguageCodes.php new file mode 100644 index 00000000000..cd923b4e802 --- /dev/null +++ b/lib/private/Repair/Owncloud/UpdateLanguageCodes.php @@ -0,0 +1,89 @@ +<?php +/** + * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de> + * + * @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\Repair\Owncloud; + +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class UpdateLanguageCodes implements IRepairStep { + /** @var IDBConnection */ + private $connection; + + /** @var IConfig */ + private $config; + + /** + * @param IDBConnection $connection + * @param IConfig $config + */ + public function __construct(IDBConnection $connection, + IConfig $config) { + $this->connection = $connection; + $this->config = $config; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'Repair language codes'; + } + + /** + * {@inheritdoc} + */ + public function run(IOutput $output) { + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0'); + + if (version_compare($versionFromBeforeUpdate, '12.0.0.13', '>')) { + return; + } + + $languages = [ + 'bg_BG' => 'bg', + 'cs_CZ' => 'cs', + 'fi_FI' => 'fi', + 'hu_HU' => 'hu', + 'nb_NO' => 'nb', + 'sk_SK' => 'sk', + 'th_TH' => 'th', + ]; + + foreach ($languages as $oldCode => $newCode) { + $qb = $this->connection->getQueryBuilder(); + + $affectedRows = $qb->update('preferences') + ->set('configvalue', $qb->createNamedParameter($newCode)) + ->where($qb->expr()->eq('appid', $qb->createNamedParameter('core'))) + ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter('lang'))) + ->andWhere($qb->expr()->eq('configvalue', $qb->createNamedParameter($oldCode), IQueryBuilder::PARAM_STR)) + ->execute(); + + $output->info('Changed ' . $affectedRows . ' setting(s) from "' . $oldCode . '" to "' . $newCode . '" in preferences table.'); + } + } +} diff --git a/lib/private/Repair/RepairInvalidShares.php b/lib/private/Repair/RepairInvalidShares.php index 27defdeda01..bc03374ff98 100644 --- a/lib/private/Repair/RepairInvalidShares.php +++ b/lib/private/Repair/RepairInvalidShares.php @@ -6,7 +6,7 @@ * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -56,7 +56,6 @@ class RepairInvalidShares implements IRepairStep { /** * Adjust file share permissions - * @suppress SqlInjectionChecker */ private function adjustFileSharePermissions(IOutput $out) { $mask = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_SHARE; diff --git a/lib/private/Repair/RepairMimeTypes.php b/lib/private/Repair/RepairMimeTypes.php index b6b6ceed104..3e6fa51b196 100644 --- a/lib/private/Repair/RepairMimeTypes.php +++ b/lib/private/Repair/RepairMimeTypes.php @@ -5,15 +5,16 @@ * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Joas Schilling <coding@schilljs.com> + * @author Julius Härtl <jus@bitgrid.net> * @author Morris Jobke <hey@morrisjobke.de> + * @author nik gaffney <nik@fo.am> * @author Olivier Paroz <github@oparoz.com> * @author Rello <Rello@users.noreply.github.com> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Stefan Weil <sw@weilnetz.de> * @author Thomas Ebert <thomas.ebert@usability.de> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Victor Dubiniuk <dubiniuk@owncloud.com> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -33,84 +34,74 @@ namespace OC\Repair; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IConfig; +use OCP\IDBConnection; use OCP\Migration\IOutput; use OCP\Migration\IRepairStep; class RepairMimeTypes implements IRepairStep { - /** - * @var \OCP\IConfig - */ + /** @var IConfig */ protected $config; + /** @var IDBConnection */ + protected $connection; - /** - * @var int - */ + /** @var int */ protected $folderMimeTypeId; - /** - * @param \OCP\IConfig $config - */ - public function __construct($config) { + public function __construct(IConfig $config, + IDBConnection $connection) { $this->config = $config; + $this->connection = $connection; } public function getName() { return 'Repair mime types'; } - private static function existsStmt() { - return \OC_DB::prepare(' - SELECT count(`mimetype`) - FROM `*PREFIX*mimetypes` - WHERE `mimetype` = ? - '); - } - - private static function getIdStmt() { - return \OC_DB::prepare(' - SELECT `id` - FROM `*PREFIX*mimetypes` - WHERE `mimetype` = ? - '); - } - - private static function insertStmt() { - return \OC_DB::prepare(' - INSERT INTO `*PREFIX*mimetypes` ( `mimetype` ) - VALUES ( ? ) - '); - } - - private static function updateByNameStmt() { - return \OC_DB::prepare(' - UPDATE `*PREFIX*filecache` - SET `mimetype` = ? - WHERE `mimetype` <> ? AND `mimetype` <> ? AND `name` ILIKE ? - '); - } - private function updateMimetypes($updatedMimetypes) { + $query = $this->connection->getQueryBuilder(); + $query->select('id') + ->from('mimetypes') + ->where($query->expr()->eq('mimetype', $query->createParameter('mimetype'), IQueryBuilder::PARAM_INT)); + $insert = $this->connection->getQueryBuilder(); + $insert->insert('mimetypes') + ->setValue('mimetype', $insert->createParameter('mimetype')); + if (empty($this->folderMimeTypeId)) { - $result = \OC_DB::executeAudited(self::getIdStmt(), ['httpd/unix-directory']); - $this->folderMimeTypeId = (int)$result->fetchOne(); + $query->setParameter('mimetype', 'httpd/unix-directory'); + $result = $query->execute(); + $this->folderMimeTypeId = (int)$result->fetchColumn(); + $result->closeCursor(); } + $update = $this->connection->getQueryBuilder(); + $update->update('filecache') + ->set('mimetype', $update->createParameter('mimetype')) + ->where($update->expr()->neq('mimetype', $update->createParameter('mimetype'), IQueryBuilder::PARAM_INT)) + ->andWhere($update->expr()->neq('mimetype', $update->createParameter('folder'), IQueryBuilder::PARAM_INT)) + ->andWhere($update->expr()->iLike('name', $update->createParameter('name'))) + ->setParameter('folder', $this->folderMimeTypeId); + $count = 0; foreach ($updatedMimetypes as $extension => $mimetype) { - $result = \OC_DB::executeAudited(self::existsStmt(), [$mimetype]); - $exists = $result->fetchOne(); + // get target mimetype id + $query->setParameter('mimetype', $mimetype); + $result = $query->execute(); + $mimetypeId = (int)$result->fetchColumn(); + $result->closeCursor(); - if (!$exists) { + if (!$mimetypeId) { // insert mimetype - \OC_DB::executeAudited(self::insertStmt(), [$mimetype]); + $insert->setParameter('mimetype', $mimetype); + $insert->execute(); + $mimetypeId = $insert->getLastInsertId(); } - // get target mimetype id - $result = \OC_DB::executeAudited(self::getIdStmt(), [$mimetype]); - $mimetypeId = $result->fetchOne(); - // change mimetype for files with x extension - $count += \OC_DB::executeAudited(self::updateByNameStmt(), [$mimetypeId, $this->folderMimeTypeId, $mimetypeId, '%.' . $extension]); + $update->setParameter('mimetype', $mimetypeId) + ->setParameter('name', '%' . $this->connection->escapeLikeParameter('.' . $extension)); + $count += $update->execute(); } return $count; @@ -191,6 +182,26 @@ class RepairMimeTypes implements IRepairStep { return $this->updateMimetypes($updatedMimetypes); } + private function introduceOpenDocumentTemplates() { + $updatedMimetypes = [ + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + ]; + + return $this->updateMimetypes($updatedMimetypes); + } + + private function introduceOrgModeType() { + $updatedMimetypes = [ + 'org' => 'text/org' + ]; + + return $this->updateMimetypes($updatedMimetypes); + } + + /** * Fix mime types */ @@ -227,5 +238,13 @@ class RepairMimeTypes implements IRepairStep { if (version_compare($ocVersionFromBeforeUpdate, '14.0.0.10', '<') && $this->introduceComicbookTypes()) { $out->info('Fixed comicbook mime types'); } + + if (version_compare($ocVersionFromBeforeUpdate, '20.0.0.5', '<') && $this->introduceOpenDocumentTemplates()) { + $out->info('Fixed OpenDocument template mime types'); + } + + if (version_compare($ocVersionFromBeforeUpdate, '21.0.0.7', '<') && $this->introduceOrgModeType()) { + $out->info('Fixed orgmode mime types'); + } } } diff --git a/lib/private/Repair/SqliteAutoincrement.php b/lib/private/Repair/SqliteAutoincrement.php index 4bdd7c729b4..651a3c144f3 100644 --- a/lib/private/Repair/SqliteAutoincrement.php +++ b/lib/private/Repair/SqliteAutoincrement.php @@ -4,7 +4,7 @@ * * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/RepairException.php b/lib/private/RepairException.php index 002ec36fb94..c06fcc91f6e 100644 --- a/lib/private/RepairException.php +++ b/lib/private/RepairException.php @@ -3,7 +3,7 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Morris Jobke <hey@morrisjobke.de> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/Route/Route.php b/lib/private/Route/Route.php index 705183d24ae..8e419344881 100644 --- a/lib/private/Route/Route.php +++ b/lib/private/Route/Route.php @@ -151,7 +151,7 @@ class Route extends SymfonyRoute implements IRoute { public function actionInclude($file) { $function = function ($param) use ($file) { unset($param["_route"]); - $_GET=array_merge($_GET, $param); + $_GET = array_merge($_GET, $param); unset($param); require_once "$file"; } ; diff --git a/lib/private/Route/Router.php b/lib/private/Route/Router.php index de7c720f271..71bc4a6c4f7 100644 --- a/lib/private/Route/Router.php +++ b/lib/private/Route/Router.php @@ -13,7 +13,7 @@ * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -33,6 +33,7 @@ namespace OC\Route; +use OC\AppFramework\Routing\RouteParser; use OCP\AppFramework\App; use OCP\ILogger; use OCP\Route\IRouter; @@ -73,7 +74,7 @@ class Router implements IRouter { $this->logger = $logger; $baseUrl = \OC::$WEBROOT; if (!(\OC::$server->getConfig()->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true')) { - $baseUrl = \OC::$server->getURLGenerator()->linkTo('', 'index.php'); + $baseUrl .= '/index.php'; } if (!\OC::$CLI && isset($_SERVER['REQUEST_METHOD'])) { $method = $_SERVER['REQUEST_METHOD']; @@ -177,14 +178,6 @@ class Router implements IRouter { } /** - * @return string - * @deprecated - */ - public function getCacheKey() { - return ''; - } - - /** * @param string $name * @return \Symfony\Component\Routing\RouteCollection */ @@ -239,9 +232,9 @@ class Router implements IRouter { * * @param string $url The url to find * @throws \Exception - * @return void + * @return array */ - public function match($url) { + public function findMatchingRoute(string $url): array { if (substr($url, 0, 6) === '/apps/') { // empty string / 'apps' / $app / rest of the route list(, , $app,) = explode('/', $url, 4); @@ -287,10 +280,24 @@ class Router implements IRouter { } } + return $parameters; + } + + /** + * Find and execute the route matching $url + * + * @param string $url The url to find + * @throws \Exception + * @return void + */ + public function match($url) { + $parameters = $this->findMatchingRoute($url); + \OC::$server->getEventLogger()->start('run_route', 'Run route'); if (isset($parameters['caller'])) { $caller = $parameters['caller']; unset($parameters['caller']); + unset($parameters['action']); $application = $this->getApplicationClass($caller[0]); \OC\AppFramework\App::main($caller[1], $caller[2], $application->getContainer(), $parameters); } elseif (isset($parameters['action'])) { @@ -299,6 +306,7 @@ class Router implements IRouter { throw new \Exception('not a callable action'); } unset($parameters['action']); + unset($parameters['caller']); call_user_func($action, $parameters); } elseif (isset($parameters['file'])) { include $parameters['file']; @@ -333,13 +341,27 @@ class Router implements IRouter { public function generate($name, $parameters = [], $absolute = false) { + $referenceType = UrlGenerator::ABSOLUTE_URL; + if ($absolute === false) { + $referenceType = UrlGenerator::ABSOLUTE_PATH; + } + $name = $this->fixLegacyRootName($name); + if (strpos($name, '.') !== false) { + list($appName, $other) = explode('.', $name, 3); + // OCS routes are prefixed with "ocs." + if ($appName === 'ocs') { + $appName = $other; + } + $this->loadRoutes($appName); + try { + return $this->getGenerator()->generate($name, $parameters, $referenceType); + } catch (RouteNotFoundException $e) { + } + } + + // Fallback load all routes $this->loadRoutes(); try { - $referenceType = UrlGenerator::ABSOLUTE_URL; - if ($absolute === false) { - $referenceType = UrlGenerator::ABSOLUTE_PATH; - } - $name = $this->fixLegacyRootName($name); return $this->getGenerator()->generate($name, $parameters, $referenceType); } catch (RouteNotFoundException $e) { $this->logger->logException($e, ['level' => ILogger::INFO]); @@ -399,8 +421,14 @@ class Router implements IRouter { */ private function setupRoutes($routes, $appName) { if (is_array($routes)) { - $application = $this->getApplicationClass($appName); - $application->registerRoutes($this, $routes); + $routeParser = new RouteParser(); + + $defaultRoutes = $routeParser->parseDefaultRoutes($routes, $appName); + $ocsRoutes = $routeParser->parseOCSRoutes($routes, $appName); + + $this->root->addCollection($defaultRoutes); + $ocsRoutes->addPrefix('/ocsapp'); + $this->root->addCollection($ocsRoutes); } } diff --git a/lib/private/Search.php b/lib/private/Search.php index ae22a6d9f19..9ecf34aef54 100644 --- a/lib/private/Search.php +++ b/lib/private/Search.php @@ -36,6 +36,7 @@ use OCP\Search\Provider; * Provide an interface to all search providers */ class Search implements ISearch { + /** @var Provider[] */ private $providers = []; private $registeredProviders = []; @@ -51,7 +52,6 @@ class Search implements ISearch { $this->initProviders(); $results = []; foreach ($this->providers as $provider) { - /** @var $provider Provider */ if (! $provider->providesResultsFor($inApps)) { continue; } diff --git a/lib/private/Search/Provider/File.php b/lib/private/Search/Provider/File.php index 9a41a46bd35..b4e35d374ca 100644 --- a/lib/private/Search/Provider/File.php +++ b/lib/private/Search/Provider/File.php @@ -6,6 +6,7 @@ * @author Bart Visscher <bartv@thisnet.nl> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Jakob Sack <mail@jakobsack.de> + * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> @@ -39,10 +40,11 @@ class File extends \OCP\Search\Provider { /** * Search for files and folders matching the given query * @param string $query - * @return \OCP\Search\Result + * @return \OCP\Search\Result[] * @deprecated 20.0.0 */ public function search($query) { + \OC_Util::setupFS(); $files = Filesystem::search($query); $results = []; // edit results diff --git a/lib/private/Search/Result/File.php b/lib/private/Search/Result/File.php index f93b033c07f..33e1e97f471 100644 --- a/lib/private/Search/Result/File.php +++ b/lib/private/Search/Result/File.php @@ -4,6 +4,7 @@ * * @author Andrew Brown <andrew@casabrown.com> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> @@ -28,6 +29,8 @@ namespace OC\Search\Result; use OCP\Files\FileInfo; use OCP\Files\Folder; +use OCP\IPreview; +use OCP\IUserSession; /** * A found file @@ -79,6 +82,14 @@ class File extends \OCP\Search\Result { public $permissions; /** + * Has a preview + * + * @var string + * @deprecated 20.0.0 + */ + public $has_preview; + + /** * Create a new file search result * @param FileInfo $data file data given by provider * @deprecated 20.0.0 @@ -101,6 +112,7 @@ class File extends \OCP\Search\Result { $this->size = $data->getSize(); $this->modified = $data->getMtime(); $this->mime_type = $data->getMimetype(); + $this->has_preview = $this->hasPreview($data); } /** @@ -118,9 +130,21 @@ class File extends \OCP\Search\Result { */ protected function getRelativePath($path) { if (!isset(self::$userFolderCache)) { - $user = \OC::$server->getUserSession()->getUser()->getUID(); - self::$userFolderCache = \OC::$server->getUserFolder($user); + $userSession = \OC::$server->get(IUserSession::class); + $userID = $userSession->getUser()->getUID(); + self::$userFolderCache = \OC::$server->getUserFolder($userID); } return self::$userFolderCache->getRelativePath($path); } + + /** + * Is the preview available + * @param FileInfo $data + * @return bool + * @deprecated 20.0.0 + */ + protected function hasPreview($data) { + $previewManager = \OC::$server->get(IPreview::class); + return $previewManager->isAvailable($data); + } } diff --git a/lib/private/Search/SearchComposer.php b/lib/private/Search/SearchComposer.php index 61bbbbc969b..5290c2ac3c1 100644 --- a/lib/private/Search/SearchComposer.php +++ b/lib/private/Search/SearchComposer.php @@ -5,7 +5,9 @@ declare(strict_types=1); /** * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * - * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> + * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> * * @license GNU AGPL version 3 or any later version * @@ -20,7 +22,8 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * */ namespace OC\Search; @@ -106,20 +109,35 @@ class SearchComposer { } /** - * Get a list of all provider IDs for the consecutive calls to `search` + * Get a list of all provider IDs & Names for the consecutive calls to `search` + * Sort the list by the order property + * + * @param string $route the route the user is currently at + * @param array $routeParameters the parameters of the route the user is currently at * - * @return string[] + * @return array */ - public function getProviders(): array { + public function getProviders(string $route, array $routeParameters): array { $this->loadLazyProviders(); + $providers = array_values( + array_map(function (IProvider $provider) use ($route, $routeParameters) { + return [ + 'id' => $provider->getId(), + 'name' => $provider->getName(), + 'order' => $provider->getOrder($route, $routeParameters), + ]; + }, $this->providers) + ); + + usort($providers, function ($provider1, $provider2) { + return $provider1['order'] <=> $provider2['order']; + }); + /** * Return an array with the IDs, but strip the associative keys */ - return array_values( - array_map(function (IProvider $provider) { - return $provider->getId(); - }, $this->providers)); + return $providers; } /** diff --git a/lib/private/Search/SearchQuery.php b/lib/private/Search/SearchQuery.php index 2ed31fed441..728802b6e80 100644 --- a/lib/private/Search/SearchQuery.php +++ b/lib/private/Search/SearchQuery.php @@ -5,7 +5,9 @@ declare(strict_types=1); /** * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * - * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> + * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> * * @license GNU AGPL version 3 or any later version * @@ -20,7 +22,8 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * */ namespace OC\Search; @@ -28,7 +31,7 @@ namespace OC\Search; use OCP\Search\ISearchQuery; class SearchQuery implements ISearchQuery { - public const LIMIT_DEFAULT = 20; + public const LIMIT_DEFAULT = 5; /** @var string */ private $term; @@ -42,20 +45,32 @@ class SearchQuery implements ISearchQuery { /** @var int|string|null */ private $cursor; + /** @var string */ + private $route; + + /** @var array */ + private $routeParameters; + /** * @param string $term * @param int $sortOrder * @param int $limit * @param int|string|null $cursor + * @param string $route + * @param array $routeParameters */ public function __construct(string $term, int $sortOrder = ISearchQuery::SORT_DATE_DESC, int $limit = self::LIMIT_DEFAULT, - $cursor = null) { + $cursor = null, + string $route = '', + array $routeParameters = []) { $this->term = $term; $this->sortOrder = $sortOrder; $this->limit = $limit; $this->cursor = $cursor; + $this->route = $route; + $this->routeParameters = $routeParameters; } /** @@ -85,4 +100,18 @@ class SearchQuery implements ISearchQuery { public function getCursor() { return $this->cursor; } + + /** + * @inheritDoc + */ + public function getRoute(): string { + return $this->route; + } + + /** + * @inheritDoc + */ + public function getRouteParameters(): array { + return $this->routeParameters; + } } diff --git a/lib/private/Security/Bruteforce/Capabilities.php b/lib/private/Security/Bruteforce/Capabilities.php index eab55db1c90..7547348ce34 100644 --- a/lib/private/Security/Bruteforce/Capabilities.php +++ b/lib/private/Security/Bruteforce/Capabilities.php @@ -2,6 +2,7 @@ /** * @copyright Copyright (c) 2017 Roeland Jago Douma <roeland@famdouma.nl> * + * @author Julius Härtl <jus@bitgrid.net> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version @@ -46,6 +47,10 @@ class Capabilities implements IPublicCapability { } public function getCapabilities() { + if (version_compare(\OC::$server->getConfig()->getSystemValue('version', '0.0.0.0'), '12.0.0.0', '<')) { + return []; + } + return [ 'bruteforce' => [ 'delay' => $this->throttler->getDelay($this->request->getRemoteAddress()) diff --git a/lib/private/Security/Bruteforce/CleanupJob.php b/lib/private/Security/Bruteforce/CleanupJob.php new file mode 100644 index 00000000000..edf59cdcdc5 --- /dev/null +++ b/lib/private/Security/Bruteforce/CleanupJob.php @@ -0,0 +1,56 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Security\Bruteforce; + +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\TimedJob; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; + +class CleanupJob extends TimedJob { + + /** @var IDBConnection */ + private $connection; + + public function __construct(ITimeFactory $time, IDBConnection $connection) { + parent::__construct($time); + $this->connection = $connection; + + // Run once a day + $this->setInterval(3600 * 24); + } + + protected function run($argument) { + // Delete all entries more than 48 hours old + $time = $this->time->getTime() - (48 * 3600); + + $qb = $this->connection->getQueryBuilder(); + $qb->delete('bruteforce_attempts') + ->where($qb->expr()->lt('occurred', $qb->createNamedParameter($time), IQueryBuilder::PARAM_INT)); + $qb->execute(); + } +} diff --git a/lib/private/Security/Bruteforce/Throttler.php b/lib/private/Security/Bruteforce/Throttler.php index 63c6361b9ce..e1d9127a7bb 100644 --- a/lib/private/Security/Bruteforce/Throttler.php +++ b/lib/private/Security/Bruteforce/Throttler.php @@ -1,11 +1,15 @@ <?php + +declare(strict_types=1); + /** * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch> * * @author Bjoern Schiessle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> + * @author Johannes Riedel <joeried@users.noreply.github.com> * @author Lukas Reschke <lukas@statuscode.ch> - * @author Mark Berezovsky <xpnf@yandex.ru> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> @@ -34,6 +38,7 @@ use OCP\AppFramework\Utility\ITimeFactory; use OCP\IConfig; use OCP\IDBConnection; use OCP\ILogger; +use OCP\Security\Bruteforce\MaxDelayReached; /** * Class Throttler implements the bruteforce protection for security actions in @@ -50,6 +55,9 @@ use OCP\ILogger; */ class Throttler { public const LOGIN_ACTION = 'login'; + public const MAX_DELAY = 25; + public const MAX_DELAY_MS = 25000; // in milliseconds + public const MAX_ATTEMPTS = 10; /** @var IDBConnection */ private $db; @@ -82,7 +90,7 @@ class Throttler { * @param int $expire * @return \DateInterval */ - private function getCutoff($expire) { + private function getCutoff(int $expire): \DateInterval { $d1 = new \DateTime(); $d2 = clone $d1; $d2->sub(new \DateInterval('PT' . $expire . 'S')); @@ -92,11 +100,12 @@ class Throttler { /** * Calculate the cut off timestamp * + * @param float $maxAgeHours * @return int */ - private function getCutoffTimestamp(): int { + private function getCutoffTimestamp(float $maxAgeHours = 12.0): int { return (new \DateTime()) - ->sub($this->getCutoff(43200)) + ->sub($this->getCutoff((int) ($maxAgeHours * 3600))) ->getTimestamp(); } @@ -106,11 +115,10 @@ class Throttler { * @param string $action * @param string $ip * @param array $metadata Optional metadata logged to the database - * @suppress SqlInjectionChecker */ - public function registerAttempt($action, - $ip, - array $metadata = []) { + public function registerAttempt(string $action, + string $ip, + array $metadata = []): void { // No need to log if the bruteforce protection is disabled if ($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) { return; @@ -150,15 +158,14 @@ class Throttler { * @param string $ip * @return bool */ - private function isIPWhitelisted($ip) { + private function isIPWhitelisted(string $ip): bool { if ($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) { return true; } $keys = $this->config->getAppKeys('bruteForce'); $keys = array_filter($keys, function ($key) { - $regex = '/^whitelist_/S'; - return preg_match($regex, $key) === 1; + return 0 === strpos($key, 'whitelist_'); }); if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { @@ -188,8 +195,8 @@ class Throttler { $valid = true; for ($i = 0; $i < $mask; $i++) { - $part = ord($addr[(int)($i/8)]); - $orig = ord($ip[(int)($i/8)]); + $part = ord($addr[(int)($i / 8)]); + $orig = ord($ip[(int)($i / 8)]); $bitmask = 1 << (7 - ($i % 8)); @@ -215,18 +222,28 @@ class Throttler { * * @param string $ip * @param string $action optionally filter by action + * @param float $maxAgeHours * @return int */ - public function getDelay($ip, $action = '') { + public function getAttempts(string $ip, string $action = '', float $maxAgeHours = 12): int { + if ($maxAgeHours > 48) { + $this->logger->error('Bruteforce has to use less than 48 hours'); + $maxAgeHours = 48; + } + + if ($ip === '') { + return 0; + } + $ipAddress = new IpAddress($ip); if ($this->isIPWhitelisted((string)$ipAddress)) { return 0; } - $cutoffTime = $this->getCutoffTimestamp(); + $cutoffTime = $this->getCutoffTimestamp($maxAgeHours); $qb = $this->db->getQueryBuilder(); - $qb->select('*') + $qb->select($qb->func()->count('*', 'attempts')) ->from('bruteforce_attempts') ->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime))) ->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet()))); @@ -235,24 +252,37 @@ class Throttler { $qb->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action))); } - $attempts = count($qb->execute()->fetchAll()); + $result = $qb->execute(); + $row = $result->fetch(); + $result->closeCursor(); + return (int) $row['attempts']; + } + + /** + * Get the throttling delay (in milliseconds) + * + * @param string $ip + * @param string $action optionally filter by action + * @return int + */ + public function getDelay(string $ip, string $action = ''): int { + $attempts = $this->getAttempts($ip, $action); if ($attempts === 0) { return 0; } - $maxDelay = 25; $firstDelay = 0.1; - if ($attempts > (8 * PHP_INT_SIZE - 1)) { + if ($attempts > self::MAX_ATTEMPTS) { // Don't ever overflow. Just assume the maxDelay time:s - $firstDelay = $maxDelay; - } else { - $firstDelay *= pow(2, $attempts); - if ($firstDelay > $maxDelay) { - $firstDelay = $maxDelay; - } + return self::MAX_DELAY_MS; + } + + $delay = $firstDelay * 2 ** $attempts; + if ($delay > self::MAX_DELAY) { + return self::MAX_DELAY_MS; } - return (int) \ceil($firstDelay * 1000); + return (int) \ceil($delay * 1000); } /** @@ -260,9 +290,9 @@ class Throttler { * * @param string $ip * @param string $action - * @param string $metadata + * @param array $metadata */ - public function resetDelay($ip, $action, $metadata) { + public function resetDelay(string $ip, string $action, array $metadata): void { $ipAddress = new IpAddress($ip); if ($this->isIPWhitelisted((string)$ipAddress)) { return; @@ -303,8 +333,27 @@ class Throttler { * @param string $action optionally filter by action * @return int the time spent sleeping */ - public function sleepDelay($ip, $action = '') { + public function sleepDelay(string $ip, string $action = ''): int { + $delay = $this->getDelay($ip, $action); + usleep($delay * 1000); + return $delay; + } + + /** + * Will sleep for the defined amount of time unless maximum was reached in the last 30 minutes + * In this case a "429 Too Many Request" exception is thrown + * + * @param string $ip + * @param string $action optionally filter by action + * @return int the time spent sleeping + * @throws MaxDelayReached when reached the maximum + */ + public function sleepDelayOrThrowOnMax(string $ip, string $action = ''): int { $delay = $this->getDelay($ip, $action); + if (($delay === self::MAX_DELAY_MS) && $this->getAttempts($ip, $action, 0.5) > self::MAX_ATTEMPTS) { + // If the ip made too many attempts within the last 30 mins we don't execute anymore + throw new MaxDelayReached('Reached maximum delay'); + } usleep($delay * 1000); return $delay; } diff --git a/lib/private/Security/CSP/ContentSecurityPolicyManager.php b/lib/private/Security/CSP/ContentSecurityPolicyManager.php index e0403e77936..60a176cbd8b 100644 --- a/lib/private/Security/CSP/ContentSecurityPolicyManager.php +++ b/lib/private/Security/CSP/ContentSecurityPolicyManager.php @@ -7,6 +7,7 @@ declare(strict_types=1); * * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Lukas Reschke <lukas@statuscode.ch> + * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license AGPL-3.0 @@ -57,7 +58,7 @@ class ContentSecurityPolicyManager implements IContentSecurityPolicyManager { */ public function getDefaultPolicy(): ContentSecurityPolicy { $event = new AddContentSecurityPolicyEvent($this); - $this->dispatcher->dispatch(AddContentSecurityPolicyEvent::class, $event); + $this->dispatcher->dispatchTyped($event); $defaultPolicy = new \OC\Security\CSP\ContentSecurityPolicy(); foreach ($this->policies as $policy) { diff --git a/lib/private/Security/CertificateManager.php b/lib/private/Security/CertificateManager.php index e69132ff4df..ef0c6563320 100644 --- a/lib/private/Security/CertificateManager.php +++ b/lib/private/Security/CertificateManager.php @@ -40,11 +40,6 @@ use OCP\Security\ISecureRandom; */ class CertificateManager implements ICertificateManager { /** - * @var string - */ - protected $uid; - - /** * @var \OC\Files\View */ protected $view; @@ -63,18 +58,15 @@ class CertificateManager implements ICertificateManager { protected $random; /** - * @param string $uid * @param \OC\Files\View $view relative to data/ * @param IConfig $config * @param ILogger $logger * @param ISecureRandom $random */ - public function __construct($uid, - \OC\Files\View $view, + public function __construct(\OC\Files\View $view, IConfig $config, ILogger $logger, ISecureRandom $random) { - $this->uid = $uid; $this->view = $view; $this->config = $config; $this->logger = $logger; @@ -112,6 +104,29 @@ class CertificateManager implements ICertificateManager { return $result; } + private function hasCertificates(): bool { + if (!$this->config->getSystemValue('installed', false)) { + return false; + } + + $path = $this->getPathToCertificates() . 'uploads/'; + if (!$this->view->is_dir($path)) { + return false; + } + $result = []; + $handle = $this->view->opendir($path); + if (!is_resource($handle)) { + return false; + } + while (false !== ($file = readdir($handle))) { + if ($file !== '.' && $file !== '..') { + return true; + } + } + closedir($handle); + return false; + } + /** * create the certificate bundle of all trusted certificated */ @@ -148,7 +163,7 @@ class CertificateManager implements ICertificateManager { fwrite($fhCerts, $defaultCertificates); // Append the system certificate bundle - $systemBundle = $this->getCertificateBundle(null); + $systemBundle = $this->getCertificateBundle(); if ($systemBundle !== $certPath && $this->view->file_exists($systemBundle)) { $systemCertificates = $this->view->file_get_contents($systemBundle); fwrite($fhCerts, $systemCertificates); @@ -207,73 +222,50 @@ class CertificateManager implements ICertificateManager { } /** - * Get the path to the certificate bundle for this user + * Get the path to the certificate bundle * - * @param string|null $uid (optional) user to get the certificate bundle for, use `null` to get the system bundle * @return string */ - public function getCertificateBundle($uid = '') { - if ($uid === '') { - $uid = $this->uid; - } - return $this->getPathToCertificates($uid) . 'rootcerts.crt'; + public function getCertificateBundle() { + return $this->getPathToCertificates() . 'rootcerts.crt'; } /** - * Get the full local path to the certificate bundle for this user + * Get the full local path to the certificate bundle * - * @param string $uid (optional) user to get the certificate bundle for, use `null` to get the system bundle * @return string */ - public function getAbsoluteBundlePath($uid = '') { - if ($uid === '') { - $uid = $this->uid; + public function getAbsoluteBundlePath() { + if (!$this->hasCertificates()) { + return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; } - if ($this->needsRebundling($uid)) { - if (is_null($uid)) { - $manager = new CertificateManager(null, $this->view, $this->config, $this->logger, $this->random); - $manager->createCertificateBundle(); - } else { - $this->createCertificateBundle(); - } + + if ($this->needsRebundling()) { + $this->createCertificateBundle(); } - return $this->view->getLocalFile($this->getCertificateBundle($uid)); + + return $this->view->getLocalFile($this->getCertificateBundle()); } /** - * @param string|null $uid (optional) user to get the certificate path for, use `null` to get the system path * @return string */ - private function getPathToCertificates($uid = '') { - if ($uid === '') { - $uid = $this->uid; - } - return is_null($uid) ? '/files_external/' : '/' . $uid . '/files_external/'; + private function getPathToCertificates() { + return '/files_external/'; } /** * Check if we need to re-bundle the certificates because one of the sources has updated * - * @param string $uid (optional) user to get the certificate path for, use `null` to get the system path * @return bool */ - private function needsRebundling($uid = '') { - if ($uid === '') { - $uid = $this->uid; - } - $sourceMTimes = [$this->getFilemtimeOfCaBundle()]; - $targetBundle = $this->getCertificateBundle($uid); + private function needsRebundling() { + $targetBundle = $this->getCertificateBundle(); if (!$this->view->file_exists($targetBundle)) { return true; } - if (!is_null($uid)) { // also depend on the system bundle - $sourceMTimes[] = $this->view->filemtime($this->getCertificateBundle(null)); - } - - $sourceMTime = array_reduce($sourceMTimes, function ($max, $mtime) { - return max($max, $mtime); - }, 0); + $sourceMTime = $this->getFilemtimeOfCaBundle(); return $sourceMTime > $this->view->filemtime($targetBundle); } diff --git a/lib/private/Security/CredentialsManager.php b/lib/private/Security/CredentialsManager.php index ace8e6889ec..7ba8a0020ff 100644 --- a/lib/private/Security/CredentialsManager.php +++ b/lib/private/Security/CredentialsManager.php @@ -3,7 +3,7 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> * @@ -35,7 +35,7 @@ use OCP\Security\ICrypto; * @package OC\Security */ class CredentialsManager implements ICredentialsManager { - public const DB_TABLE = 'credentials'; + public const DB_TABLE = 'storages_credentials'; /** @var ICrypto */ protected $crypto; @@ -81,10 +81,17 @@ class CredentialsManager implements ICredentialsManager { $qb = $this->dbConnection->getQueryBuilder(); $qb->select('credentials') ->from(self::DB_TABLE) - ->where($qb->expr()->eq('user', $qb->createNamedParameter((string)$userId))) - ->andWhere($qb->expr()->eq('identifier', $qb->createNamedParameter($identifier))) - ; - $result = $qb->execute()->fetch(); + ->where($qb->expr()->eq('identifier', $qb->createNamedParameter($identifier))); + + if ($userId === '') { + $qb->andWhere($qb->expr()->emptyString('user')); + } else { + $qb->andWhere($qb->expr()->eq('user', $qb->createNamedParameter((string)$userId))); + } + + $qResult = $qb->execute(); + $result = $qResult->fetch(); + $qResult->closeCursor(); if (!$result) { return null; @@ -104,9 +111,14 @@ class CredentialsManager implements ICredentialsManager { public function delete($userId, $identifier) { $qb = $this->dbConnection->getQueryBuilder(); $qb->delete(self::DB_TABLE) - ->where($qb->expr()->eq('user', $qb->createNamedParameter((string)$userId))) - ->andWhere($qb->expr()->eq('identifier', $qb->createNamedParameter($identifier))) - ; + ->where($qb->expr()->eq('identifier', $qb->createNamedParameter($identifier))); + + if ($userId === '') { + $qb->andWhere($qb->expr()->emptyString('user')); + } else { + $qb->andWhere($qb->expr()->eq('user', $qb->createNamedParameter((string)$userId))); + } + return $qb->execute(); } diff --git a/lib/private/Security/Crypto.php b/lib/private/Security/Crypto.php index 154448281b9..7b1e1a49b19 100644 --- a/lib/private/Security/Crypto.php +++ b/lib/private/Security/Crypto.php @@ -8,6 +8,7 @@ declare(strict_types=1); * @author Andreas Fischer <bantu@owncloud.com> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Lukas Reschke <lukas@statuscode.ch> + * @author lynn-stephenson <lynn.stephenson@protonmail.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> * @@ -90,16 +91,17 @@ class Crypto implements ICrypto { if ($password === '') { $password = $this->config->getSystemValue('secret'); } - $this->cipher->setPassword($password); + $keyMaterial = hash_hkdf('sha512', $password); + $this->cipher->setPassword(substr($keyMaterial, 0, 32)); $iv = \random_bytes($this->ivLength); $this->cipher->setIV($iv); $ciphertext = bin2hex($this->cipher->encrypt($plaintext)); $iv = bin2hex($iv); - $hmac = bin2hex($this->calculateHMAC($ciphertext.$iv, $password)); + $hmac = bin2hex($this->calculateHMAC($ciphertext.$iv, substr($keyMaterial, 32))); - return $ciphertext.'|'.$iv.'|'.$hmac.'|2'; + return $ciphertext.'|'.$iv.'|'.$hmac.'|3'; } /** @@ -114,7 +116,7 @@ class Crypto implements ICrypto { if ($password === '') { $password = $this->config->getSystemValue('secret'); } - $this->cipher->setPassword($password); + $hmacKey = $encryptionKey = $password; $parts = explode('|', $authenticatedCiphertext); $partCount = \count($parts); @@ -128,14 +130,20 @@ class Crypto implements ICrypto { if ($partCount === 4) { $version = $parts[3]; - if ($version === '2') { + if ($version >= '2') { $iv = hex2bin($iv); } - } + if ($version === '3') { + $keyMaterial = hash_hkdf('sha512', $password); + $encryptionKey = substr($keyMaterial, 0, 32); + $hmacKey = substr($keyMaterial, 32); + } + } + $this->cipher->setPassword($encryptionKey); $this->cipher->setIV($iv); - if (!hash_equals($this->calculateHMAC($parts[0] . $parts[1], $password), $hmac)) { + if (!hash_equals($this->calculateHMAC($parts[0] . $parts[1], $hmacKey), $hmac)) { throw new \Exception('HMAC does not match.'); } diff --git a/lib/private/Security/FeaturePolicy/FeaturePolicyManager.php b/lib/private/Security/FeaturePolicy/FeaturePolicyManager.php index 6143a5b95fc..b2959c310c8 100644 --- a/lib/private/Security/FeaturePolicy/FeaturePolicyManager.php +++ b/lib/private/Security/FeaturePolicy/FeaturePolicyManager.php @@ -5,6 +5,7 @@ declare(strict_types=1); /** * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl> * + * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version @@ -47,7 +48,7 @@ class FeaturePolicyManager { public function getDefaultPolicy(): FeaturePolicy { $event = new AddFeaturePolicyEvent($this); - $this->dispatcher->dispatch(AddFeaturePolicyEvent::class, $event); + $this->dispatcher->dispatchTyped($event); $defaultPolicy = new FeaturePolicy(); foreach ($this->policies as $policy) { diff --git a/lib/private/Security/Hasher.php b/lib/private/Security/Hasher.php index a51508ba8e9..4b068ce0110 100644 --- a/lib/private/Security/Hasher.php +++ b/lib/private/Security/Hasher.php @@ -8,6 +8,7 @@ declare(strict_types=1); * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Lukas Reschke <lukas@statuscode.ch> + * @author MichaIng <micha@dietpi.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> * diff --git a/lib/private/Security/Normalizer/IpAddress.php b/lib/private/Security/Normalizer/IpAddress.php index b471c499440..a679936e20c 100644 --- a/lib/private/Security/Normalizer/IpAddress.php +++ b/lib/private/Security/Normalizer/IpAddress.php @@ -5,6 +5,7 @@ declare(strict_types=1); /** * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> * + * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Konrad Bucheli <kb@open.ch> * @author Lukas Reschke <lukas@statuscode.ch> * @author Roeland Jago Douma <roeland@famdouma.nl> @@ -78,7 +79,7 @@ class IpAddress { } $pos = strpos($ip, '%'); // if there is an explicit interface added to the IP, e.g. fe80::ae2d:d1e7:fe1e:9a8d%enp2s0 if ($pos !== false) { - $ip = substr($ip, 0, $pos-1); + $ip = substr($ip, 0, $pos - 1); } $binary = \inet_pton($ip); for ($i = 128; $i > $maskBits; $i -= 8) { diff --git a/lib/private/Server.php b/lib/private/Server.php index 09bb7336785..687eba68e73 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -20,6 +20,7 @@ * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Julius Haertl <jus@bitgrid.net> * @author Julius Härtl <jus@bitgrid.net> + * @author Lionel Elie Mamane <lionel@mamane.lu> * @author Lukas Reschke <lukas@statuscode.ch> * @author Maxence Lange <maxence@artificial-owl.com> * @author Michael Weimann <mail@michael-weimann.eu> @@ -29,12 +30,10 @@ * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author root <root@localhost.localdomain> - * @author Sander <brantje@gmail.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Thomas Tanghus <thomas@tanghus.net> * @author Tobia De Koninck <tobia@ledfan.be> - * @author Vincent Petry <pvince81@owncloud.com> - * @author Xheni Myrtaj <myrtajxheni@gmail.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -61,10 +60,10 @@ use OC\App\AppStore\Bundles\BundleFetcher; use OC\App\AppStore\Fetcher\AppFetcher; use OC\App\AppStore\Fetcher\CategoryFetcher; use OC\AppFramework\Http\Request; -use OC\AppFramework\Utility\SimpleContainer; use OC\AppFramework\Utility\TimeFactory; use OC\Authentication\Events\LoginFailed; use OC\Authentication\Listeners\LoginFailedListener; +use OC\Authentication\Listeners\UserLoggedInListener; use OC\Authentication\LoginCredentials\Store; use OC\Authentication\Token\IProvider; use OC\Avatar\AvatarManager; @@ -80,6 +79,7 @@ use OC\Contacts\ContactsMenu\ContactsStore; use OC\Dashboard\DashboardManager; use OC\Diagnostics\EventLogger; use OC\Diagnostics\QueryLogger; +use OC\EventDispatcher\SymfonyAdapter; use OC\Federation\CloudFederationFactory; use OC\Federation\CloudFederationProviderManager; use OC\Federation\CloudIdManager; @@ -88,10 +88,12 @@ use OC\Files\Config\UserMountCacheListener; use OC\Files\Mount\CacheMountProvider; use OC\Files\Mount\LocalHomeMountProvider; use OC\Files\Mount\ObjectHomeMountProvider; +use OC\Files\Mount\ObjectStorePreviewCacheMountProvider; use OC\Files\Node\HookConnector; use OC\Files\Node\LazyRoot; use OC\Files\Node\Root; use OC\Files\Storage\StorageFactory; +use OC\Files\Type\Loader; use OC\Files\View; use OC\FullTextSearch\FullTextSearchManager; use OC\Http\Client\ClientService; @@ -114,13 +116,13 @@ use OC\Preview\GeneratorHelper; use OC\Remote\Api\ApiFactory; use OC\Remote\InstanceFactory; use OC\RichObjectStrings\Validator; +use OC\Route\Router; use OC\Security\Bruteforce\Throttler; use OC\Security\CertificateManager; use OC\Security\CredentialsManager; use OC\Security\Crypto; use OC\Security\CSP\ContentSecurityPolicyManager; use OC\Security\CSP\ContentSecurityPolicyNonceManager; -use OC\Security\CSRF\CsrfTokenGenerator; use OC\Security\CSRF\CsrfTokenManager; use OC\Security\CSRF\TokenStorage\SessionStorage; use OC\Security\Hasher; @@ -131,9 +133,7 @@ use OC\Share20\ProviderFactory; use OC\Share20\ShareHelper; use OC\SystemTag\ManagerFactory as SystemTagManagerFactory; use OC\Tagging\TagMapper; -use OC\Template\IconsCacher; use OC\Template\JSCombiner; -use OC\Template\SCSSCacher; use OCA\Theming\ImageManager; use OCA\Theming\ThemingDefaults; use OCA\Theming\Util; @@ -150,6 +150,8 @@ use OCP\Dashboard\IDashboardManager; use OCP\Defaults; use OCP\Diagnostics\IEventLogger; use OCP\Diagnostics\IQueryLogger; +use OCP\Encryption\IFile; +use OCP\Encryption\Keys\IStorage; use OCP\EventDispatcher\IEventDispatcher; use OCP\Federation\ICloudFederationFactory; use OCP\Federation\ICloudFederationProviderManager; @@ -178,6 +180,7 @@ use OCP\IAppConfig; use OCP\IAvatarManager; use OCP\ICache; use OCP\ICacheFactory; +use OCP\ICertificateManager; use OCP\IDateTimeFormatter; use OCP\IDateTimeZone; use OCP\IDBConnection; @@ -190,6 +193,7 @@ use OCP\IPreview; use OCP\IRequest; use OCP\ISearch; use OCP\IServerContainer; +use OCP\ISession; use OCP\ITagManager; use OCP\ITempManager; use OCP\IURLGenerator; @@ -219,8 +223,8 @@ use OCP\User\Events\BeforeUserLoggedInEvent; use OCP\User\Events\BeforeUserLoggedInWithCookieEvent; use OCP\User\Events\BeforeUserLoggedOutEvent; use OCP\User\Events\PasswordUpdatedEvent; +use OCP\User\Events\PostLoginEvent; use OCP\User\Events\UserChangedEvent; -use OCP\User\Events\UserCreatedEvent; use OCP\User\Events\UserDeletedEvent; use OCP\User\Events\UserLoggedInEvent; use OCP\User\Events\UserLoggedInWithCookieEvent; @@ -257,6 +261,7 @@ class Server extends ServerContainer implements IServerContainer { // To find out if we are running from CLI or not $this->registerParameter('isCLI', \OC::$CLI); + $this->registerParameter('serverRoot', \OC::$SERVERROOT); $this->registerService(ContainerInterface::class, function (ContainerInterface $c) { return $c; @@ -266,37 +271,51 @@ class Server extends ServerContainer implements IServerContainer { }); $this->registerAlias(\OCP\Calendar\IManager::class, \OC\Calendar\Manager::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('CalendarManager', \OC\Calendar\Manager::class); $this->registerAlias(\OCP\Calendar\Resource\IManager::class, \OC\Calendar\Resource\Manager::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('CalendarResourceBackendManager', \OC\Calendar\Resource\Manager::class); $this->registerAlias(\OCP\Calendar\Room\IManager::class, \OC\Calendar\Room\Manager::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('CalendarRoomBackendManager', \OC\Calendar\Room\Manager::class); $this->registerAlias(\OCP\Contacts\IManager::class, \OC\ContactsManager::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('ContactsManager', \OCP\Contacts\IManager::class); $this->registerAlias(\OCP\DirectEditing\IManager::class, \OC\DirectEditing\Manager::class); $this->registerAlias(IActionFactory::class, ActionFactory::class); + $this->registerService(View::class, function (Server $c) { + return new View(); + }, false); - $this->registerService(IPreview::class, function (Server $c) { + $this->registerService(IPreview::class, function (ContainerInterface $c) { return new PreviewManager( - $c->getConfig(), - $c->getRootFolder(), - new \OC\Preview\Storage\Root($c->getRootFolder(), $c->getSystemConfig(), 'preview'), - $c->getEventDispatcher(), - $c->getGeneratorHelper(), - $c->getSession()->get('user_id') + $c->get(\OCP\IConfig::class), + $c->get(IRootFolder::class), + new \OC\Preview\Storage\Root( + $c->get(IRootFolder::class), + $c->get(SystemConfig::class) + ), + $c->get(SymfonyAdapter::class), + $c->get(GeneratorHelper::class), + $c->get(ISession::class)->get('user_id') ); }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('PreviewManager', IPreview::class); - $this->registerService(\OC\Preview\Watcher::class, function (Server $c) { + $this->registerService(\OC\Preview\Watcher::class, function (ContainerInterface $c) { return new \OC\Preview\Watcher( - new \OC\Preview\Storage\Root($c->getRootFolder(), $c->getSystemConfig(), 'preview') + new \OC\Preview\Storage\Root( + $c->get(IRootFolder::class), + $c->get(SystemConfig::class) + ) ); }); @@ -304,371 +323,351 @@ class Server extends ServerContainer implements IServerContainer { $view = new View(); $util = new Encryption\Util( $view, - $c->getUserManager(), - $c->getGroupManager(), - $c->getConfig() + $c->get(IUserManager::class), + $c->get(IGroupManager::class), + $c->get(\OCP\IConfig::class) ); return new Encryption\Manager( - $c->getConfig(), - $c->getLogger(), + $c->get(\OCP\IConfig::class), + $c->get(ILogger::class), $c->getL10N('core'), new View(), $util, new ArrayCache() ); }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('EncryptionManager', \OCP\Encryption\IManager::class); - $this->registerService('EncryptionFileHelper', function (Server $c) { + /** @deprecated 21.0.0 */ + $this->registerDeprecatedAlias('EncryptionFileHelper', IFile::class); + $this->registerService(IFile::class, function (ContainerInterface $c) { $util = new Encryption\Util( new View(), - $c->getUserManager(), - $c->getGroupManager(), - $c->getConfig() + $c->get(IUserManager::class), + $c->get(IGroupManager::class), + $c->get(\OCP\IConfig::class) ); return new Encryption\File( $util, - $c->getRootFolder(), - $c->getShareManager() + $c->get(IRootFolder::class), + $c->get(\OCP\Share\IManager::class) ); }); - $this->registerService('EncryptionKeyStorage', function (Server $c) { + /** @deprecated 21.0.0 */ + $this->registerDeprecatedAlias('EncryptionKeyStorage', IStorage::class); + $this->registerService(IStorage::class, function (ContainerInterface $c) { $view = new View(); $util = new Encryption\Util( $view, - $c->getUserManager(), - $c->getGroupManager(), - $c->getConfig() + $c->get(IUserManager::class), + $c->get(IGroupManager::class), + $c->get(\OCP\IConfig::class) ); - return new Encryption\Keys\Storage($view, $util); - }); - $this->registerService('TagMapper', function (Server $c) { - return new TagMapper($c->getDatabaseConnection()); + return new Encryption\Keys\Storage( + $view, + $util, + $c->get(ICrypto::class), + $c->get(\OCP\IConfig::class) + ); }); + /** @deprecated 20.0.0 */ + $this->registerDeprecatedAlias('TagMapper', TagMapper::class); - $this->registerService(\OCP\ITagManager::class, function (Server $c) { - $tagMapper = $c->query('TagMapper'); - return new TagManager($tagMapper, $c->getUserSession()); - }); + $this->registerAlias(\OCP\ITagManager::class, TagManager::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('TagManager', \OCP\ITagManager::class); - $this->registerService('SystemTagManagerFactory', function (Server $c) { - $config = $c->getConfig(); + $this->registerService('SystemTagManagerFactory', function (ContainerInterface $c) { + /** @var \OCP\IConfig $config */ + $config = $c->get(\OCP\IConfig::class); $factoryClass = $config->getSystemValue('systemtags.managerFactory', SystemTagManagerFactory::class); return new $factoryClass($this); }); - $this->registerService(ISystemTagManager::class, function (Server $c) { - return $c->query('SystemTagManagerFactory')->getManager(); + $this->registerService(ISystemTagManager::class, function (ContainerInterface $c) { + return $c->get('SystemTagManagerFactory')->getManager(); }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('SystemTagManager', ISystemTagManager::class); - $this->registerService(ISystemTagObjectMapper::class, function (Server $c) { - return $c->query('SystemTagManagerFactory')->getObjectMapper(); + $this->registerService(ISystemTagObjectMapper::class, function (ContainerInterface $c) { + return $c->get('SystemTagManagerFactory')->getObjectMapper(); }); - $this->registerService('RootFolder', function (Server $c) { + $this->registerService('RootFolder', function (ContainerInterface $c) { $manager = \OC\Files\Filesystem::getMountManager(null); $view = new View(); $root = new Root( $manager, $view, null, - $c->getUserMountCache(), - $this->getLogger(), - $this->getUserManager() + $c->get(IUserMountCache::class), + $this->get(ILogger::class), + $this->get(IUserManager::class) ); - $previewConnector = new \OC\Preview\WatcherConnector($root, $c->getSystemConfig()); + $previewConnector = new \OC\Preview\WatcherConnector( + $root, + $c->get(SystemConfig::class) + ); $previewConnector->connectWatcher(); return $root; }); - $this->registerService(HookConnector::class, function (Server $c) { + $this->registerService(HookConnector::class, function (ContainerInterface $c) { return new HookConnector( - $c->query(IRootFolder::class), + $c->get(IRootFolder::class), new View(), - $c->query(\OC\EventDispatcher\SymfonyAdapter::class), - $c->query(IEventDispatcher::class) + $c->get(\OC\EventDispatcher\SymfonyAdapter::class), + $c->get(IEventDispatcher::class) ); }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('SystemTagObjectMapper', ISystemTagObjectMapper::class); - $this->registerService(IRootFolder::class, function (Server $c) { + $this->registerService(IRootFolder::class, function (ContainerInterface $c) { return new LazyRoot(function () use ($c) { - return $c->query('RootFolder'); + return $c->get('RootFolder'); }); }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('LazyRootFolder', IRootFolder::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('UserManager', \OC\User\Manager::class); $this->registerAlias(\OCP\IUserManager::class, \OC\User\Manager::class); - $this->registerService(\OCP\IGroupManager::class, function (Server $c) { - $groupManager = new \OC\Group\Manager($this->getUserManager(), $c->getEventDispatcher(), $this->getLogger()); + $this->registerService(\OCP\IGroupManager::class, function (ContainerInterface $c) { + $groupManager = new \OC\Group\Manager($this->get(IUserManager::class), $c->get(SymfonyAdapter::class), $this->get(ILogger::class)); $groupManager->listen('\OC\Group', 'preCreate', function ($gid) { - \OC_Hook::emit('OC_Group', 'pre_createGroup', ['run' => true, 'gid' => $gid]); - /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher = $this->get(IEventDispatcher::class); $dispatcher->dispatchTyped(new BeforeGroupCreatedEvent($gid)); }); $groupManager->listen('\OC\Group', 'postCreate', function (\OC\Group\Group $group) { - \OC_Hook::emit('OC_User', 'post_createGroup', ['gid' => $group->getGID()]); - /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher = $this->get(IEventDispatcher::class); $dispatcher->dispatchTyped(new GroupCreatedEvent($group)); }); $groupManager->listen('\OC\Group', 'preDelete', function (\OC\Group\Group $group) { - \OC_Hook::emit('OC_Group', 'pre_deleteGroup', ['run' => true, 'gid' => $group->getGID()]); - /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher = $this->get(IEventDispatcher::class); $dispatcher->dispatchTyped(new BeforeGroupDeletedEvent($group)); }); $groupManager->listen('\OC\Group', 'postDelete', function (\OC\Group\Group $group) { - \OC_Hook::emit('OC_User', 'post_deleteGroup', ['gid' => $group->getGID()]); - /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher = $this->get(IEventDispatcher::class); $dispatcher->dispatchTyped(new GroupDeletedEvent($group)); }); $groupManager->listen('\OC\Group', 'preAddUser', function (\OC\Group\Group $group, \OC\User\User $user) { - \OC_Hook::emit('OC_Group', 'pre_addToGroup', ['run' => true, 'uid' => $user->getUID(), 'gid' => $group->getGID()]); - /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher = $this->get(IEventDispatcher::class); $dispatcher->dispatchTyped(new BeforeUserAddedEvent($group, $user)); }); $groupManager->listen('\OC\Group', 'postAddUser', function (\OC\Group\Group $group, \OC\User\User $user) { - \OC_Hook::emit('OC_Group', 'post_addToGroup', ['uid' => $user->getUID(), 'gid' => $group->getGID()]); - //Minimal fix to keep it backward compatible TODO: clean up all the GroupManager hooks - \OC_Hook::emit('OC_User', 'post_addToGroup', ['uid' => $user->getUID(), 'gid' => $group->getGID()]); - /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher = $this->get(IEventDispatcher::class); $dispatcher->dispatchTyped(new UserAddedEvent($group, $user)); }); $groupManager->listen('\OC\Group', 'preRemoveUser', function (\OC\Group\Group $group, \OC\User\User $user) { /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher = $this->get(IEventDispatcher::class); $dispatcher->dispatchTyped(new BeforeUserRemovedEvent($group, $user)); }); $groupManager->listen('\OC\Group', 'postRemoveUser', function (\OC\Group\Group $group, \OC\User\User $user) { /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher = $this->get(IEventDispatcher::class); $dispatcher->dispatchTyped(new UserRemovedEvent($group, $user)); }); return $groupManager; }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('GroupManager', \OCP\IGroupManager::class); - $this->registerService(Store::class, function (Server $c) { - $session = $c->getSession(); - if (\OC::$server->getSystemConfig()->getValue('installed', false)) { - $tokenProvider = $c->query(IProvider::class); + $this->registerService(Store::class, function (ContainerInterface $c) { + $session = $c->get(ISession::class); + if (\OC::$server->get(SystemConfig::class)->getValue('installed', false)) { + $tokenProvider = $c->get(IProvider::class); } else { $tokenProvider = null; } - $logger = $c->getLogger(); + $logger = $c->get(LoggerInterface::class); return new Store($session, $logger, $tokenProvider); }); $this->registerAlias(IStore::class, Store::class); - $this->registerService(Authentication\Token\DefaultTokenMapper::class, function (Server $c) { - $dbConnection = $c->getDatabaseConnection(); - return new Authentication\Token\DefaultTokenMapper($dbConnection); - }); $this->registerAlias(IProvider::class, Authentication\Token\Manager::class); $this->registerService(\OC\User\Session::class, function (Server $c) { - $manager = $c->getUserManager(); + $manager = $c->get(IUserManager::class); $session = new \OC\Session\Memory(''); $timeFactory = new TimeFactory(); // Token providers might require a working database. This code // might however be called when ownCloud is not yet setup. - if (\OC::$server->getSystemConfig()->getValue('installed', false)) { - $defaultTokenProvider = $c->query(IProvider::class); + if (\OC::$server->get(SystemConfig::class)->getValue('installed', false)) { + $defaultTokenProvider = $c->get(IProvider::class); } else { $defaultTokenProvider = null; } - $legacyDispatcher = $c->getEventDispatcher(); + $legacyDispatcher = $c->get(SymfonyAdapter::class); $userSession = new \OC\User\Session( $manager, $session, $timeFactory, $defaultTokenProvider, - $c->getConfig(), - $c->getSecureRandom(), + $c->get(\OCP\IConfig::class), + $c->get(ISecureRandom::class), $c->getLockdownManager(), - $c->getLogger(), - $c->query(IEventDispatcher::class) + $c->get(ILogger::class), + $c->get(IEventDispatcher::class) ); + /** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */ $userSession->listen('\OC\User', 'preCreateUser', function ($uid, $password) { \OC_Hook::emit('OC_User', 'pre_createUser', ['run' => true, 'uid' => $uid, 'password' => $password]); - - /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); - $dispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password)); }); + /** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */ $userSession->listen('\OC\User', 'postCreateUser', function ($user, $password) { - /** @var $user \OC\User\User */ + /** @var \OC\User\User $user */ \OC_Hook::emit('OC_User', 'post_createUser', ['uid' => $user->getUID(), 'password' => $password]); - - /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); - $dispatcher->dispatchTyped(new UserCreatedEvent($user, $password)); }); + /** @deprecated 21.0.0 use BeforeUserDeletedEvent event with the IEventDispatcher instead */ $userSession->listen('\OC\User', 'preDelete', function ($user) use ($legacyDispatcher) { - /** @var $user \OC\User\User */ + /** @var \OC\User\User $user */ \OC_Hook::emit('OC_User', 'pre_deleteUser', ['run' => true, 'uid' => $user->getUID()]); $legacyDispatcher->dispatch('OCP\IUser::preDelete', new GenericEvent($user)); - - /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); - $dispatcher->dispatchTyped(new BeforeUserDeletedEvent($user)); }); + /** @deprecated 21.0.0 use UserDeletedEvent event with the IEventDispatcher instead */ $userSession->listen('\OC\User', 'postDelete', function ($user) { - /** @var $user \OC\User\User */ + /** @var \OC\User\User $user */ \OC_Hook::emit('OC_User', 'post_deleteUser', ['uid' => $user->getUID()]); - - /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); - $dispatcher->dispatchTyped(new UserDeletedEvent($user)); }); $userSession->listen('\OC\User', 'preSetPassword', function ($user, $password, $recoveryPassword) { - /** @var $user \OC\User\User */ + /** @var \OC\User\User $user */ \OC_Hook::emit('OC_User', 'pre_setPassword', ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]); /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher = $this->get(IEventDispatcher::class); $dispatcher->dispatchTyped(new BeforePasswordUpdatedEvent($user, $password, $recoveryPassword)); }); $userSession->listen('\OC\User', 'postSetPassword', function ($user, $password, $recoveryPassword) { - /** @var $user \OC\User\User */ + /** @var \OC\User\User $user */ \OC_Hook::emit('OC_User', 'post_setPassword', ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]); /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher = $this->get(IEventDispatcher::class); $dispatcher->dispatchTyped(new PasswordUpdatedEvent($user, $password, $recoveryPassword)); }); $userSession->listen('\OC\User', 'preLogin', function ($uid, $password) { \OC_Hook::emit('OC_User', 'pre_login', ['run' => true, 'uid' => $uid, 'password' => $password]); /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher = $this->get(IEventDispatcher::class); $dispatcher->dispatchTyped(new BeforeUserLoggedInEvent($uid, $password)); }); - $userSession->listen('\OC\User', 'postLogin', function ($user, $password, $isTokenLogin) { - /** @var $user \OC\User\User */ - \OC_Hook::emit('OC_User', 'post_login', ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'isTokenLogin' => $isTokenLogin]); + $userSession->listen('\OC\User', 'postLogin', function ($user, $loginName, $password, $isTokenLogin) { + /** @var \OC\User\User $user */ + \OC_Hook::emit('OC_User', 'post_login', ['run' => true, 'uid' => $user->getUID(), 'loginName' => $loginName, 'password' => $password, 'isTokenLogin' => $isTokenLogin]); /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher = $this->get(IEventDispatcher::class); $dispatcher->dispatchTyped(new UserLoggedInEvent($user, $password, $isTokenLogin)); }); $userSession->listen('\OC\User', 'preRememberedLogin', function ($uid) { /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher = $this->get(IEventDispatcher::class); $dispatcher->dispatchTyped(new BeforeUserLoggedInWithCookieEvent($uid)); }); $userSession->listen('\OC\User', 'postRememberedLogin', function ($user, $password) { - /** @var $user \OC\User\User */ + /** @var \OC\User\User $user */ \OC_Hook::emit('OC_User', 'post_login', ['run' => true, 'uid' => $user->getUID(), 'password' => $password]); /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher = $this->get(IEventDispatcher::class); $dispatcher->dispatchTyped(new UserLoggedInWithCookieEvent($user, $password)); }); $userSession->listen('\OC\User', 'logout', function ($user) { \OC_Hook::emit('OC_User', 'logout', []); /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher = $this->get(IEventDispatcher::class); $dispatcher->dispatchTyped(new BeforeUserLoggedOutEvent($user)); }); $userSession->listen('\OC\User', 'postLogout', function ($user) { /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher = $this->get(IEventDispatcher::class); $dispatcher->dispatchTyped(new UserLoggedOutEvent($user)); }); $userSession->listen('\OC\User', 'changeUser', function ($user, $feature, $value, $oldValue) { - /** @var $user \OC\User\User */ + /** @var \OC\User\User $user */ \OC_Hook::emit('OC_User', 'changeUser', ['run' => true, 'user' => $user, 'feature' => $feature, 'value' => $value, 'old_value' => $oldValue]); /** @var IEventDispatcher $dispatcher */ - $dispatcher = $this->query(IEventDispatcher::class); + $dispatcher = $this->get(IEventDispatcher::class); $dispatcher->dispatchTyped(new UserChangedEvent($user, $feature, $value, $oldValue)); }); return $userSession; }); $this->registerAlias(\OCP\IUserSession::class, \OC\User\Session::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('UserSession', \OC\User\Session::class); $this->registerAlias(\OCP\Authentication\TwoFactorAuth\IRegistry::class, \OC\Authentication\TwoFactorAuth\Registry::class); $this->registerAlias(INavigationManager::class, \OC\NavigationManager::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('NavigationManager', INavigationManager::class); - $this->registerService(\OC\AllConfig::class, function (Server $c) { - return new \OC\AllConfig( - $c->getSystemConfig() - ); - }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('AllConfig', \OC\AllConfig::class); $this->registerAlias(\OCP\IConfig::class, \OC\AllConfig::class); $this->registerService(\OC\SystemConfig::class, function ($c) use ($config) { return new \OC\SystemConfig($config); }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('SystemConfig', \OC\SystemConfig::class); - $this->registerService(\OC\AppConfig::class, function (Server $c) { - return new \OC\AppConfig($c->getDatabaseConnection()); - }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('AppConfig', \OC\AppConfig::class); $this->registerAlias(IAppConfig::class, \OC\AppConfig::class); $this->registerService(IFactory::class, function (Server $c) { return new \OC\L10N\Factory( - $c->getConfig(), + $c->get(\OCP\IConfig::class), $c->getRequest(), - $c->getUserSession(), + $c->get(IUserSession::class), \OC::$SERVERROOT ); }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('L10NFactory', IFactory::class); - $this->registerService(IURLGenerator::class, function (Server $c) { - $config = $c->getConfig(); - $cacheFactory = $c->getMemCacheFactory(); - $request = $c->getRequest(); - return new \OC\URLGenerator( - $config, - $cacheFactory, - $request - ); - }); + $this->registerAlias(IURLGenerator::class, URLGenerator::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('URLGenerator', IURLGenerator::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('AppFetcher', AppFetcher::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('CategoryFetcher', CategoryFetcher::class); $this->registerService(ICache::class, function ($c) { return new Cache\File(); }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('UserCache', ICache::class); $this->registerService(Factory::class, function (Server $c) { - $arrayCacheFactory = new \OC\Memcache\Factory('', $c->getLogger(), + $arrayCacheFactory = new \OC\Memcache\Factory('', $c->get(ILogger::class), ArrayCache::class, ArrayCache::class, ArrayCache::class ); - $config = $c->getConfig(); + /** @var \OCP\IConfig $config */ + $config = $c->get(\OCP\IConfig::class); if ($config->getSystemValue('installed', false) && !(defined('PHPUNIT_RUN') && PHPUNIT_RUN)) { $v = \OC_App::getAppVersions(); @@ -677,7 +676,7 @@ class Server extends ServerContainer implements IServerContainer { $instanceId = \OC_Util::getInstanceId(); $path = \OC::$SERVERROOT; $prefix = md5($instanceId . '-' . $version . '-' . $path); - return new \OC\Memcache\Factory($prefix, $c->getLogger(), + return new \OC\Memcache\Factory($prefix, $c->get(ILogger::class), $config->getSystemValue('memcache.local', null), $config->getSystemValue('memcache.distributed', null), $config->getSystemValue('memcache.locking', null) @@ -685,22 +684,26 @@ class Server extends ServerContainer implements IServerContainer { } return $arrayCacheFactory; }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('MemCacheFactory', Factory::class); $this->registerAlias(ICacheFactory::class, Factory::class); $this->registerService('RedisFactory', function (Server $c) { - $systemConfig = $c->getSystemConfig(); + $systemConfig = $c->get(SystemConfig::class); return new RedisFactory($systemConfig); }); $this->registerService(\OCP\Activity\IManager::class, function (Server $c) { + $l10n = $this->get(IFactory::class)->get('lib'); return new \OC\Activity\Manager( $c->getRequest(), - $c->getUserSession(), - $c->getConfig(), - $c->query(IValidator::class) + $c->get(IUserSession::class), + $c->get(\OCP\IConfig::class), + $c->get(IValidator::class), + $l10n ); }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('ActivityManager', \OCP\Activity\IManager::class); $this->registerService(\OCP\Activity\IEventMerger::class, function (Server $c) { @@ -712,49 +715,45 @@ class Server extends ServerContainer implements IServerContainer { $this->registerService(AvatarManager::class, function (Server $c) { return new AvatarManager( - $c->query(\OC\User\Manager::class), + $c->get(\OC\User\Manager::class), $c->getAppDataDir('avatar'), $c->getL10N('lib'), - $c->getLogger(), - $c->getConfig() + $c->get(ILogger::class), + $c->get(\OCP\IConfig::class) ); }); $this->registerAlias(IAvatarManager::class, AvatarManager::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('AvatarManager', AvatarManager::class); $this->registerAlias(\OCP\Support\CrashReport\IRegistry::class, \OC\Support\CrashReport\Registry::class); $this->registerAlias(\OCP\Support\Subscription\IRegistry::class, \OC\Support\Subscription\Registry::class); $this->registerService(\OC\Log::class, function (Server $c) { - $logType = $c->query(AllConfig::class)->getSystemValue('log_type', 'file'); - $factory = new LogFactory($c, $this->getSystemConfig()); + $logType = $c->get(AllConfig::class)->getSystemValue('log_type', 'file'); + $factory = new LogFactory($c, $this->get(SystemConfig::class)); $logger = $factory->get($logType); - $registry = $c->query(\OCP\Support\CrashReport\IRegistry::class); + $registry = $c->get(\OCP\Support\CrashReport\IRegistry::class); - return new Log($logger, $this->getSystemConfig(), null, $registry); + return new Log($logger, $this->get(SystemConfig::class), null, $registry); }); $this->registerAlias(ILogger::class, \OC\Log::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('Logger', \OC\Log::class); // PSR-3 logger $this->registerAlias(LoggerInterface::class, PsrLoggerAdapter::class); $this->registerService(ILogFactory::class, function (Server $c) { - return new LogFactory($c, $this->getSystemConfig()); + return new LogFactory($c, $this->get(SystemConfig::class)); }); - $this->registerService(IJobList::class, function (Server $c) { - $config = $c->getConfig(); - return new \OC\BackgroundJob\JobList( - $c->getDatabaseConnection(), - $config, - new TimeFactory() - ); - }); + $this->registerAlias(IJobList::class, \OC\BackgroundJob\JobList::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('JobList', IJobList::class); - $this->registerService(IRouter::class, function (Server $c) { - $cacheFactory = $c->getMemCacheFactory(); - $logger = $c->getLogger(); + $this->registerService(Router::class, function (Server $c) { + $cacheFactory = $c->get(ICacheFactory::class); + $logger = $c->get(ILogger::class); if ($cacheFactory->isLocalCacheAvailable()) { $router = new \OC\Route\CachingRouter($cacheFactory->createLocal('route'), $logger); } else { @@ -762,42 +761,39 @@ class Server extends ServerContainer implements IServerContainer { } return $router; }); + $this->registerAlias(IRouter::class, Router::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('Router', IRouter::class); - $this->registerService(ISearch::class, function ($c) { - return new Search(); - }); + $this->registerAlias(ISearch::class, Search::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('Search', ISearch::class); $this->registerService(\OC\Security\RateLimiting\Backend\IBackend::class, function ($c) { return new \OC\Security\RateLimiting\Backend\MemoryCache( - $this->getMemCacheFactory(), + $this->get(ICacheFactory::class), new \OC\AppFramework\Utility\TimeFactory() ); }); - $this->registerService(\OCP\Security\ISecureRandom::class, function ($c) { - return new SecureRandom(); - }); + $this->registerAlias(\OCP\Security\ISecureRandom::class, SecureRandom::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('SecureRandom', \OCP\Security\ISecureRandom::class); - $this->registerService(ICrypto::class, function (Server $c) { - return new Crypto($c->getConfig(), $c->getSecureRandom()); - }); + $this->registerAlias(ICrypto::class, Crypto::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('Crypto', ICrypto::class); - $this->registerService(IHasher::class, function (Server $c) { - return new Hasher($c->getConfig()); - }); + $this->registerAlias(IHasher::class, Hasher::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('Hasher', IHasher::class); - $this->registerService(ICredentialsManager::class, function (Server $c) { - return new CredentialsManager($c->getCrypto(), $c->getDatabaseConnection()); - }); + $this->registerAlias(ICredentialsManager::class, CredentialsManager::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('CredentialsManager', ICredentialsManager::class); $this->registerService(IDBConnection::class, function (Server $c) { - $systemConfig = $c->getSystemConfig(); + $systemConfig = $c->get(SystemConfig::class); $factory = new \OC\DB\ConnectionFactory($systemConfig); $type = $systemConfig->getValue('dbtype', 'sqlite'); if (!$factory->isValidType($type)) { @@ -808,147 +804,129 @@ class Server extends ServerContainer implements IServerContainer { $connection->getConfiguration()->setSQLLogger($c->getQueryLogger()); return $connection; }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('DatabaseConnection', IDBConnection::class); - - $this->registerService(IClientService::class, function (Server $c) { - $user = \OC_User::getUser(); - $uid = $user ? $user : null; - return new ClientService( - $c->getConfig(), - $c->getLogger(), - new \OC\Security\CertificateManager( - $uid, - new View(), - $c->getConfig(), - $c->getLogger(), - $c->getSecureRandom() - ) - ); - }); + $this->registerAlias(ICertificateManager::class, CertificateManager::class); + $this->registerAlias(IClientService::class, ClientService::class); $this->registerDeprecatedAlias('HttpClientService', IClientService::class); - $this->registerService(IEventLogger::class, function (Server $c) { + $this->registerService(IEventLogger::class, function (ContainerInterface $c) { $eventLogger = new EventLogger(); - if ($c->getSystemConfig()->getValue('debug', false)) { + if ($c->get(SystemConfig::class)->getValue('debug', false)) { // In debug mode, module is being activated by default $eventLogger->activate(); } return $eventLogger; }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('EventLogger', IEventLogger::class); - $this->registerService(IQueryLogger::class, function (Server $c) { + $this->registerService(IQueryLogger::class, function (ContainerInterface $c) { $queryLogger = new QueryLogger(); - if ($c->getSystemConfig()->getValue('debug', false)) { + if ($c->get(SystemConfig::class)->getValue('debug', false)) { // In debug mode, module is being activated by default $queryLogger->activate(); } return $queryLogger; }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('QueryLogger', IQueryLogger::class); - $this->registerService(TempManager::class, function (Server $c) { - return new TempManager( - $c->getLogger(), - $c->getConfig() - ); - }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('TempManager', TempManager::class); $this->registerAlias(ITempManager::class, TempManager::class); - $this->registerService(AppManager::class, function (Server $c) { + $this->registerService(AppManager::class, function (ContainerInterface $c) { + // TODO: use auto-wiring return new \OC\App\AppManager( - $c->getUserSession(), - $c->getConfig(), - $c->query(\OC\AppConfig::class), - $c->getGroupManager(), - $c->getMemCacheFactory(), - $c->getEventDispatcher(), - $c->getLogger() + $c->get(IUserSession::class), + $c->get(\OCP\IConfig::class), + $c->get(\OC\AppConfig::class), + $c->get(IGroupManager::class), + $c->get(ICacheFactory::class), + $c->get(SymfonyAdapter::class), + $c->get(ILogger::class) ); }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('AppManager', AppManager::class); $this->registerAlias(IAppManager::class, AppManager::class); - $this->registerService(IDateTimeZone::class, function (Server $c) { - return new DateTimeZone( - $c->getConfig(), - $c->getSession() - ); - }); + $this->registerAlias(IDateTimeZone::class, DateTimeZone::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('DateTimeZone', IDateTimeZone::class); $this->registerService(IDateTimeFormatter::class, function (Server $c) { - $language = $c->getConfig()->getUserValue($c->getSession()->get('user_id'), 'core', 'lang', null); + $language = $c->get(\OCP\IConfig::class)->getUserValue($c->get(ISession::class)->get('user_id'), 'core', 'lang', null); return new DateTimeFormatter( - $c->getDateTimeZone()->getTimeZone(), + $c->get(IDateTimeZone::class)->getTimeZone(), $c->getL10N('lib', $language) ); }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('DateTimeFormatter', IDateTimeFormatter::class); - $this->registerService(IUserMountCache::class, function (Server $c) { - $mountCache = new UserMountCache($c->getDatabaseConnection(), $c->getUserManager(), $c->getLogger()); + $this->registerService(IUserMountCache::class, function (ContainerInterface $c) { + $mountCache = new UserMountCache( + $c->get(IDBConnection::class), + $c->get(IUserManager::class), + $c->get(ILogger::class) + ); $listener = new UserMountCacheListener($mountCache); - $listener->listen($c->getUserManager()); + $listener->listen($c->get(IUserManager::class)); return $mountCache; }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('UserMountCache', IUserMountCache::class); - $this->registerService(IMountProviderCollection::class, function (Server $c) { + $this->registerService(IMountProviderCollection::class, function (ContainerInterface $c) { $loader = \OC\Files\Filesystem::getLoader(); - $mountCache = $c->query(IUserMountCache::class); + $mountCache = $c->get(IUserMountCache::class); $manager = new \OC\Files\Config\MountProviderCollection($loader, $mountCache); // builtin providers - $config = $c->getConfig(); + $config = $c->get(\OCP\IConfig::class); + $logger = $c->get(ILogger::class); $manager->registerProvider(new CacheMountProvider($config)); $manager->registerHomeProvider(new LocalHomeMountProvider()); $manager->registerHomeProvider(new ObjectHomeMountProvider($config)); + $manager->registerRootProvider(new ObjectStorePreviewCacheMountProvider($logger, $config)); return $manager; }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('MountConfigManager', IMountProviderCollection::class); - $this->registerService('IniWrapper', function ($c) { - return new IniGetWrapper(); - }); - $this->registerService('AsyncCommandBus', function (Server $c) { - $busClass = $c->getConfig()->getSystemValue('commandbus'); + /** @deprecated 20.0.0 */ + $this->registerDeprecatedAlias('IniWrapper', IniGetWrapper::class); + $this->registerService(IBus::class, function (ContainerInterface $c) { + $busClass = $c->get(\OCP\IConfig::class)->getSystemValue('commandbus'); if ($busClass) { [$app, $class] = explode('::', $busClass, 2); - if ($c->getAppManager()->isInstalled($app)) { + if ($c->get(IAppManager::class)->isInstalled($app)) { \OC_App::loadApp($app); - return $c->query($class); + return $c->get($class); } else { throw new ServiceUnavailableException("The app providing the command bus ($app) is not enabled"); } } else { - $jobList = $c->getJobList(); + $jobList = $c->get(IJobList::class); return new CronBus($jobList); } }); - $this->registerAlias(IBus::class, 'AsyncCommandBus'); - $this->registerService('TrustedDomainHelper', function ($c) { - return new TrustedDomainHelper($this->getConfig()); - }); - $this->registerService(Throttler::class, function (Server $c) { - return new Throttler( - $c->getDatabaseConnection(), - new TimeFactory(), - $c->getLogger(), - $c->getConfig() - ); - }); + $this->registerDeprecatedAlias('AsyncCommandBus', IBus::class); + /** @deprecated 20.0.0 */ + $this->registerDeprecatedAlias('TrustedDomainHelper', TrustedDomainHelper::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('Throttler', Throttler::class); - $this->registerService('IntegrityCodeChecker', function (Server $c) { + $this->registerService('IntegrityCodeChecker', function (ContainerInterface $c) { // IConfig and IAppManager requires a working database. This code // might however be called when ownCloud is not yet setup. - if (\OC::$server->getSystemConfig()->getValue('installed', false)) { - $config = $c->getConfig(); - $appManager = $c->getAppManager(); + if (\OC::$server->get(SystemConfig::class)->getValue('installed', false)) { + $config = $c->get(\OCP\IConfig::class); + $appManager = $c->get(IAppManager::class); } else { $config = null; $appManager = null; @@ -959,13 +937,13 @@ class Server extends ServerContainer implements IServerContainer { new FileAccessHelper(), new AppLocator(), $config, - $c->getMemCacheFactory(), + $c->get(ICacheFactory::class), $appManager, - $c->getTempManager(), - $c->getMimeTypeDetector() + $c->get(ITempManager::class), + $c->get(IMimeTypeDetector::class) ); }); - $this->registerService(\OCP\IRequest::class, function ($c) { + $this->registerService(\OCP\IRequest::class, function (ContainerInterface $c) { if (isset($this['urlParams'])) { $urlParams = $this['urlParams']; } else { @@ -993,29 +971,31 @@ class Server extends ServerContainer implements IServerContainer { : '', 'urlParams' => $urlParams, ], - $this->getSecureRandom(), - $this->getConfig(), - $this->getCsrfTokenManager(), + $this->get(ISecureRandom::class), + $this->get(\OCP\IConfig::class), + $this->get(CsrfTokenManager::class), $stream ); }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('Request', \OCP\IRequest::class); $this->registerService(IMailer::class, function (Server $c) { return new Mailer( - $c->getConfig(), - $c->getLogger(), - $c->query(Defaults::class), - $c->getURLGenerator(), + $c->get(\OCP\IConfig::class), + $c->get(ILogger::class), + $c->get(Defaults::class), + $c->get(IURLGenerator::class), $c->getL10N('lib'), - $c->query(IEventDispatcher::class), - $c->getL10NFactory() + $c->get(IEventDispatcher::class), + $c->get(IFactory::class) ); }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('Mailer', IMailer::class); - $this->registerService('LDAPProvider', function (Server $c) { - $config = $c->getConfig(); + $this->registerService('LDAPProvider', function (ContainerInterface $c) { + $config = $c->get(\OCP\IConfig::class); $factoryClass = $config->getSystemValue('ldapProviderFactory', null); if (is_null($factoryClass)) { throw new \Exception('ldapProviderFactory not set'); @@ -1024,20 +1004,20 @@ class Server extends ServerContainer implements IServerContainer { $factory = new $factoryClass($this); return $factory->getLDAPProvider(); }); - $this->registerService(ILockingProvider::class, function (Server $c) { - $ini = $c->getIniWrapper(); - $config = $c->getConfig(); + $this->registerService(ILockingProvider::class, function (ContainerInterface $c) { + $ini = $c->get(IniGetWrapper::class); + $config = $c->get(\OCP\IConfig::class); $ttl = $config->getSystemValue('filelocking.ttl', max(3600, $ini->getNumeric('max_execution_time'))); if ($config->getSystemValue('filelocking.enabled', true) or (defined('PHPUNIT_RUN') && PHPUNIT_RUN)) { /** @var \OC\Memcache\Factory $memcacheFactory */ - $memcacheFactory = $c->getMemCacheFactory(); + $memcacheFactory = $c->get(ICacheFactory::class); $memcache = $memcacheFactory->createLocking('lock'); if (!($memcache instanceof \OC\Memcache\NullCache)) { return new MemcacheLockingProvider($memcache, $ttl); } return new DBLockingProvider( - $c->getDatabaseConnection(), - $c->getLogger(), + $c->get(IDBConnection::class), + $c->get(ILogger::class), new TimeFactory(), $ttl, !\OC::$CLI @@ -1045,61 +1025,56 @@ class Server extends ServerContainer implements IServerContainer { } return new NoopLockingProvider(); }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('LockingProvider', ILockingProvider::class); - $this->registerService(IMountManager::class, function () { - return new \OC\Files\Mount\Manager(); - }); + $this->registerAlias(IMountManager::class, \OC\Files\Mount\Manager::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('MountManager', IMountManager::class); - $this->registerService(IMimeTypeDetector::class, function (Server $c) { + $this->registerService(IMimeTypeDetector::class, function (ContainerInterface $c) { return new \OC\Files\Type\Detection( - $c->getURLGenerator(), - $c->getLogger(), + $c->get(IURLGenerator::class), + $c->get(ILogger::class), \OC::$configDir, \OC::$SERVERROOT . '/resources/config/' ); }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('MimeTypeDetector', IMimeTypeDetector::class); - $this->registerService(IMimeTypeLoader::class, function (Server $c) { - return new \OC\Files\Type\Loader( - $c->getDatabaseConnection() - ); - }); + $this->registerAlias(IMimeTypeLoader::class, Loader::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('MimeTypeLoader', IMimeTypeLoader::class); $this->registerService(BundleFetcher::class, function () { return new BundleFetcher($this->getL10N('lib')); }); - $this->registerService(\OCP\Notification\IManager::class, function (Server $c) { - return new Manager( - $c->query(IValidator::class), - $c->getLogger() - ); - }); + $this->registerAlias(\OCP\Notification\IManager::class, Manager::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('NotificationManager', \OCP\Notification\IManager::class); - $this->registerService(CapabilitiesManager::class, function (Server $c) { - $manager = new CapabilitiesManager($c->getLogger()); + $this->registerService(CapabilitiesManager::class, function (ContainerInterface $c) { + $manager = new CapabilitiesManager($c->get(ILogger::class)); $manager->registerCapability(function () use ($c) { - return new \OC\OCS\CoreCapabilities($c->getConfig()); + return new \OC\OCS\CoreCapabilities($c->get(\OCP\IConfig::class)); }); $manager->registerCapability(function () use ($c) { - return $c->query(\OC\Security\Bruteforce\Capabilities::class); + return $c->get(\OC\Security\Bruteforce\Capabilities::class); }); return $manager; }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('CapabilitiesManager', CapabilitiesManager::class); $this->registerService(ICommentsManager::class, function (Server $c) { - $config = $c->getConfig(); + $config = $c->get(\OCP\IConfig::class); $factoryClass = $config->getSystemValue('comments.managerFactory', CommentsManagerFactory::class); /** @var \OCP\Comments\ICommentsManagerFactory $factory */ $factory = new $factoryClass($this); $manager = $factory->getManager(); $manager->registerDisplayNameResolver('user', function ($id) use ($c) { - $manager = $c->getUserManager(); + $manager = $c->get(IUserManager::class); $user = $manager->get($id); if (is_null($user)) { $l = $c->getL10N('core'); @@ -1112,8 +1087,10 @@ class Server extends ServerContainer implements IServerContainer { return $manager; }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('CommentsManager', ICommentsManager::class); + $this->registerAlias(\OC_Defaults::class, 'ThemingDefaults'); $this->registerService('ThemingDefaults', function (Server $c) { /* * Dark magic for autoloader. @@ -1128,47 +1105,42 @@ class Server extends ServerContainer implements IServerContainer { $classExists = false; } - if ($classExists && $c->getConfig()->getSystemValue('installed', false) && $c->getAppManager()->isInstalled('theming') && $c->getTrustedDomainHelper()->isTrustedDomain($c->getRequest()->getInsecureServerHost())) { + if ($classExists && $c->get(\OCP\IConfig::class)->getSystemValue('installed', false) && $c->get(IAppManager::class)->isInstalled('theming') && $c->getTrustedDomainHelper()->isTrustedDomain($c->getRequest()->getInsecureServerHost())) { return new ThemingDefaults( - $c->getConfig(), + $c->get(\OCP\IConfig::class), $c->getL10N('theming'), - $c->getURLGenerator(), - $c->getMemCacheFactory(), - new Util($c->getConfig(), $this->getAppManager(), $c->getAppDataDir('theming')), - new ImageManager($c->getConfig(), $c->getAppDataDir('theming'), $c->getURLGenerator(), $this->getMemCacheFactory(), $this->getLogger()), - $c->getAppManager(), - $c->getNavigationManager() + $c->get(IURLGenerator::class), + $c->get(ICacheFactory::class), + new Util($c->get(\OCP\IConfig::class), $this->get(IAppManager::class), $c->getAppDataDir('theming')), + new ImageManager( + $c->get(\OCP\IConfig::class), + $c->getAppDataDir('theming'), + $c->get(IURLGenerator::class), + $this->get(ICacheFactory::class), + $this->get(ILogger::class), + $this->get(ITempManager::class) + ), + $c->get(IAppManager::class), + $c->get(INavigationManager::class) ); } return new \OC_Defaults(); }); - $this->registerService(SCSSCacher::class, function (Server $c) { - return new SCSSCacher( - $c->getLogger(), - $c->query(\OC\Files\AppData\Factory::class), - $c->getURLGenerator(), - $c->getConfig(), - $c->getThemingDefaults(), - \OC::$SERVERROOT, - $this->getMemCacheFactory(), - $c->query(IconsCacher::class), - new TimeFactory() - ); - }); $this->registerService(JSCombiner::class, function (Server $c) { return new JSCombiner( $c->getAppDataDir('js'), - $c->getURLGenerator(), - $this->getMemCacheFactory(), - $c->getSystemConfig(), - $c->getLogger() + $c->get(IURLGenerator::class), + $this->get(ICacheFactory::class), + $c->get(SystemConfig::class), + $c->get(ILogger::class) ); }); $this->registerAlias(\OCP\EventDispatcher\IEventDispatcher::class, \OC\EventDispatcher\EventDispatcher::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('EventDispatcher', \OC\EventDispatcher\SymfonyAdapter::class); $this->registerAlias(EventDispatcherInterface::class, \OC\EventDispatcher\SymfonyAdapter::class); - $this->registerService('CryptoWrapper', function (Server $c) { + $this->registerService('CryptoWrapper', function (ContainerInterface $c) { // FIXME: Instantiiated here due to cyclic dependency $request = new Request( [ @@ -1182,66 +1154,54 @@ class Server extends ServerContainer implements IServerContainer { ? $_SERVER['REQUEST_METHOD'] : null, ], - $c->getSecureRandom(), - $c->getConfig() + $c->get(ISecureRandom::class), + $c->get(\OCP\IConfig::class) ); return new CryptoWrapper( - $c->getConfig(), - $c->getCrypto(), - $c->getSecureRandom(), + $c->get(\OCP\IConfig::class), + $c->get(ICrypto::class), + $c->get(ISecureRandom::class), $request ); }); - $this->registerService(CsrfTokenManager::class, function (Server $c) { - $tokenGenerator = new CsrfTokenGenerator($c->getSecureRandom()); - - return new CsrfTokenManager( - $tokenGenerator, - $c->query(SessionStorage::class) - ); - }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('CsrfTokenManager', CsrfTokenManager::class); - $this->registerService(SessionStorage::class, function (Server $c) { - return new SessionStorage($c->getSession()); + $this->registerService(SessionStorage::class, function (ContainerInterface $c) { + return new SessionStorage($c->get(ISession::class)); }); $this->registerAlias(\OCP\Security\IContentSecurityPolicyManager::class, ContentSecurityPolicyManager::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('ContentSecurityPolicyManager', ContentSecurityPolicyManager::class); - $this->registerService('ContentSecurityPolicyNonceManager', function (Server $c) { - return new ContentSecurityPolicyNonceManager( - $c->getCsrfTokenManager(), - $c->getRequest() - ); - }); - - $this->registerService(\OCP\Share\IManager::class, function (Server $c) { - $config = $c->getConfig(); + $this->registerService(\OCP\Share\IManager::class, function (IServerContainer $c) { + $config = $c->get(\OCP\IConfig::class); $factoryClass = $config->getSystemValue('sharing.managerFactory', ProviderFactory::class); /** @var \OCP\Share\IProviderFactory $factory */ $factory = new $factoryClass($this); $manager = new \OC\Share20\Manager( - $c->getLogger(), - $c->getConfig(), - $c->getSecureRandom(), - $c->getHasher(), - $c->getMountManager(), - $c->getGroupManager(), + $c->get(ILogger::class), + $c->get(\OCP\IConfig::class), + $c->get(ISecureRandom::class), + $c->get(IHasher::class), + $c->get(IMountManager::class), + $c->get(IGroupManager::class), $c->getL10N('lib'), - $c->getL10NFactory(), + $c->get(IFactory::class), $factory, - $c->getUserManager(), - $c->getLazyRootFolder(), - $c->getEventDispatcher(), - $c->getMailer(), - $c->getURLGenerator(), - $c->getThemingDefaults(), - $c->query(IEventDispatcher::class) + $c->get(IUserManager::class), + $c->get(IRootFolder::class), + $c->get(SymfonyAdapter::class), + $c->get(IMailer::class), + $c->get(IURLGenerator::class), + $c->get('ThemingDefaults'), + $c->get(IEventDispatcher::class) ); return $manager; }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('ShareManager', \OCP\Share\IManager::class); $this->registerService(\OCP\Collaboration\Collaborators\ISearch::class, function (Server $c) { @@ -1256,6 +1216,7 @@ class Server extends ServerContainer implements IServerContainer { return $instance; }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('CollaboratorSearch', \OCP\Collaboration\Collaborators\ISearch::class); $this->registerAlias(\OCP\Collaboration\Collaborators\ISearchResult::class, \OC\Collaboration\Collaborators\SearchResult::class); @@ -1264,40 +1225,41 @@ class Server extends ServerContainer implements IServerContainer { $this->registerAlias(\OCP\Collaboration\Resources\IProviderManager::class, \OC\Collaboration\Resources\ProviderManager::class); $this->registerAlias(\OCP\Collaboration\Resources\IManager::class, \OC\Collaboration\Resources\Manager::class); - $this->registerService('SettingsManager', function (Server $c) { - $manager = new \OC\Settings\Manager( - $c->getLogger(), - $c->getL10NFactory(), - $c->getURLGenerator(), - $c - ); - return $manager; - }); - $this->registerService(\OC\Files\AppData\Factory::class, function (Server $c) { + $this->registerDeprecatedAlias('SettingsManager', \OC\Settings\Manager::class); + $this->registerAlias(\OCP\Settings\IManager::class, \OC\Settings\Manager::class); + $this->registerService(\OC\Files\AppData\Factory::class, function (ContainerInterface $c) { return new \OC\Files\AppData\Factory( - $c->getRootFolder(), - $c->getSystemConfig() + $c->get(IRootFolder::class), + $c->get(SystemConfig::class) ); }); - $this->registerService('LockdownManager', function (Server $c) { + $this->registerService('LockdownManager', function (ContainerInterface $c) { return new LockdownManager(function () use ($c) { - return $c->getSession(); + return $c->get(ISession::class); }); }); - $this->registerService(\OCP\OCS\IDiscoveryService::class, function (Server $c) { - return new DiscoveryService($c->getMemCacheFactory(), $c->getHTTPClientService()); + $this->registerService(\OCP\OCS\IDiscoveryService::class, function (ContainerInterface $c) { + return new DiscoveryService( + $c->get(ICacheFactory::class), + $c->get(IClientService::class) + ); }); - $this->registerService(ICloudIdManager::class, function (Server $c) { - return new CloudIdManager(); + $this->registerService(ICloudIdManager::class, function (ContainerInterface $c) { + return new CloudIdManager($c->get(\OCP\Contacts\IManager::class)); }); $this->registerAlias(\OCP\GlobalScale\IConfig::class, \OC\GlobalScale\Config::class); - $this->registerService(ICloudFederationProviderManager::class, function (Server $c) { - return new CloudFederationProviderManager($c->getAppManager(), $c->getHTTPClientService(), $c->getCloudIdManager(), $c->getLogger()); + $this->registerService(ICloudFederationProviderManager::class, function (ContainerInterface $c) { + return new CloudFederationProviderManager( + $c->get(IAppManager::class), + $c->get(IClientService::class), + $c->get(ICloudIdManager::class), + $c->get(ILogger::class) + ); }); $this->registerService(ICloudFederationFactory::class, function (Server $c) { @@ -1305,9 +1267,11 @@ class Server extends ServerContainer implements IServerContainer { }); $this->registerAlias(\OCP\AppFramework\Utility\IControllerMethodReflector::class, \OC\AppFramework\Utility\ControllerMethodReflector::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('ControllerMethodReflector', \OCP\AppFramework\Utility\IControllerMethodReflector::class); $this->registerAlias(\OCP\AppFramework\Utility\ITimeFactory::class, \OC\AppFramework\Utility\TimeFactory::class); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('TimeFactory', \OCP\AppFramework\Utility\ITimeFactory::class); $this->registerService(Defaults::class, function (Server $c) { @@ -1315,52 +1279,43 @@ class Server extends ServerContainer implements IServerContainer { $c->getThemingDefaults() ); }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('Defaults', \OCP\Defaults::class); - $this->registerService(\OCP\ISession::class, function (SimpleContainer $c) { - return $c->query(\OCP\IUserSession::class)->getSession(); - }); + $this->registerService(\OCP\ISession::class, function (ContainerInterface $c) { + return $c->get(\OCP\IUserSession::class)->getSession(); + }, false); - $this->registerService(IShareHelper::class, function (Server $c) { + $this->registerService(IShareHelper::class, function (ContainerInterface $c) { return new ShareHelper( - $c->query(\OCP\Share\IManager::class) + $c->get(\OCP\Share\IManager::class) ); }); - $this->registerService(Installer::class, function (Server $c) { + $this->registerService(Installer::class, function (ContainerInterface $c) { return new Installer( - $c->getAppFetcher(), - $c->getHTTPClientService(), - $c->getTempManager(), - $c->getLogger(), - $c->getConfig(), + $c->get(AppFetcher::class), + $c->get(IClientService::class), + $c->get(ITempManager::class), + $c->get(ILogger::class), + $c->get(\OCP\IConfig::class), \OC::$CLI ); }); - $this->registerService(IApiFactory::class, function (Server $c) { - return new ApiFactory($c->getHTTPClientService()); + $this->registerService(IApiFactory::class, function (ContainerInterface $c) { + return new ApiFactory($c->get(IClientService::class)); }); - $this->registerService(IInstanceFactory::class, function (Server $c) { - $memcacheFactory = $c->getMemCacheFactory(); - return new InstanceFactory($memcacheFactory->createLocal('remoteinstance.'), $c->getHTTPClientService()); + $this->registerService(IInstanceFactory::class, function (ContainerInterface $c) { + $memcacheFactory = $c->get(ICacheFactory::class); + return new InstanceFactory($memcacheFactory->createLocal('remoteinstance.'), $c->get(IClientService::class)); }); - $this->registerService(IContactsStore::class, function (Server $c) { - return new ContactsStore( - $c->getContactsManager(), - $c->getConfig(), - $c->getUserManager(), - $c->getGroupManager() - ); - }); $this->registerAlias(IContactsStore::class, ContactsStore::class); $this->registerAlias(IAccountManager::class, AccountManager::class); - $this->registerService(IStorageFactory::class, function () { - return new StorageFactory(); - }); + $this->registerAlias(IStorageFactory::class, StorageFactory::class); $this->registerAlias(IDashboardManager::class, DashboardManager::class); $this->registerAlias(\OCP\Dashboard\IManager::class, \OC\Dashboard\Manager::class); @@ -1370,45 +1325,47 @@ class Server extends ServerContainer implements IServerContainer { $this->registerAlias(IInitialStateService::class, InitialStateService::class); + $this->registerAlias(\OCP\UserStatus\IManager::class, \OC\UserStatus\Manager::class); + $this->connectDispatcher(); } public function boot() { /** @var HookConnector $hookConnector */ - $hookConnector = $this->query(HookConnector::class); + $hookConnector = $this->get(HookConnector::class); $hookConnector->viewToNode(); } /** * @return \OCP\Calendar\IManager - * @deprecated + * @deprecated 20.0.0 */ public function getCalendarManager() { - return $this->query(\OC\Calendar\Manager::class); + return $this->get(\OC\Calendar\Manager::class); } /** * @return \OCP\Calendar\Resource\IManager - * @deprecated + * @deprecated 20.0.0 */ public function getCalendarResourceBackendManager() { - return $this->query(\OC\Calendar\Resource\Manager::class); + return $this->get(\OC\Calendar\Resource\Manager::class); } /** * @return \OCP\Calendar\Room\IManager - * @deprecated + * @deprecated 20.0.0 */ public function getCalendarRoomBackendManager() { - return $this->query(\OC\Calendar\Room\Manager::class); + return $this->get(\OC\Calendar\Room\Manager::class); } private function connectDispatcher() { - $dispatcher = $this->getEventDispatcher(); + $dispatcher = $this->get(SymfonyAdapter::class); // Delete avatar on user deletion $dispatcher->addListener('OCP\IUser::preDelete', function (GenericEvent $e) { - $logger = $this->getLogger(); + $logger = $this->get(ILogger::class); $manager = $this->getAvatarManager(); /** @var IUser $user */ $user = $e->getSubject(); @@ -1446,40 +1403,41 @@ class Server extends ServerContainer implements IServerContainer { }); /** @var IEventDispatcher $eventDispatched */ - $eventDispatched = $this->query(IEventDispatcher::class); + $eventDispatched = $this->get(IEventDispatcher::class); $eventDispatched->addServiceListener(LoginFailed::class, LoginFailedListener::class); + $eventDispatched->addServiceListener(PostLoginEvent::class, UserLoggedInListener::class); } /** * @return \OCP\Contacts\IManager - * @deprecated + * @deprecated 20.0.0 */ public function getContactsManager() { - return $this->query(\OCP\Contacts\IManager::class); + return $this->get(\OCP\Contacts\IManager::class); } /** * @return \OC\Encryption\Manager - * @deprecated + * @deprecated 20.0.0 */ public function getEncryptionManager() { - return $this->query(\OCP\Encryption\IManager::class); + return $this->get(\OCP\Encryption\IManager::class); } /** * @return \OC\Encryption\File - * @deprecated + * @deprecated 20.0.0 */ public function getEncryptionFilesHelper() { - return $this->query('EncryptionFileHelper'); + return $this->get(IFile::class); } /** * @return \OCP\Encryption\Keys\IStorage - * @deprecated + * @deprecated 20.0.0 */ public function getEncryptionKeyStorage() { - return $this->query('EncryptionKeyStorage'); + return $this->get(IStorage::class); } /** @@ -1488,20 +1446,20 @@ class Server extends ServerContainer implements IServerContainer { * In case the current execution was not initiated by a web request null is returned * * @return \OCP\IRequest - * @deprecated + * @deprecated 20.0.0 */ public function getRequest() { - return $this->query(IRequest::class); + return $this->get(IRequest::class); } /** * Returns the preview manager which can create preview images for a given file * * @return IPreview - * @deprecated + * @deprecated 20.0.0 */ public function getPreviewManager() { - return $this->query(IPreview::class); + return $this->get(IPreview::class); } /** @@ -1509,10 +1467,10 @@ class Server extends ServerContainer implements IServerContainer { * * @see \OCP\ITagManager::load() * @return ITagManager - * @deprecated + * @deprecated 20.0.0 */ public function getTagManager() { - return $this->query(ITagManager::class); + return $this->get(ITagManager::class); } /** @@ -1521,10 +1479,10 @@ class Server extends ServerContainer implements IServerContainer { * @return ISystemTagManager * * @since 9.0.0 - * @deprecated + * @deprecated 20.0.0 */ public function getSystemTagManager() { - return $this->query(ISystemTagManager::class); + return $this->get(ISystemTagManager::class); } /** @@ -1533,30 +1491,30 @@ class Server extends ServerContainer implements IServerContainer { * @return ISystemTagObjectMapper * * @since 9.0.0 - * @deprecated + * @deprecated 20.0.0 */ public function getSystemTagObjectMapper() { - return $this->query(ISystemTagObjectMapper::class); + return $this->get(ISystemTagObjectMapper::class); } /** * Returns the avatar manager, used for avatar functionality * * @return IAvatarManager - * @deprecated + * @deprecated 20.0.0 */ public function getAvatarManager() { - return $this->query(IAvatarManager::class); + return $this->get(IAvatarManager::class); } /** * Returns the root folder of ownCloud's data directory * * @return IRootFolder - * @deprecated + * @deprecated 20.0.0 */ public function getRootFolder() { - return $this->query(IRootFolder::class); + return $this->get(IRootFolder::class); } /** @@ -1565,9 +1523,10 @@ class Server extends ServerContainer implements IServerContainer { * is actually used. * * @return IRootFolder + * @deprecated 20.0.0 */ public function getLazyRootFolder() { - return $this->query(IRootFolder::class); + return $this->get(IRootFolder::class); } /** @@ -1575,109 +1534,109 @@ class Server extends ServerContainer implements IServerContainer { * * @param string $userId user ID * @return \OCP\Files\Folder|null - * @deprecated + * @deprecated 20.0.0 */ public function getUserFolder($userId = null) { if ($userId === null) { - $user = $this->getUserSession()->getUser(); + $user = $this->get(IUserSession::class)->getUser(); if (!$user) { return null; } $userId = $user->getUID(); } - $root = $this->getRootFolder(); + $root = $this->get(IRootFolder::class); return $root->getUserFolder($userId); } /** * @return \OC\User\Manager - * @deprecated + * @deprecated 20.0.0 */ public function getUserManager() { - return $this->query(IUserManager::class); + return $this->get(IUserManager::class); } /** * @return \OC\Group\Manager - * @deprecated + * @deprecated 20.0.0 */ public function getGroupManager() { - return $this->query(IGroupManager::class); + return $this->get(IGroupManager::class); } /** * @return \OC\User\Session - * @deprecated + * @deprecated 20.0.0 */ public function getUserSession() { - return $this->query(IUserSession::class); + return $this->get(IUserSession::class); } /** * @return \OCP\ISession - * @deprecated + * @deprecated 20.0.0 */ public function getSession() { - return $this->getUserSession()->getSession(); + return $this->get(IUserSession::class)->getSession(); } /** * @param \OCP\ISession $session */ public function setSession(\OCP\ISession $session) { - $this->query(SessionStorage::class)->setSession($session); - $this->getUserSession()->setSession($session); - $this->query(Store::class)->setSession($session); + $this->get(SessionStorage::class)->setSession($session); + $this->get(IUserSession::class)->setSession($session); + $this->get(Store::class)->setSession($session); } /** * @return \OC\Authentication\TwoFactorAuth\Manager - * @deprecated + * @deprecated 20.0.0 */ public function getTwoFactorAuthManager() { - return $this->query(\OC\Authentication\TwoFactorAuth\Manager::class); + return $this->get(\OC\Authentication\TwoFactorAuth\Manager::class); } /** * @return \OC\NavigationManager - * @deprecated + * @deprecated 20.0.0 */ public function getNavigationManager() { - return $this->query(INavigationManager::class); + return $this->get(INavigationManager::class); } /** * @return \OCP\IConfig - * @deprecated + * @deprecated 20.0.0 */ public function getConfig() { - return $this->query(AllConfig::class); + return $this->get(AllConfig::class); } /** * @return \OC\SystemConfig - * @deprecated + * @deprecated 20.0.0 */ public function getSystemConfig() { - return $this->query(SystemConfig::class); + return $this->get(SystemConfig::class); } /** * Returns the app config manager * * @return IAppConfig - * @deprecated + * @deprecated 20.0.0 */ public function getAppConfig() { - return $this->query(IAppConfig::class); + return $this->get(IAppConfig::class); } /** * @return IFactory - * @deprecated + * @deprecated 20.0.0 */ public function getL10NFactory() { - return $this->query(IFactory::class); + return $this->get(IFactory::class); } /** @@ -1686,26 +1645,26 @@ class Server extends ServerContainer implements IServerContainer { * @param string $app appid * @param string $lang * @return IL10N - * @deprecated + * @deprecated 20.0.0 */ public function getL10N($app, $lang = null) { - return $this->getL10NFactory()->get($app, $lang); + return $this->get(IFactory::class)->get($app, $lang); } /** * @return IURLGenerator - * @deprecated + * @deprecated 20.0.0 */ public function getURLGenerator() { - return $this->query(IURLGenerator::class); + return $this->get(IURLGenerator::class); } /** * @return AppFetcher - * @deprecated + * @deprecated 20.0.0 */ public function getAppFetcher() { - return $this->query(AppFetcher::class); + return $this->get(AppFetcher::class); } /** @@ -1716,27 +1675,27 @@ class Server extends ServerContainer implements IServerContainer { * @deprecated 8.1.0 use getMemCacheFactory to obtain a proper cache */ public function getCache() { - return $this->query(ICache::class); + return $this->get(ICache::class); } /** * Returns an \OCP\CacheFactory instance * * @return \OCP\ICacheFactory - * @deprecated + * @deprecated 20.0.0 */ public function getMemCacheFactory() { - return $this->query(Factory::class); + return $this->get(ICacheFactory::class); } /** * Returns an \OC\RedisFactory instance * * @return \OC\RedisFactory - * @deprecated + * @deprecated 20.0.0 */ public function getGetRedisFactory() { - return $this->query('RedisFactory'); + return $this->get('RedisFactory'); } @@ -1744,151 +1703,135 @@ class Server extends ServerContainer implements IServerContainer { * Returns the current session * * @return \OCP\IDBConnection - * @deprecated + * @deprecated 20.0.0 */ public function getDatabaseConnection() { - return $this->query(IDBConnection::class); + return $this->get(IDBConnection::class); } /** * Returns the activity manager * * @return \OCP\Activity\IManager - * @deprecated + * @deprecated 20.0.0 */ public function getActivityManager() { - return $this->query(\OCP\Activity\IManager::class); + return $this->get(\OCP\Activity\IManager::class); } /** * Returns an job list for controlling background jobs * * @return IJobList - * @deprecated + * @deprecated 20.0.0 */ public function getJobList() { - return $this->query(IJobList::class); + return $this->get(IJobList::class); } /** * Returns a logger instance * * @return ILogger - * @deprecated + * @deprecated 20.0.0 */ public function getLogger() { - return $this->query(ILogger::class); + return $this->get(ILogger::class); } /** * @return ILogFactory * @throws \OCP\AppFramework\QueryException - * @deprecated + * @deprecated 20.0.0 */ public function getLogFactory() { - return $this->query(ILogFactory::class); + return $this->get(ILogFactory::class); } /** * Returns a router for generating and matching urls * * @return IRouter - * @deprecated + * @deprecated 20.0.0 */ public function getRouter() { - return $this->query(IRouter::class); + return $this->get(IRouter::class); } /** * Returns a search instance * * @return ISearch - * @deprecated + * @deprecated 20.0.0 */ public function getSearch() { - return $this->query(ISearch::class); + return $this->get(ISearch::class); } /** * Returns a SecureRandom instance * * @return \OCP\Security\ISecureRandom - * @deprecated + * @deprecated 20.0.0 */ public function getSecureRandom() { - return $this->query(ISecureRandom::class); + return $this->get(ISecureRandom::class); } /** * Returns a Crypto instance * * @return ICrypto - * @deprecated + * @deprecated 20.0.0 */ public function getCrypto() { - return $this->query(ICrypto::class); + return $this->get(ICrypto::class); } /** * Returns a Hasher instance * * @return IHasher - * @deprecated + * @deprecated 20.0.0 */ public function getHasher() { - return $this->query(IHasher::class); + return $this->get(IHasher::class); } /** * Returns a CredentialsManager instance * * @return ICredentialsManager - * @deprecated + * @deprecated 20.0.0 */ public function getCredentialsManager() { - return $this->query(ICredentialsManager::class); + return $this->get(ICredentialsManager::class); } /** - * Get the certificate manager for the user + * Get the certificate manager * - * @param string $userId (optional) if not specified the current loggedin user is used, use null to get the system certificate manager - * @return \OCP\ICertificateManager | null if $uid is null and no user is logged in - * @deprecated - */ - public function getCertificateManager($userId = '') { - if ($userId === '') { - $userSession = $this->getUserSession(); - $user = $userSession->getUser(); - if (is_null($user)) { - return null; - } - $userId = $user->getUID(); - } - return new CertificateManager( - $userId, - new View(), - $this->getConfig(), - $this->getLogger(), - $this->getSecureRandom() - ); + * @return \OCP\ICertificateManager + */ + public function getCertificateManager() { + return $this->get(ICertificateManager::class); } /** * Returns an instance of the HTTP client service * * @return IClientService - * @deprecated + * @deprecated 20.0.0 */ public function getHTTPClientService() { - return $this->query(IClientService::class); + return $this->get(IClientService::class); } /** * Create a new event source * * @return \OCP\IEventSource - * @deprecated + * @deprecated 20.0.0 */ public function createEventSource() { return new \OC_EventSource(); @@ -1900,10 +1843,10 @@ class Server extends ServerContainer implements IServerContainer { * The returned logger only logs data when debug mode is enabled * * @return IEventLogger - * @deprecated + * @deprecated 20.0.0 */ public function getEventLogger() { - return $this->query(IEventLogger::class); + return $this->get(IEventLogger::class); } /** @@ -1912,47 +1855,47 @@ class Server extends ServerContainer implements IServerContainer { * The returned logger only logs data when debug mode is enabled * * @return IQueryLogger - * @deprecated + * @deprecated 20.0.0 */ public function getQueryLogger() { - return $this->query(IQueryLogger::class); + return $this->get(IQueryLogger::class); } /** * Get the manager for temporary files and folders * * @return \OCP\ITempManager - * @deprecated + * @deprecated 20.0.0 */ public function getTempManager() { - return $this->query(ITempManager::class); + return $this->get(ITempManager::class); } /** * Get the app manager * * @return \OCP\App\IAppManager - * @deprecated + * @deprecated 20.0.0 */ public function getAppManager() { - return $this->query(IAppManager::class); + return $this->get(IAppManager::class); } /** * Creates a new mailer * * @return IMailer - * @deprecated + * @deprecated 20.0.0 */ public function getMailer() { - return $this->query(IMailer::class); + return $this->get(IMailer::class); } /** * Get the webroot * * @return string - * @deprecated + * @deprecated 20.0.0 */ public function getWebRoot() { return $this->webRoot; @@ -1960,62 +1903,62 @@ class Server extends ServerContainer implements IServerContainer { /** * @return \OC\OCSClient - * @deprecated + * @deprecated 20.0.0 */ public function getOcsClient() { - return $this->query('OcsClient'); + return $this->get('OcsClient'); } /** * @return IDateTimeZone - * @deprecated + * @deprecated 20.0.0 */ public function getDateTimeZone() { - return $this->query(IDateTimeZone::class); + return $this->get(IDateTimeZone::class); } /** * @return IDateTimeFormatter - * @deprecated + * @deprecated 20.0.0 */ public function getDateTimeFormatter() { - return $this->query(IDateTimeFormatter::class); + return $this->get(IDateTimeFormatter::class); } /** * @return IMountProviderCollection - * @deprecated + * @deprecated 20.0.0 */ public function getMountProviderCollection() { - return $this->query(IMountProviderCollection::class); + return $this->get(IMountProviderCollection::class); } /** * Get the IniWrapper * * @return IniGetWrapper - * @deprecated + * @deprecated 20.0.0 */ public function getIniWrapper() { - return $this->query('IniWrapper'); + return $this->get(IniGetWrapper::class); } /** * @return \OCP\Command\IBus - * @deprecated + * @deprecated 20.0.0 */ public function getCommandBus() { - return $this->query('AsyncCommandBus'); + return $this->get(IBus::class); } /** * Get the trusted domain helper * * @return TrustedDomainHelper - * @deprecated + * @deprecated 20.0.0 */ public function getTrustedDomainHelper() { - return $this->query('TrustedDomainHelper'); + return $this->get(TrustedDomainHelper::class); } /** @@ -2023,56 +1966,56 @@ class Server extends ServerContainer implements IServerContainer { * * @return ILockingProvider * @since 8.1.0 - * @deprecated + * @deprecated 20.0.0 */ public function getLockingProvider() { - return $this->query(ILockingProvider::class); + return $this->get(ILockingProvider::class); } /** * @return IMountManager - * @deprecated + * @deprecated 20.0.0 **/ public function getMountManager() { - return $this->query(IMountManager::class); + return $this->get(IMountManager::class); } /** * @return IUserMountCache - * @deprecated + * @deprecated 20.0.0 */ public function getUserMountCache() { - return $this->query(IUserMountCache::class); + return $this->get(IUserMountCache::class); } /** * Get the MimeTypeDetector * * @return IMimeTypeDetector - * @deprecated + * @deprecated 20.0.0 */ public function getMimeTypeDetector() { - return $this->query(IMimeTypeDetector::class); + return $this->get(IMimeTypeDetector::class); } /** * Get the MimeTypeLoader * * @return IMimeTypeLoader - * @deprecated + * @deprecated 20.0.0 */ public function getMimeTypeLoader() { - return $this->query(IMimeTypeLoader::class); + return $this->get(IMimeTypeLoader::class); } /** * Get the manager of all the capabilities * * @return CapabilitiesManager - * @deprecated + * @deprecated 20.0.0 */ public function getCapabilitiesManager() { - return $this->query(CapabilitiesManager::class); + return $this->get(CapabilitiesManager::class); } /** @@ -2083,7 +2026,7 @@ class Server extends ServerContainer implements IServerContainer { * @deprecated 18.0.0 use \OCP\EventDispatcher\IEventDispatcher */ public function getEventDispatcher() { - return $this->query(\OC\EventDispatcher\SymfonyAdapter::class); + return $this->get(\OC\EventDispatcher\SymfonyAdapter::class); } /** @@ -2091,230 +2034,230 @@ class Server extends ServerContainer implements IServerContainer { * * @return \OCP\Notification\IManager * @since 8.2.0 - * @deprecated + * @deprecated 20.0.0 */ public function getNotificationManager() { - return $this->query(\OCP\Notification\IManager::class); + return $this->get(\OCP\Notification\IManager::class); } /** * @return ICommentsManager - * @deprecated + * @deprecated 20.0.0 */ public function getCommentsManager() { - return $this->query(ICommentsManager::class); + return $this->get(ICommentsManager::class); } /** * @return \OCA\Theming\ThemingDefaults - * @deprecated + * @deprecated 20.0.0 */ public function getThemingDefaults() { - return $this->query('ThemingDefaults'); + return $this->get('ThemingDefaults'); } /** * @return \OC\IntegrityCheck\Checker - * @deprecated + * @deprecated 20.0.0 */ public function getIntegrityCodeChecker() { - return $this->query('IntegrityCodeChecker'); + return $this->get('IntegrityCodeChecker'); } /** * @return \OC\Session\CryptoWrapper - * @deprecated + * @deprecated 20.0.0 */ public function getSessionCryptoWrapper() { - return $this->query('CryptoWrapper'); + return $this->get('CryptoWrapper'); } /** * @return CsrfTokenManager - * @deprecated + * @deprecated 20.0.0 */ public function getCsrfTokenManager() { - return $this->query(CsrfTokenManager::class); + return $this->get(CsrfTokenManager::class); } /** * @return Throttler - * @deprecated + * @deprecated 20.0.0 */ public function getBruteForceThrottler() { - return $this->query(Throttler::class); + return $this->get(Throttler::class); } /** * @return IContentSecurityPolicyManager - * @deprecated + * @deprecated 20.0.0 */ public function getContentSecurityPolicyManager() { - return $this->query(ContentSecurityPolicyManager::class); + return $this->get(ContentSecurityPolicyManager::class); } /** * @return ContentSecurityPolicyNonceManager - * @deprecated + * @deprecated 20.0.0 */ public function getContentSecurityPolicyNonceManager() { - return $this->query('ContentSecurityPolicyNonceManager'); + return $this->get(ContentSecurityPolicyNonceManager::class); } /** * Not a public API as of 8.2, wait for 9.0 * * @return \OCA\Files_External\Service\BackendService - * @deprecated + * @deprecated 20.0.0 */ public function getStoragesBackendService() { - return $this->query(BackendService::class); + return $this->get(BackendService::class); } /** * Not a public API as of 8.2, wait for 9.0 * * @return \OCA\Files_External\Service\GlobalStoragesService - * @deprecated + * @deprecated 20.0.0 */ public function getGlobalStoragesService() { - return $this->query(GlobalStoragesService::class); + return $this->get(GlobalStoragesService::class); } /** * Not a public API as of 8.2, wait for 9.0 * * @return \OCA\Files_External\Service\UserGlobalStoragesService - * @deprecated + * @deprecated 20.0.0 */ public function getUserGlobalStoragesService() { - return $this->query(UserGlobalStoragesService::class); + return $this->get(UserGlobalStoragesService::class); } /** * Not a public API as of 8.2, wait for 9.0 * * @return \OCA\Files_External\Service\UserStoragesService - * @deprecated + * @deprecated 20.0.0 */ public function getUserStoragesService() { - return $this->query(UserStoragesService::class); + return $this->get(UserStoragesService::class); } /** * @return \OCP\Share\IManager - * @deprecated + * @deprecated 20.0.0 */ public function getShareManager() { - return $this->query(\OCP\Share\IManager::class); + return $this->get(\OCP\Share\IManager::class); } /** * @return \OCP\Collaboration\Collaborators\ISearch - * @deprecated + * @deprecated 20.0.0 */ public function getCollaboratorSearch() { - return $this->query(\OCP\Collaboration\Collaborators\ISearch::class); + return $this->get(\OCP\Collaboration\Collaborators\ISearch::class); } /** * @return \OCP\Collaboration\AutoComplete\IManager - * @deprecated + * @deprecated 20.0.0 */ public function getAutoCompleteManager() { - return $this->query(IManager::class); + return $this->get(IManager::class); } /** * Returns the LDAP Provider * * @return \OCP\LDAP\ILDAPProvider - * @deprecated + * @deprecated 20.0.0 */ public function getLDAPProvider() { - return $this->query('LDAPProvider'); + return $this->get('LDAPProvider'); } /** * @return \OCP\Settings\IManager - * @deprecated + * @deprecated 20.0.0 */ public function getSettingsManager() { - return $this->query('SettingsManager'); + return $this->get(\OC\Settings\Manager::class); } /** * @return \OCP\Files\IAppData - * @deprecated + * @deprecated 20.0.0 */ public function getAppDataDir($app) { /** @var \OC\Files\AppData\Factory $factory */ - $factory = $this->query(\OC\Files\AppData\Factory::class); + $factory = $this->get(\OC\Files\AppData\Factory::class); return $factory->get($app); } /** * @return \OCP\Lockdown\ILockdownManager - * @deprecated + * @deprecated 20.0.0 */ public function getLockdownManager() { - return $this->query('LockdownManager'); + return $this->get('LockdownManager'); } /** * @return \OCP\Federation\ICloudIdManager - * @deprecated + * @deprecated 20.0.0 */ public function getCloudIdManager() { - return $this->query(ICloudIdManager::class); + return $this->get(ICloudIdManager::class); } /** * @return \OCP\GlobalScale\IConfig - * @deprecated + * @deprecated 20.0.0 */ public function getGlobalScaleConfig() { - return $this->query(IConfig::class); + return $this->get(IConfig::class); } /** * @return \OCP\Federation\ICloudFederationProviderManager - * @deprecated + * @deprecated 20.0.0 */ public function getCloudFederationProviderManager() { - return $this->query(ICloudFederationProviderManager::class); + return $this->get(ICloudFederationProviderManager::class); } /** * @return \OCP\Remote\Api\IApiFactory - * @deprecated + * @deprecated 20.0.0 */ public function getRemoteApiFactory() { - return $this->query(IApiFactory::class); + return $this->get(IApiFactory::class); } /** * @return \OCP\Federation\ICloudFederationFactory - * @deprecated + * @deprecated 20.0.0 */ public function getCloudFederationFactory() { - return $this->query(ICloudFederationFactory::class); + return $this->get(ICloudFederationFactory::class); } /** * @return \OCP\Remote\IInstanceFactory - * @deprecated + * @deprecated 20.0.0 */ public function getRemoteInstanceFactory() { - return $this->query(IInstanceFactory::class); + return $this->get(IInstanceFactory::class); } /** * @return IStorageFactory - * @deprecated + * @deprecated 20.0.0 */ public function getStorageFactory() { - return $this->query(IStorageFactory::class); + return $this->get(IStorageFactory::class); } /** @@ -2322,10 +2265,10 @@ class Server extends ServerContainer implements IServerContainer { * * @return GeneratorHelper * @since 17.0.0 - * @deprecated + * @deprecated 20.0.0 */ public function getGeneratorHelper() { - return $this->query(\OC\Preview\GeneratorHelper::class); + return $this->get(\OC\Preview\GeneratorHelper::class); } private function registerDeprecatedAlias(string $alias, string $target) { @@ -2333,7 +2276,7 @@ class Server extends ServerContainer implements IServerContainer { try { /** @var ILogger $logger */ $logger = $container->get(ILogger::class); - $logger->debug('The requested alias "' . $alias . '" is depreacted. Please request "' . $target . '" directly. This alias will be removed in a future Nextcloud version.', ['app' => 'serverDI']); + $logger->debug('The requested alias "' . $alias . '" is deprecated. Please request "' . $target . '" directly. This alias will be removed in a future Nextcloud version.', ['app' => 'serverDI']); } catch (ContainerExceptionInterface $e) { // Could not get logger. Continue } diff --git a/lib/private/ServerContainer.php b/lib/private/ServerContainer.php index 72275ac1205..50592f36ada 100644 --- a/lib/private/ServerContainer.php +++ b/lib/private/ServerContainer.php @@ -133,6 +133,12 @@ class ServerContainer extends SimpleContainer { public function query(string $name, bool $autoload = true) { $name = $this->sanitizeName($name); + try { + return parent::query($name, false); + } catch (QueryException $e) { + // Continue with general autoloading then + } + // In case the service starts with OCA\ we try to find the service in // the apps container first. if (($appContainer = $this->getAppContainerForService($name)) !== null) { diff --git a/lib/private/Session/CryptoSessionData.php b/lib/private/Session/CryptoSessionData.php index fc7693b71b2..2b5b5c7b5e7 100644 --- a/lib/private/Session/CryptoSessionData.php +++ b/lib/private/Session/CryptoSessionData.php @@ -87,6 +87,7 @@ class CryptoSessionData implements \ArrayAccess, ISession { ); } catch (\Exception $e) { $this->sessionValues = []; + $this->regenerateId(true, false); } } diff --git a/lib/private/Session/CryptoWrapper.php b/lib/private/Session/CryptoWrapper.php index f7f26bb10d2..0f791a865d8 100644 --- a/lib/private/Session/CryptoWrapper.php +++ b/lib/private/Session/CryptoWrapper.php @@ -88,22 +88,18 @@ class CryptoWrapper { $webRoot = '/'; } - if (PHP_VERSION_ID < 70300) { - setcookie(self::COOKIE_NAME, $this->passphrase, 0, $webRoot, '', $secureCookie, true); - } else { - setcookie( - self::COOKIE_NAME, - $this->passphrase, - [ - 'expires' => 0, - 'path' => $webRoot, - 'domain' => '', - 'secure' => $secureCookie, - 'httponly' => true, - 'samesite' => 'Lax', - ] - ); - } + setcookie( + self::COOKIE_NAME, + $this->passphrase, + [ + 'expires' => 0, + 'path' => $webRoot, + 'domain' => '', + 'secure' => $secureCookie, + 'httponly' => true, + 'samesite' => 'Lax', + ] + ); } } } diff --git a/lib/private/Session/Internal.php b/lib/private/Session/Internal.php index ffe16537874..b7e1c7b7bf8 100644 --- a/lib/private/Session/Internal.php +++ b/lib/private/Session/Internal.php @@ -104,7 +104,7 @@ class Internal extends Session { public function clear() { $this->invoke('session_unset'); $this->regenerateId(); - $this->startSession(); + $this->startSession(true); $_SESSION = []; } @@ -213,11 +213,7 @@ class Internal extends Session { } } - private function startSession() { - if (PHP_VERSION_ID < 70300) { - $this->invoke('session_start'); - } else { - $this->invoke('session_start', [['cookie_samesite' => 'Lax']]); - } + private function startSession(bool $silence = false) { + $this->invoke('session_start', [['cookie_samesite' => 'Lax']], $silence); } } diff --git a/lib/private/Settings/Manager.php b/lib/private/Settings/Manager.php index ac4f8f9342c..37a87a40725 100644 --- a/lib/private/Settings/Manager.php +++ b/lib/private/Settings/Manager.php @@ -38,8 +38,8 @@ use OCP\ILogger; use OCP\IServerContainer; use OCP\IURLGenerator; use OCP\L10N\IFactory; +use OCP\Settings\IIconSection; use OCP\Settings\IManager; -use OCP\Settings\ISection; use OCP\Settings\ISettings; use OCP\Settings\ISubAdminSettings; @@ -80,7 +80,7 @@ class Manager implements IManager { /** * @param string $type 'admin' or 'personal' - * @param string $section Class must implement OCP\Settings\ISection + * @param string $section Class must implement OCP\Settings\IIconSection * * @return void */ @@ -95,7 +95,7 @@ class Manager implements IManager { /** * @param string $type 'admin' or 'personal' * - * @return ISection[] + * @return IIconSection[] */ protected function getSections(string $type): array { if (!isset($this->sections[$type])) { @@ -106,24 +106,19 @@ class Manager implements IManager { return $this->sections[$type]; } - foreach ($this->sectionClasses[$type] as $index => $class) { + foreach (array_unique($this->sectionClasses[$type]) as $index => $class) { try { - /** @var ISection $section */ + /** @var IIconSection $section */ $section = \OC::$server->query($class); } catch (QueryException $e) { $this->log->logException($e, ['level' => ILogger::INFO]); continue; } - if (!$section instanceof ISection) { - $this->log->logException(new \InvalidArgumentException('Invalid settings section registered'), ['level' => ILogger::INFO]); - continue; - } - $sectionID = $section->getID(); - if (isset($this->sections[$type][$sectionID])) { - $this->log->logException(new \InvalidArgumentException('Section with the same ID already registered'), ['level' => ILogger::INFO]); + if ($sectionID !== 'connected-accounts' && isset($this->sections[$type][$sectionID])) { + $this->log->logException(new \InvalidArgumentException('Section with the same ID already registered: ' . $sectionID . ', class: ' . $class), ['level' => ILogger::INFO]); continue; } @@ -212,7 +207,7 @@ class Manager implements IManager { $appSections = $this->getSections('admin'); foreach ($appSections as $section) { - /** @var ISection $section */ + /** @var IIconSection $section */ if (!isset($sections[$section->getPriority()])) { $sections[$section->getPriority()] = []; } @@ -261,14 +256,15 @@ class Manager implements IManager { $sections = []; $legacyForms = \OC_App::getForms('personal'); - if (!empty($legacyForms) && $this->hasLegacyPersonalSettingsToRender($legacyForms)) { + if ((!empty($legacyForms) && $this->hasLegacyPersonalSettingsToRender($legacyForms)) + || count($this->getPersonalSettings('additional')) > 1) { $sections[98] = [new Section('additional', $this->l->t('Additional settings'), 0, $this->url->imagePath('core', 'actions/settings-dark.svg'))]; } $appSections = $this->getSections('personal'); foreach ($appSections as $section) { - /** @var ISection $section */ + /** @var IIconSection $section */ if (!isset($sections[$section->getPriority()])) { $sections[$section->getPriority()] = []; } diff --git a/lib/private/Setup.php b/lib/private/Setup.php index 775c2d0a952..873e82e55de 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -17,6 +17,7 @@ * @author Joas Schilling <coding@schilljs.com> * @author Julius Härtl <jus@bitgrid.net> * @author KB7777 <k.burkowski@gmail.com> + * @author Kevin Lanni <therealklanni@gmail.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author MichaIng <28480705+MichaIng@users.noreply.github.com> * @author MichaIng <micha@dietpi.com> @@ -27,7 +28,7 @@ * @author Serge Martin <edb@sigluy.net> * @author Simounet <contact@simounet.net> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -503,7 +504,7 @@ class Setup { $setupHelper = new \OC\Setup( $config, - \OC::$server->getIniWrapper(), + \OC::$server->get(IniGetWrapper::class), \OC::$server->getL10N('lib'), \OC::$server->query(Defaults::class), \OC::$server->getLogger(), @@ -543,7 +544,7 @@ class Setup { $content .= "\n RewriteCond %{REQUEST_FILENAME} !/ocs-provider/"; $content .= "\n RewriteCond %{REQUEST_FILENAME} !/ocm-provider/"; $content .= "\n RewriteCond %{REQUEST_URI} !^/\\.well-known/(acme-challenge|pki-validation)/.*"; - $content .= "\n RewriteCond %{REQUEST_FILENAME} !/richdocumentscode/proxy.php$"; + $content .= "\n RewriteCond %{REQUEST_FILENAME} !/richdocumentscode(_arm64)?/proxy.php$"; $content .= "\n RewriteRule . index.php [PT,E=PATH_INFO:$1]"; $content .= "\n RewriteBase " . $rewriteBase; $content .= "\n <IfModule mod_env.c>"; diff --git a/lib/private/Setup/AbstractDatabase.php b/lib/private/Setup/AbstractDatabase.php index b2c290eb874..8a9aed09f1b 100644 --- a/lib/private/Setup/AbstractDatabase.php +++ b/lib/private/Setup/AbstractDatabase.php @@ -90,10 +90,10 @@ abstract class AbstractDatabase { $dbTablePrefix = isset($config['dbtableprefix']) ? $config['dbtableprefix'] : 'oc_'; $this->config->setValues([ - 'dbname' => $dbName, - 'dbhost' => $dbHost, + 'dbname' => $dbName, + 'dbhost' => $dbHost, 'dbport' => $dbPort, - 'dbtableprefix' => $dbTablePrefix, + 'dbtableprefix' => $dbTablePrefix, ]); $this->dbUser = $dbUser; @@ -150,6 +150,6 @@ abstract class AbstractDatabase { return; } $ms = new MigrationService('core', \OC::$server->getDatabaseConnection()); - $ms->migrate(); + $ms->migrate('latest', true); } } diff --git a/lib/private/Setup/MySQL.php b/lib/private/Setup/MySQL.php index 6a5513eab43..54542de72bf 100644 --- a/lib/private/Setup/MySQL.php +++ b/lib/private/Setup/MySQL.php @@ -9,10 +9,9 @@ * @author Joas Schilling <coding@schilljs.com> * @author Michael Göhler <somebody.here@gmx.de> * @author Morris Jobke <hey@morrisjobke.de> - * @author Oliver Salzburg <oliver.salzburg@gmail.com> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -57,7 +56,7 @@ class MySQL extends AbstractDatabase { $this->createDatabase($connection); //fill the database if needed - $query='select count(*) from information_schema.tables where table_schema=? AND table_name = ?'; + $query = 'select count(*) from information_schema.tables where table_schema=? AND table_name = ?'; $connection->executeQuery($query, [$this->dbName, $this->tablePrefix.'users']); $connection->close(); @@ -93,7 +92,7 @@ class MySQL extends AbstractDatabase { try { //this query will fail if there aren't the right permissions, ignore the error - $query="GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, REFERENCES, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EVENT, TRIGGER ON `$name` . * TO '$user'"; + $query = "GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, REFERENCES, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EVENT, TRIGGER ON `$name` . * TO '$user'"; $connection->executeUpdate($query); } catch (\Exception $ex) { $this->logger->logException($ex, [ @@ -165,7 +164,7 @@ class MySQL extends AbstractDatabase { $this->dbUser = $adminUser; //create a random password so we don't need to store the admin password in the config file - $this->dbPassword = $this->random->generate(30); + $this->dbPassword = $this->random->generate(30); $this->createDBUser($connection); diff --git a/lib/private/Setup/PostgreSQL.php b/lib/private/Setup/PostgreSQL.php index 17ae991a1de..3d7a0b2a4b1 100644 --- a/lib/private/Setup/PostgreSQL.php +++ b/lib/private/Setup/PostgreSQL.php @@ -39,7 +39,6 @@ class PostgreSQL extends AbstractDatabase { /** * @param string $username * @throws \OC\DatabaseSetupException - * @suppress SqlInjectionChecker */ public function setupDatabase($username) { try { diff --git a/lib/private/Share/Constants.php b/lib/private/Share/Constants.php index 2310859c5be..0a705366283 100644 --- a/lib/private/Share/Constants.php +++ b/lib/private/Share/Constants.php @@ -7,6 +7,7 @@ * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Daniel Calviño Sánchez <danxuliu@gmail.com> * @author Joas Schilling <coding@schilljs.com> + * @author Julius Härtl <jus@bitgrid.net> * @author Morris Jobke <hey@morrisjobke.de> * * @license AGPL-3.0 @@ -70,6 +71,11 @@ class Constants { */ public const SHARE_TYPE_ROOM = 10; // const SHARE_TYPE_USERROOM = 11; // Internal type used by RoomShareProvider + /** + * @deprecated 21.0.0 - use IShare::TYPE_DECK instead + */ + public const SHARE_TYPE_DECK = 12; + // const SHARE_TYPE_DECK_USER = 13; // Internal type used by DeckShareProvider public const FORMAT_NONE = -1; public const FORMAT_STATUSES = -2; diff --git a/lib/private/Share/Helper.php b/lib/private/Share/Helper.php index 90dc3e957e9..6c92661fa76 100644 --- a/lib/private/Share/Helper.php +++ b/lib/private/Share/Helper.php @@ -7,10 +7,6 @@ * @author Joas Schilling <coding@schilljs.com> * @author Miguel Prokop <miguel.prokop@vtu.com> * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * @@ -30,132 +26,9 @@ namespace OC\Share; -use OC\HintException; -use OCP\Share\IShare; - class Helper extends \OC\Share\Constants { /** - * Generate a unique target for the item - * @param string $itemType - * @param string $itemSource - * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK - * @param string $shareWith User or group the item is being shared with - * @param string $uidOwner User that is the owner of shared item - * @param string $suggestedTarget The suggested target originating from a reshare (optional) - * @param int $groupParent The id of the parent group share (optional) - * @throws \Exception - * @return string Item target - */ - public static function generateTarget($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $suggestedTarget = null, $groupParent = null) { - // FIXME: $uidOwner and $groupParent seems to be unused - $backend = \OC\Share\Share::getBackend($itemType); - if ($shareType === IShare::TYPE_LINK || $shareType === IShare::TYPE_REMOTE) { - if (isset($suggestedTarget)) { - return $suggestedTarget; - } - return $backend->generateTarget($itemSource, false); - } else { - if ($shareType == IShare::TYPE_USER) { - // Share with is a user, so set share type to user and groups - $shareType = self::$shareTypeUserAndGroups; - } - - // Check if suggested target exists first - if (!isset($suggestedTarget)) { - $suggestedTarget = $itemSource; - } - if ($shareType == IShare::TYPE_GROUP) { - $target = $backend->generateTarget($suggestedTarget, false); - } else { - $target = $backend->generateTarget($suggestedTarget, $shareWith); - } - - return $target; - } - } - - /** - * Delete all reshares and group share children of an item - * @param int $parent Id of item to delete - * @param bool $excludeParent If true, exclude the parent from the delete (optional) - * @param string $uidOwner The user that the parent was shared with (optional) - * @param int $newParent new parent for the childrens - * @param bool $excludeGroupChildren exclude group children elements - */ - public static function delete($parent, $excludeParent = false, $uidOwner = null, $newParent = null, $excludeGroupChildren = false) { - $ids = [$parent]; - $deletedItems = []; - $changeParent = []; - $parents = [$parent]; - while (!empty($parents)) { - $parents = "'".implode("','", $parents)."'"; - // Check the owner on the first search of reshares, useful for - // finding and deleting the reshares by a single user of a group share - $params = []; - if (count($ids) == 1 && isset($uidOwner)) { - // FIXME: don't concat $parents, use Docrine's PARAM_INT_ARRAY approach - $queryString = 'SELECT `id`, `share_with`, `item_type`, `share_type`, ' . - '`item_target`, `file_target`, `parent` ' . - 'FROM `*PREFIX*share` ' . - 'WHERE `parent` IN ('.$parents.') AND `uid_owner` = ? '; - $params[] = $uidOwner; - } else { - $queryString = 'SELECT `id`, `share_with`, `item_type`, `share_type`, ' . - '`item_target`, `file_target`, `parent`, `uid_owner` ' . - 'FROM `*PREFIX*share` WHERE `parent` IN ('.$parents.') '; - } - if ($excludeGroupChildren) { - $queryString .= ' AND `share_type` != ?'; - $params[] = self::$shareTypeGroupUserUnique; - } - $query = \OC_DB::prepare($queryString); - $result = $query->execute($params); - // Reset parents array, only go through loop again if items are found - $parents = []; - while ($item = $result->fetchRow()) { - $tmpItem = [ - 'id' => $item['id'], - 'shareWith' => $item['share_with'], - 'itemTarget' => $item['item_target'], - 'itemType' => $item['item_type'], - 'shareType' => (int)$item['share_type'], - ]; - if (isset($item['file_target'])) { - $tmpItem['fileTarget'] = $item['file_target']; - } - // if we have a new parent for the child we remember the child - // to update the parent, if not we add it to the list of items - // which should be deleted - if ($newParent !== null) { - $changeParent[] = $item['id']; - } else { - $deletedItems[] = $tmpItem; - $ids[] = $item['id']; - $parents[] = $item['id']; - } - } - } - if ($excludeParent) { - unset($ids[0]); - } - - if (!empty($changeParent)) { - $idList = "'".implode("','", $changeParent)."'"; - $query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `parent` = ? WHERE `id` IN ('.$idList.')'); - $query->execute([$newParent]); - } - - if (!empty($ids)) { - $idList = "'".implode("','", $ids)."'"; - $query = \OC_DB::prepare('DELETE FROM `*PREFIX*share` WHERE `id` IN ('.$idList.')'); - $query->execute(); - } - - return $deletedItems; - } - - /** * get default expire settings defined by the admin * @return array contains 'defaultExpireDateSet', 'enforceExpireDate', 'expireAfterDays' */ @@ -241,24 +114,6 @@ class Helper extends \OC\Share\Constants { } /** - * split user and remote from federated cloud id - * - * @param string $id - * @return string[] - * @throws HintException - */ - public static function splitUserRemote($id) { - try { - $cloudId = \OC::$server->getCloudIdManager()->resolveCloudId($id); - return [$cloudId->getUser(), $cloudId->getRemote()]; - } catch (\InvalidArgumentException $e) { - $l = \OC::$server->getL10N('core'); - $hint = $l->t('Invalid Federated Cloud ID'); - throw new HintException('Invalid Federated Cloud ID', $hint, 0, $e); - } - } - - /** * check if two federated cloud IDs refer to the same user * * @param string $user1 diff --git a/lib/private/Share/Share.php b/lib/private/Share/Share.php index aed233692ed..2d0d4f1cf87 100644 --- a/lib/private/Share/Share.php +++ b/lib/private/Share/Share.php @@ -5,18 +5,16 @@ * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Bart Visscher <bartv@thisnet.nl> * @author Bernhard Reiter <ockham@raz.or.at> - * @author Bjoern Schiessle <bjoern@schiessle.org> * @author Björn Schießle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Sebastian Döll <sebastian.doell@libasys.de> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * @author Volkan Gezer <volkangezer@gmail.com> * * @license AGPL-3.0 @@ -104,8 +102,7 @@ class Share extends Constants { * \OC\Share\Share::getItemsSharedWith('folder'); (apps/files_sharing/tests/UpdaterTest.php) */ public static function getItemsSharedWith() { - return self::getItems('folder', null, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, self::FORMAT_NONE, - null, -1, false); + return self::getItems('folder', null, self::$shareTypeUserAndGroups, \OC_User::getUser()); } /** @@ -117,11 +114,12 @@ class Share extends Constants { * @param int $limit Number of items to return (optional) Returns all by default * @param boolean $includeCollections (optional) * @return mixed Return depends on format + * @deprecated TESTS ONLY - this methods is only used by tests + * called like this: + * \OC\Share\Share::getItemsSharedWithUser('test', $shareWith); (tests/lib/Share/Backend.php) */ - public static function getItemsSharedWithUser($itemType, $user, $format = self::FORMAT_NONE, - $parameters = null, $limit = -1, $includeCollections = false) { - return self::getItems($itemType, null, self::$shareTypeUserAndGroups, $user, null, $format, - $parameters, $limit, $includeCollections); + public static function getItemsSharedWithUser($itemType, $user) { + return self::getItems('test', null, self::$shareTypeUserAndGroups, $user); } /** @@ -194,6 +192,7 @@ class Share extends Constants { } $shares[] = $row; } + $result->closeCursor(); //if didn't found a result than let's look for a group share. if (empty($shares) && $user !== null) { @@ -233,23 +232,6 @@ class Share extends Constants { } /** - * Get the item of item type shared with the current user by source - * @param string $itemType - * @param string $itemSource - * @param int $format (optional) Format type must be defined by the backend - * @param mixed $parameters - * @param boolean $includeCollections - * @param string $shareWith (optional) define against which user should be checked, default: current user - * @return array - */ - public static function getItemSharedWithBySource($itemType, $itemSource, $format = self::FORMAT_NONE, - $parameters = null, $includeCollections = false, $shareWith = null) { - $shareWith = ($shareWith === null) ? \OC_User::getUser() : $shareWith; - return self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups, $shareWith, null, $format, - $parameters, 1, $includeCollections, true); - } - - /** * Get the shared item of item type owned by the current user * @param string $itemType * @param string $itemSource @@ -257,126 +239,14 @@ class Share extends Constants { * @param mixed $parameters * @param boolean $includeCollections * @return mixed Return depends on format + * + * Refactoring notes: + * * defacto $parameters and $format is always the default and therefore is removed in the subsequent call */ public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE, $parameters = null, $includeCollections = false) { - return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), $format, - $parameters, -1, $includeCollections); - } - - /** - * Unshare an item from a user, group, or delete a private link - * @param string $itemType - * @param string $itemSource - * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK - * @param string $shareWith User or group the item is being shared with - * @param string $owner owner of the share, if null the current user is used - * @return boolean true on success or false on failure - */ - public static function unshare($itemType, $itemSource, $shareType, $shareWith, $owner = null) { - - // check if it is a valid itemType - self::getBackend($itemType); - - $items = self::getItemSharedWithUser($itemType, $itemSource, $shareWith, $owner, $shareType); - - $toDelete = []; - $newParent = null; - $currentUser = $owner ? $owner : \OC_User::getUser(); - foreach ($items as $item) { - // delete the item with the expected share_type and owner - if ((int)$item['share_type'] === (int)$shareType && $item['uid_owner'] === $currentUser) { - $toDelete = $item; - // if there is more then one result we don't have to delete the children - // but update their parent. For group shares the new parent should always be - // the original group share and not the db entry with the unique name - } elseif ((int)$item['share_type'] === self::$shareTypeGroupUserUnique) { - $newParent = $item['parent']; - } else { - $newParent = $item['id']; - } - } - - if (!empty($toDelete)) { - self::unshareItem($toDelete, $newParent); - return true; - } - return false; - } - - /** - * Checks whether a share has expired, calls unshareItem() if yes. - * @param array $item Share data (usually database row) - * @return boolean True if item was expired, false otherwise. - */ - protected static function expireItem(array $item) { - $result = false; - - // only use default expiration date for link shares - if ((int) $item['share_type'] === IShare::TYPE_LINK) { - - // calculate expiration date - if (!empty($item['expiration'])) { - $userDefinedExpire = new \DateTime($item['expiration']); - $expires = $userDefinedExpire->getTimestamp(); - } else { - $expires = null; - } - - - // get default expiration settings - $defaultSettings = Helper::getDefaultExpireSetting(); - $expires = Helper::calculateExpireDate($defaultSettings, $item['stime'], $expires); - - - if (is_int($expires)) { - $now = time(); - if ($now > $expires) { - self::unshareItem($item); - $result = true; - } - } - } - return $result; - } - - /** - * Unshares a share given a share data array - * @param array $item Share data (usually database row) - * @param int $newParent parent ID - * @return null - */ - protected static function unshareItem(array $item, $newParent = null) { - $shareType = (int)$item['share_type']; - $shareWith = null; - if ($shareType !== IShare::TYPE_LINK) { - $shareWith = $item['share_with']; - } - - // Pass all the vars we have for now, they may be useful - $hookParams = [ - 'id' => $item['id'], - 'itemType' => $item['item_type'], - 'itemSource' => $item['item_source'], - 'shareType' => $shareType, - 'shareWith' => $shareWith, - 'itemParent' => $item['parent'], - 'uidOwner' => $item['uid_owner'], - ]; - if ($item['item_type'] === 'file' || $item['item_type'] === 'folder') { - $hookParams['fileSource'] = $item['file_source']; - $hookParams['fileTarget'] = $item['file_target']; - } - - \OC_Hook::emit(\OCP\Share::class, 'pre_unshare', $hookParams); - $deletedShares = Helper::delete($item['id'], false, null, $newParent); - $deletedShares[] = $hookParams; - $hookParams['deletedShares'] = $deletedShares; - \OC_Hook::emit(\OCP\Share::class, 'post_unshare', $hookParams); - if ((int)$item['share_type'] === IShare::TYPE_REMOTE && \OC::$server->getUserSession()->getUser()) { - list(, $remote) = Helper::splitUserRemote($item['share_with']); - self::sendRemoteUnshare($remote, $item['id'], $item['token']); - } + return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), self::FORMAT_NONE, + null, -1, $includeCollections); } /** @@ -471,10 +341,12 @@ class Share extends Constants { * * See public functions getItem(s)... for parameter usage * + * Refactoring notes: + * * defacto $limit, $itemsShareWithBySource, $checkExpireDate, $parameters and $format is always the default and therefore is removed in the subsequent call */ public static function getItems($itemType, $item = null, $shareType = null, $shareWith = null, $uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1, - $includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) { + $includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) { if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') != 'yes') { return []; } @@ -578,7 +450,7 @@ class Share extends Constants { $where .= ' AND'; } // If looking for own shared items, check item_source else check item_target - if (isset($uidOwner) || $itemShareWithBySource) { + if (isset($uidOwner)) { // If item type is a file, file source needs to be checked in case the item was converted if ($fileDependent) { $where .= ' `file_source` = ?'; @@ -603,26 +475,10 @@ class Share extends Constants { } } - if ($shareType == self::$shareTypeUserAndGroups && $limit === 1) { - // Make sure the unique user target is returned if it exists, - // unique targets should follow the group share in the database - // If the limit is not 1, the filtering can be done later - $where .= ' ORDER BY `*PREFIX*share`.`id` DESC'; - } else { - $where .= ' ORDER BY `*PREFIX*share`.`id` ASC'; - } + $where .= ' ORDER BY `*PREFIX*share`.`id` ASC'; - if ($limit != -1 && !$includeCollections) { - // The limit must be at least 3, because filtering needs to be done - if ($limit < 3) { - $queryLimit = 3; - } else { - $queryLimit = $limit; - } - } else { - $queryLimit = null; - } - $select = self::createSelectStatement($format, $fileDependent, $uidOwner); + $queryLimit = null; + $select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent, $uidOwner); $root = strlen($root); $query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit); $result = $query->execute($queryArgs); @@ -687,14 +543,20 @@ class Share extends Constants { // Remove root from file source paths if retrieving own shared items if (isset($uidOwner) && isset($row['path'])) { if (isset($row['parent'])) { - $query = \OC_DB::prepare('SELECT `file_target` FROM `*PREFIX*share` WHERE `id` = ?'); - $parentResult = $query->execute([$row['parent']]); - if ($result === false) { + $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + $query->select('file_target') + ->from('share') + ->where($query->expr()->eq('id', $query->createNamedParameter($row['parent']))); + + $parentResult = $query->execute(); + $parentRow = $parentResult->fetch(); + $parentResult->closeCursor(); + + if ($parentRow === false) { \OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' . \OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where, ILogger::ERROR); } else { - $parentRow = $parentResult->fetchRow(); $tmpPath = $parentRow['file_target']; // find the right position where the row path continues from the target path $pos = strrpos($row['path'], $parentRow['file_target']); @@ -720,11 +582,6 @@ class Share extends Constants { } } - if ($checkExpireDate) { - if (self::expireItem($row)) { - continue; - } - } // Check if resharing is allowed, if not remove share permission if (isset($row['permissions']) && (!self::isResharingAllowed() | \OCP\Util::isSharingDisabledForUser())) { $row['permissions'] &= ~\OCP\Constants::PERMISSION_SHARE; @@ -764,14 +621,6 @@ class Share extends Constants { if (!empty($items)) { $collectionItems = []; foreach ($items as &$row) { - // Return only the item instead of a 2-dimensional array - if ($limit == 1 && $row[$column] == $item && ($row['item_type'] == $itemType || $itemType == 'file')) { - if ($format == self::FORMAT_NONE) { - return $row; - } else { - break; - } - } // Check if this is a collection of the requested item type if ($includeCollections && $collectionTypes && $row['item_type'] !== 'folder' && in_array($row['item_type'], $collectionTypes)) { if (($collectionBackend = self::getBackend($row['item_type'])) @@ -807,19 +656,7 @@ class Share extends Constants { } if (isset($item)) { if ($childItem[$column] == $item) { - // Return only the item instead of a 2-dimensional array - if ($limit == 1) { - if ($format == self::FORMAT_NONE) { - return $childItem; - } else { - // Unset the items array and break out of both loops - $items = []; - $items[] = $childItem; - break 2; - } - } else { - $collectionItems[] = $childItem; - } + $collectionItems[] = $childItem; } } else { $collectionItems[] = $childItem; @@ -855,7 +692,7 @@ class Share extends Constants { return $item['share_type'] !== self::$shareTypeGroupUserUnique; }); - return self::formatResult($items, $column, $backend, $format, $parameters); + return self::formatResult($items, $column, $backend); } elseif ($includeCollections && $collectionTypes && in_array('folder', $collectionTypes)) { // FIXME: Thats a dirty hack to improve file sharing performance, // see github issue #10588 for more details @@ -866,10 +703,7 @@ class Share extends Constants { foreach ($sharedParents as $parent) { $collectionItems[] = $parent; } - if ($limit === 1) { - return reset($collectionItems); - } - return self::formatResult($collectionItems, $column, $backend, $format, $parameters); + return self::formatResult($collectionItems, $column, $backend); } return []; @@ -1049,65 +883,6 @@ class Share extends Constants { return $url; } - /** - * try http post first with https and then with http as a fallback - * - * @param string $remoteDomain - * @param string $urlSuffix - * @param array $fields post parameters - * @return array - */ - private static function tryHttpPostToShareEndpoint($remoteDomain, $urlSuffix, array $fields) { - $protocol = 'https://'; - $result = [ - 'success' => false, - 'result' => '', - ]; - $try = 0; - $discoveryService = \OC::$server->query(\OCP\OCS\IDiscoveryService::class); - while ($result['success'] === false && $try < 2) { - $federationEndpoints = $discoveryService->discover($protocol . $remoteDomain, 'FEDERATED_SHARING'); - $endpoint = isset($federationEndpoints['share']) ? $federationEndpoints['share'] : '/ocs/v2.php/cloud/shares'; - $client = \OC::$server->getHTTPClientService()->newClient(); - - try { - $response = $client->post( - $protocol . $remoteDomain . $endpoint . $urlSuffix . '?format=' . self::RESPONSE_FORMAT, - [ - 'body' => $fields, - 'connect_timeout' => 10, - ] - ); - - $result = ['success' => true, 'result' => $response->getBody()]; - } catch (\Exception $e) { - $result = ['success' => false, 'result' => $e->getMessage()]; - } - - $try++; - $protocol = 'http://'; - } - - return $result; - } - - /** - * send server-to-server unshare to remote server - * - * @param string $remote url - * @param int $id share id - * @param string $token - * @return bool - */ - private static function sendRemoteUnshare($remote, $id, $token) { - $url = rtrim($remote, '/'); - $fields = ['token' => $token, 'format' => 'json']; - $url = self::removeProtocolFromUrl($url); - $result = self::tryHttpPostToShareEndpoint($url, '/'.$id.'/unshare', $fields); - $status = json_decode($result['result'], true); - - return ($result['success'] && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200)); - } /** * @return int diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index ecfe282dfbf..cf05e9bfbc3 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -2,6 +2,7 @@ /** * @copyright Copyright (c) 2016, ownCloud, Inc. * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Bjoern Schiessle <bjoern@schiessle.org> * @author Björn Schießle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> @@ -14,7 +15,7 @@ * @author phisch <git@philippschaffrath.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -824,7 +825,7 @@ class DefaultShareProvider implements IShareProvider { $pathSections = explode('/', $data['path'], 2); // FIXME: would not detect rare md5'd home storage case properly if ($pathSections[0] !== 'files' - && in_array(explode(':', $data['storage_string_id'], 2)[0], ['home', 'object'])) { + && (strpos($data['storage_string_id'], 'home::') === 0 || strpos($data['storage_string_id'], 'object::user') === 0)) { return false; } return true; @@ -874,6 +875,11 @@ class DefaultShareProvider implements IShareProvider { $cursor = $qb->execute(); while ($data = $cursor->fetch()) { + if ($data['fileid'] && $data['path'] === null) { + $data['path'] = (string) $data['path']; + $data['name'] = (string) $data['name']; + $data['checksum'] = (string) $data['checksum']; + } if ($this->isAccessibleResult($data)) { $shares[] = $this->createShare($data); } @@ -881,7 +887,7 @@ class DefaultShareProvider implements IShareProvider { $cursor->closeCursor(); } elseif ($shareType === IShare::TYPE_GROUP) { $user = $this->userManager->get($userId); - $allGroups = $this->groupManager->getUserGroupIds($user); + $allGroups = ($user instanceof IUser) ? $this->groupManager->getUserGroupIds($user) : []; /** @var Share[] $shares2 */ $shares2 = []; @@ -1004,7 +1010,7 @@ class DefaultShareProvider implements IShareProvider { ->setShareType((int)$data['share_type']) ->setPermissions((int)$data['permissions']) ->setTarget($data['file_target']) - ->setNote($data['note']) + ->setNote((string)$data['note']) ->setMailSend((bool)$data['mail_send']) ->setStatus((int)$data['accepted']) ->setLabel($data['label']); diff --git a/lib/private/Share20/Hooks.php b/lib/private/Share20/Hooks.php index 0e41e20a2cd..b596123bbe0 100644 --- a/lib/private/Share20/Hooks.php +++ b/lib/private/Share20/Hooks.php @@ -30,8 +30,4 @@ class Hooks { public static function post_deleteGroup($arguments) { \OC::$server->getShareManager()->groupDeleted($arguments['gid']); } - - public static function post_removeFromGroupLDAP($arguments) { - \OC::$server->getShareManager()->userDeletedFromGroup($arguments['uid'], $arguments['gid']); - } } diff --git a/lib/private/Share20/LegacyHooks.php b/lib/private/Share20/LegacyHooks.php index 285b45be809..f657b832b5a 100644 --- a/lib/private/Share20/LegacyHooks.php +++ b/lib/private/Share20/LegacyHooks.php @@ -3,6 +3,7 @@ * @copyright 2017, Roeland Jago Douma <roeland@famdouma.nl> * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Pauli Järvinen <pauli.jarvinen@gmail.com> * @author Roeland Jago Douma <roeland@famdouma.nl> diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 3b022f5951c..0787f6d32aa 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -7,6 +7,7 @@ * @author Björn Schießle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Daniel Calviño Sánchez <danxuliu@gmail.com> + * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Jan-Christoph Borchardt <hey@jancborchardt.net> * @author Joas Schilling <coding@schilljs.com> * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> @@ -18,8 +19,7 @@ * @author Pauli Järvinen <pauli.jarvinen@gmail.com> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thibault Coupin <thibault.coupin@gmail.com> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -43,6 +43,7 @@ use OC\Cache\CappedMemoryCache; use OC\Files\Mount\MoveableMount; use OC\HintException; use OC\Share20\Exception\ProviderException; +use OCA\Files_Sharing\ISharedStorage; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\File; use OCP\Files\Folder; @@ -247,6 +248,7 @@ class Manager implements IManager { throw new \InvalidArgumentException('SharedWith is not a valid circle'); } } elseif ($share->getShareType() === IShare::TYPE_ROOM) { + } elseif ($share->getShareType() === IShare::TYPE_DECK) { } else { // We can't handle other types yet throw new \InvalidArgumentException('unknown share type'); @@ -286,8 +288,7 @@ class Manager implements IManager { // Check if we actually have share permissions if (!$share->getNode()->isShareable()) { - $path = $userFolder->getRelativePath($share->getNode()->getPath()); - $message_t = $this->l->t('You are not allowed to share %s', [$path]); + $message_t = $this->l->t('You are not allowed to share %s', [$share->getNode()->getName()]); throw new GenericShareException($message_t, $message_t, 404); } @@ -299,10 +300,17 @@ class Manager implements IManager { $isFederatedShare = $share->getNode()->getStorage()->instanceOfStorage('\OCA\Files_Sharing\External\Storage'); $permissions = 0; - $userMounts = $userFolder->getById($share->getNode()->getId()); - $userMount = array_shift($userMounts); - $mount = $userMount->getMountPoint(); if (!$isFederatedShare && $share->getNode()->getOwner() && $share->getNode()->getOwner()->getUID() !== $share->getSharedBy()) { + $userMounts = array_filter($userFolder->getById($share->getNode()->getId()), function ($mount) { + // We need to filter since there might be other mountpoints that contain the file + // e.g. if the user has access to the same external storage that the file is originating from + return $mount->getStorage()->instanceOfStorage(ISharedStorage::class); + }); + $userMount = array_shift($userMounts); + if ($userMount === null) { + throw new GenericShareException('Could not get proper share mount for ' . $share->getNode()->getId() . '. Failing since else the next calls are called with null'); + } + $mount = $userMount->getMountPoint(); // When it's a reshare use the parent share permissions as maximum $userMountPointId = $mount->getStorageRootId(); $userMountPoints = $userFolder->getById($userMountPointId); @@ -331,7 +339,7 @@ class Manager implements IManager { * while we 'most likely' do have that on the storage. */ $permissions = $share->getNode()->getPermissions(); - if (!($mount instanceof MoveableMount)) { + if (!($share->getNode()->getMountPoint() instanceof MoveableMount)) { $permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE; } } @@ -401,9 +409,9 @@ class Manager implements IManager { $expirationDate = new \DateTime(); $expirationDate->setTime(0,0,0); - $days = (int)$this->config->getAppValue('core', 'internal_defaultExpDays', $this->shareApiLinkDefaultExpireDays()); - if ($days > $this->shareApiLinkDefaultExpireDays()) { - $days = $this->shareApiLinkDefaultExpireDays(); + $days = (int)$this->config->getAppValue('core', 'internal_defaultExpDays', (string)$this->shareApiInternalDefaultExpireDays()); + if ($days > $this->shareApiInternalDefaultExpireDays()) { + $days = $this->shareApiInternalDefaultExpireDays(); } $expirationDate->add(new \DateInterval('P'.$days.'D')); } @@ -805,7 +813,8 @@ class Manager implements IManager { $this->dispatcher->dispatchTyped(new Share\Events\ShareCreatedEvent($share)); - if ($share->getShareType() === IShare::TYPE_USER) { + if ($this->config->getSystemValueBool('sharing.enable_share_mail', true) + && $share->getShareType() === IShare::TYPE_USER) { $mailSend = $share->getMailSend(); if ($mailSend === true) { $user = $this->userManager->get($share->getSharedWith()); @@ -1386,7 +1395,7 @@ class Manager implements IManager { * * @return Share[] */ - public function getSharesByPath(\OCP\Files\Node $path, $page=0, $perPage=50) { + public function getSharesByPath(\OCP\Files\Node $path, $page = 0, $perPage = 50) { return []; } @@ -1879,6 +1888,10 @@ class Manager implements IManager { return true; } + public function registerShareProvider(string $shareProviderClass): void { + $this->factory->registerProvider($shareProviderClass); + } + public function getAllShares(): iterable { $providers = $this->factory->getAllProviders(); diff --git a/lib/private/Share20/ProviderFactory.php b/lib/private/Share20/ProviderFactory.php index 0606fe9c849..643a4fac4c5 100644 --- a/lib/private/Share20/ProviderFactory.php +++ b/lib/private/Share20/ProviderFactory.php @@ -6,9 +6,12 @@ * @author Björn Schießle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Daniel Calviño Sánchez <danxuliu@gmail.com> + * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Joas Schilling <coding@schilljs.com> + * @author Julius Härtl <jus@bitgrid.net> * @author Lukas Reschke <lukas@statuscode.ch> * @author Maxence Lange <maxence@nextcloud.com> + * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @@ -30,7 +33,6 @@ namespace OC\Share20; -use OC\CapabilitiesManager; use OC\Share20\Exception\ProviderException; use OCA\FederatedFileSharing\AddressHandler; use OCA\FederatedFileSharing\FederatedShareProvider; @@ -43,6 +45,7 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\IServerContainer; use OCP\Share\IProviderFactory; use OCP\Share\IShare; +use OCP\Share\IShareProvider; /** * Class ProviderFactory @@ -66,6 +69,10 @@ class ProviderFactory implements IProviderFactory { /** @var \OCA\Talk\Share\RoomShareProvider */ private $roomShareProvider = null; + private $registeredShareProviders = []; + + private $shareProviders = []; + /** * IProviderFactory constructor. * @@ -75,6 +82,10 @@ class ProviderFactory implements IProviderFactory { $this->serverContainer = $serverContainer; } + public function registerProvider(string $shareProviderClass): void { + $this->registeredShareProviders[] = $shareProviderClass; + } + /** * Create the default share provider. * @@ -184,7 +195,7 @@ class ProviderFactory implements IProviderFactory { $settingsManager, $this->serverContainer->query(Defaults::class), $this->serverContainer->getHasher(), - $this->serverContainer->query(CapabilitiesManager::class) + $this->serverContainer->get(IEventDispatcher::class) ); } @@ -256,6 +267,10 @@ class ProviderFactory implements IProviderFactory { */ public function getProvider($id) { $provider = null; + if (isset($this->shareProviders[$id])) { + return $this->shareProviders[$id]; + } + if ($id === 'ocinternal') { $provider = $this->defaultShareProvider(); } elseif ($id === 'ocFederatedSharing') { @@ -268,6 +283,16 @@ class ProviderFactory implements IProviderFactory { $provider = $this->getRoomShareProvider(); } + foreach ($this->registeredShareProviders as $shareProvider) { + /** @var IShareProvider $instance */ + $instance = $this->serverContainer->get($shareProvider); + $this->shareProviders[$instance->identifier()] = $instance; + } + + if (isset($this->shareProviders[$id])) { + $provider = $this->shareProviders[$id]; + } + if ($provider === null) { throw new ProviderException('No provider with id .' . $id . ' found.'); } @@ -294,6 +319,8 @@ class ProviderFactory implements IProviderFactory { $provider = $this->getShareByCircleProvider(); } elseif ($shareType === IShare::TYPE_ROOM) { $provider = $this->getRoomShareProvider(); + } elseif ($shareType === IShare::TYPE_DECK) { + $provider = $this->getProvider('deck'); } @@ -319,6 +346,17 @@ class ProviderFactory implements IProviderFactory { $shares[] = $roomShare; } + foreach ($this->registeredShareProviders as $shareProvider) { + /** @var IShareProvider $instance */ + $instance = $this->serverContainer->get($shareProvider); + if (!isset($this->shareProviders[$instance->identifier()])) { + $this->shareProviders[$instance->identifier()] = $instance; + } + $shares[] = $this->shareProviders[$instance->identifier()]; + } + + + return $shares; } } diff --git a/lib/private/Share20/UserRemovedListener.php b/lib/private/Share20/UserRemovedListener.php index 06ac52c05d4..0a81d049494 100644 --- a/lib/private/Share20/UserRemovedListener.php +++ b/lib/private/Share20/UserRemovedListener.php @@ -1,9 +1,12 @@ <?php declare(strict_types=1); + /** * @copyright Copyright (c) 2020 Joas Schilling <coding@schilljs.com> * + * @author Joas Schilling <coding@schilljs.com> + * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify @@ -17,7 +20,7 @@ declare(strict_types=1); * 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/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ diff --git a/lib/private/Streamer.php b/lib/private/Streamer.php index a05f77f3cbd..0e3018f77b7 100644 --- a/lib/private/Streamer.php +++ b/lib/private/Streamer.php @@ -76,8 +76,12 @@ class Streamer { * would still be possible to create an invalid zip32 file (for example, * a zip file from files smaller than 4GB with a central directory * larger than 4GiB), but it should not happen in the real world. + * + * We also have to check for a size above 0. As negative sizes could be + * from not fully scanned external storages. And then things fall apart + * if somebody tries to package to much. */ - if ($size < 4 * 1000 * 1000 * 1000 && $numberOfFiles < 65536) { + if ($size > 0 && $size < 4 * 1000 * 1000 * 1000 && $numberOfFiles < 65536) { $this->streamerInstance = new ZipStreamer(['zip64' => false]); } elseif ($request->isUserAgent($this->preferTarFor)) { $this->streamerInstance = new TarStreamer(); diff --git a/lib/private/SubAdmin.php b/lib/private/SubAdmin.php index 890bcf67b3b..a8769f28023 100644 --- a/lib/private/SubAdmin.php +++ b/lib/private/SubAdmin.php @@ -6,8 +6,10 @@ * @author Bart Visscher <bartv@thisnet.nl> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Georg Ehrke <oc.list@georgehrke.com> + * @author Joas Schilling <coding@schilljs.com> * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> * @author Lukas Reschke <lukas@statuscode.ch> + * @author Mikael Hammarin <mikael@try2.se> * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> * @@ -30,6 +32,9 @@ namespace OC; use OC\Hooks\PublicEmitter; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Group\Events\SubAdminAddedEvent; +use OCP\Group\Events\SubAdminRemovedEvent; use OCP\Group\ISubAdmin; use OCP\IDBConnection; use OCP\IGroup; @@ -48,6 +53,9 @@ class SubAdmin extends PublicEmitter implements ISubAdmin { /** @var IDBConnection */ private $dbConn; + /** @var IEventDispatcher */ + private $eventDispatcher; + /** * @param IUserManager $userManager * @param IGroupManager $groupManager @@ -55,10 +63,12 @@ class SubAdmin extends PublicEmitter implements ISubAdmin { */ public function __construct(IUserManager $userManager, IGroupManager $groupManager, - IDBConnection $dbConn) { + IDBConnection $dbConn, + IEventDispatcher $eventDispatcher) { $this->userManager = $userManager; $this->groupManager = $groupManager; $this->dbConn = $dbConn; + $this->eventDispatcher = $eventDispatcher; $this->userManager->listen('\OC\User', 'postDelete', function ($user) { $this->post_deleteUser($user); @@ -83,8 +93,10 @@ class SubAdmin extends PublicEmitter implements ISubAdmin { ]) ->execute(); + /** @deprecated 21.0.0 - use type SubAdminAddedEvent instead */ $this->emit('\OC\SubAdmin', 'postCreateSubAdmin', [$user, $group]); - \OC_Hook::emit("OC_SubAdmin", "post_createSubAdmin", ["gid" => $group->getGID()]); + $event = new SubAdminAddedEvent($group, $user); + $this->eventDispatcher->dispatchTyped($event); } /** @@ -100,8 +112,10 @@ class SubAdmin extends PublicEmitter implements ISubAdmin { ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID()))) ->execute(); + /** @deprecated 21.0.0 - use type SubAdminRemovedEvent instead */ $this->emit('\OC\SubAdmin', 'postDeleteSubAdmin', [$user, $group]); - \OC_Hook::emit("OC_SubAdmin", "post_deleteSubAdmin", ["gid" => $group->getGID()]); + $event = new SubAdminRemovedEvent($group, $user); + $this->eventDispatcher->dispatchTyped($event); } /** @@ -198,7 +212,7 @@ class SubAdmin extends PublicEmitter implements ISubAdmin { $group = $this->groupManager->get($row['gid']); if (!is_null($user) && !is_null($group)) { $subadmins[] = [ - 'user' => $user, + 'user' => $user, 'group' => $group ]; } @@ -226,7 +240,7 @@ class SubAdmin extends PublicEmitter implements ISubAdmin { ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID()))) ->execute(); - $fetch = $result->fetch(); + $fetch = $result->fetch(); $result->closeCursor(); $result = !empty($fetch) ? true : false; diff --git a/lib/private/Support/Subscription/Registry.php b/lib/private/Support/Subscription/Registry.php index 9f5d78bebb2..72bae2adc8e 100644 --- a/lib/private/Support/Subscription/Registry.php +++ b/lib/private/Support/Subscription/Registry.php @@ -7,6 +7,7 @@ declare(strict_types=1); * * @author Julius Härtl <jus@bitgrid.net> * @author Morris Jobke <hey@morrisjobke.de> + * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version * @@ -27,13 +28,18 @@ declare(strict_types=1); namespace OC\Support\Subscription; +use OC\User\Backend; use OCP\AppFramework\QueryException; use OCP\IConfig; +use OCP\IGroupManager; use OCP\IServerContainer; +use OCP\IUserManager; +use OCP\Notification\IManager; use OCP\Support\Subscription\Exception\AlreadyRegisteredException; use OCP\Support\Subscription\IRegistry; use OCP\Support\Subscription\ISubscription; use OCP\Support\Subscription\ISupportedApps; +use Psr\Log\LoggerInterface; class Registry implements IRegistry { @@ -48,10 +54,27 @@ class Registry implements IRegistry { /** @var IServerContainer */ private $container; - - public function __construct(IConfig $config, IServerContainer $container) { + /** @var IUserManager */ + private $userManager; + /** @var IGroupManager */ + private $groupManager; + /** @var LoggerInterface */ + private $logger; + /** @var IManager */ + private $notificationManager; + + public function __construct(IConfig $config, + IServerContainer $container, + IUserManager $userManager, + IGroupManager $groupManager, + LoggerInterface $logger, + IManager $notificationManager) { $this->config = $config; $this->container = $container; + $this->userManager = $userManager; + $this->groupManager = $groupManager; + $this->logger = $logger; + $this->notificationManager = $notificationManager; } private function getSubscription(): ?ISubscription { @@ -126,9 +149,87 @@ class Registry implements IRegistry { * @since 17.0.0 */ public function delegateHasExtendedSupport(): bool { - if ($this->getSubscription() instanceof ISubscription && method_exists($this->subscription, 'hasExtendedSupport')) { + if ($this->getSubscription() instanceof ISubscription) { return $this->getSubscription()->hasExtendedSupport(); } return false; } + + + /** + * Indicates if a hard user limit is reached and no new users should be created + * + * @since 21.0.0 + */ + public function delegateIsHardUserLimitReached(): bool { + $subscription = $this->getSubscription(); + if ($subscription instanceof ISubscription && + $subscription->hasValidSubscription()) { + $userLimitReached = $subscription->isHardUserLimitReached(); + if ($userLimitReached) { + $this->notifyAboutReachedUserLimit(); + } + return $userLimitReached; + } + + $isOneClickInstance = $this->config->getSystemValueBool('one-click-instance', false); + + if (!$isOneClickInstance) { + return false; + } + + $userCount = $this->getUserCount(); + $hardUserLimit = $this->config->getSystemValue('one-click-instance.user-limit', 50); + + $userLimitReached = $userCount >= $hardUserLimit; + if ($userLimitReached) { + $this->notifyAboutReachedUserLimit(); + } + return $userLimitReached; + } + + private function getUserCount(): int { + $userCount = 0; + $backends = $this->userManager->getBackends(); + foreach ($backends as $backend) { + if ($backend->implementsActions(Backend::COUNT_USERS)) { + $backendUsers = $backend->countUsers(); + if ($backendUsers !== false) { + $userCount += $backendUsers; + } else { + // TODO what if the user count can't be determined? + $this->logger->warning('Can not determine user count for ' . get_class($backend), ['app' => 'lib']); + } + } + } + + $disabledUsers = $this->config->getUsersForUserValue('core', 'enabled', 'false'); + $disabledUsersCount = count($disabledUsers); + $userCount = $userCount - $disabledUsersCount; + + if ($userCount < 0) { + $userCount = 0; + + // this should never happen + $this->logger->warning("Total user count was negative (users: $userCount, disabled: $disabledUsersCount)", ['app' => 'lib']); + } + + return $userCount; + } + + private function notifyAboutReachedUserLimit() { + $admins = $this->groupManager->get('admin')->getUsers(); + foreach ($admins as $admin) { + $notification = $this->notificationManager->createNotification(); + + $notification->setApp('core') + ->setUser($admin->getUID()) + ->setDateTime(new \DateTime()) + ->setObject('user_limit_reached', '1') + ->setSubject('user_limit_reached'); + $this->notificationManager->notify($notification); + } + + $this->logger->warning('The user limit was reached and the new user was not created', ['app' => 'lib']); + } } diff --git a/lib/private/SystemTag/ManagerFactory.php b/lib/private/SystemTag/ManagerFactory.php index a75a090304e..f894a8fe3ed 100644 --- a/lib/private/SystemTag/ManagerFactory.php +++ b/lib/private/SystemTag/ManagerFactory.php @@ -7,7 +7,7 @@ declare(strict_types=1); * * @author Joas Schilling <coding@schilljs.com> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/SystemTag/SystemTag.php b/lib/private/SystemTag/SystemTag.php index 14ccaffdd4e..8a48e048d39 100644 --- a/lib/private/SystemTag/SystemTag.php +++ b/lib/private/SystemTag/SystemTag.php @@ -7,7 +7,7 @@ declare(strict_types=1); * * @author Joas Schilling <coding@schilljs.com> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/SystemTag/SystemTagManager.php b/lib/private/SystemTag/SystemTagManager.php index 5941400883b..0f700a83490 100644 --- a/lib/private/SystemTag/SystemTagManager.php +++ b/lib/private/SystemTag/SystemTagManager.php @@ -8,8 +8,9 @@ declare(strict_types=1); * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Joas Schilling <coding@schilljs.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -230,7 +231,7 @@ class SystemTagManager implements ISystemTagManager { /** * {@inheritdoc} */ - public function updateTag(string $tagId, string $tagName, bool $userVisible, bool $userAssignable) { + public function updateTag(string $tagId, string $newName, bool $userVisible, bool $userAssignable) { try { $tags = $this->getTagsByIds($tagId); } catch (TagNotFoundException $e) { @@ -242,7 +243,7 @@ class SystemTagManager implements ISystemTagManager { $beforeUpdate = array_shift($tags); $afterUpdate = new SystemTag( $tagId, - $tagName, + $newName, $userVisible, $userAssignable ); @@ -253,7 +254,7 @@ class SystemTagManager implements ISystemTagManager { ->set('visibility', $query->createParameter('visibility')) ->set('editable', $query->createParameter('editable')) ->where($query->expr()->eq('id', $query->createParameter('tagid'))) - ->setParameter('name', $tagName) + ->setParameter('name', $newName) ->setParameter('visibility', $userVisible ? 1 : 0) ->setParameter('editable', $userAssignable ? 1 : 0) ->setParameter('tagid', $tagId); @@ -266,7 +267,7 @@ class SystemTagManager implements ISystemTagManager { } } catch (UniqueConstraintViolationException $e) { throw new TagAlreadyExistsException( - 'Tag ("' . $tagName . '", '. $userVisible . ', ' . $userAssignable . ') already exists', + 'Tag ("' . $newName . '", '. $userVisible . ', ' . $userAssignable . ') already exists', 0, $e ); diff --git a/lib/private/SystemTag/SystemTagObjectMapper.php b/lib/private/SystemTag/SystemTagObjectMapper.php index 5403321e582..a13afa8c79b 100644 --- a/lib/private/SystemTag/SystemTagObjectMapper.php +++ b/lib/private/SystemTag/SystemTagObjectMapper.php @@ -8,7 +8,7 @@ declare(strict_types=1); * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Joas Schilling <coding@schilljs.com> * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/TagManager.php b/lib/private/TagManager.php index 96786c58a1a..f283a5d5ada 100644 --- a/lib/private/TagManager.php +++ b/lib/private/TagManager.php @@ -4,9 +4,10 @@ * * @author Bernhard Reiter <ockham@raz.or.at> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Thomas Tanghus <thomas@tanghus.net> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -38,32 +39,27 @@ namespace OC; use OC\Tagging\TagMapper; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\ITagManager; +use OCP\ITags; +use OCP\IUserSession; -class TagManager implements \OCP\ITagManager { +class TagManager implements ITagManager { - /** - * User session - * - * @var \OCP\IUserSession - */ + /** @var TagMapper */ + private $mapper; + + /** @var IUserSession */ private $userSession; - /** - * TagMapper - * - * @var TagMapper - */ - private $mapper; + /** @var IDBConnection */ + private $connection; - /** - * Constructor. - * - * @param TagMapper $mapper Instance of the TagMapper abstraction layer. - * @param \OCP\IUserSession $userSession the user session - */ - public function __construct(TagMapper $mapper, \OCP\IUserSession $userSession) { + public function __construct(TagMapper $mapper, IUserSession $userSession, IDBConnection $connection) { $this->mapper = $mapper; $this->userSession = $userSession; + $this->connection = $connection; } /** @@ -90,4 +86,27 @@ class TagManager implements \OCP\ITagManager { } return new Tags($this->mapper, $userId, $type, $defaultTags); } + + /** + * Get all users who favorited an object + * + * @param string $objectType + * @param int $objectId + * @return array + */ + public function getUsersFavoritingObject(string $objectType, int $objectId): array { + $query = $this->connection->getQueryBuilder(); + $query->select('uid') + ->from('vcategory_to_object', 'o') + ->innerJoin('o', 'vcategory', 'c', $query->expr()->eq('o.categoryid', 'c.id')) + ->where($query->expr()->eq('objid', $query->createNamedParameter($objectId, IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->eq('c.type', $query->createNamedParameter($objectType))) + ->andWhere($query->expr()->eq('c.category', $query->createNamedParameter(ITags::TAG_FAVORITE))); + + $result = $query->execute(); + $users = $result->fetchAll(\PDO::FETCH_COLUMN); + $result->closeCursor(); + + return $users; + } } diff --git a/lib/private/Tagging/TagMapper.php b/lib/private/Tagging/TagMapper.php index d9c8a7fb470..cf1d93e3d26 100644 --- a/lib/private/Tagging/TagMapper.php +++ b/lib/private/Tagging/TagMapper.php @@ -57,7 +57,7 @@ class TagMapper extends Mapper { } $sql = 'SELECT `id`, `uid`, `type`, `category` FROM `' . $this->getTableName() . '` ' - . 'WHERE `uid` IN (' . str_repeat('?,', count($owners)-1) . '?) AND `type` = ? ORDER BY `category`'; + . 'WHERE `uid` IN (' . str_repeat('?,', count($owners) - 1) . '?) AND `type` = ? ORDER BY `category`'; return $this->findEntities($sql, array_merge($owners, [$type])); } diff --git a/lib/private/Tags.php b/lib/private/Tags.php index 3fc66c69d6c..720e9dd5d7d 100644 --- a/lib/private/Tags.php +++ b/lib/private/Tags.php @@ -12,7 +12,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Tanghus <thomas@tanghus.net> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -285,6 +285,7 @@ class Tags implements ITags { $stmt = \OC_DB::prepare($sql); $result = $stmt->execute([$tagId]); if ($result === null) { + $stmt->closeCursor(); \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR); return false; } @@ -301,6 +302,7 @@ class Tags implements ITags { while ($row = $result->fetchRow()) { $ids[] = (int)$row['objid']; } + $result->closeCursor(); } return $ids; @@ -417,7 +419,7 @@ class Tags implements ITags { * @param int|null $id int Optional object id to add to this|these tag(s) * @return bool Returns false on error. */ - public function addMultiple($names, $sync=false, $id = null) { + public function addMultiple($names, $sync = false, $id = null) { if (!is_array($names)) { $names = [$names]; } @@ -538,6 +540,7 @@ class Tags implements ITags { ]); } } + $result->closeCursor(); } catch (\Exception $e) { \OC::$server->getLogger()->logException($e, [ 'message' => __METHOD__, @@ -576,7 +579,7 @@ class Tags implements ITags { $updates = $ids; try { $query = 'DELETE FROM `' . self::RELATION_TABLE . '` '; - $query .= 'WHERE `objid` IN (' . str_repeat('?,', count($ids)-1) . '?) '; + $query .= 'WHERE `objid` IN (' . str_repeat('?,', count($ids) - 1) . '?) '; $query .= 'AND `type`= ?'; $updates[] = $this->type; $stmt = \OC_DB::prepare($query); @@ -658,7 +661,7 @@ class Tags implements ITags { if (!$this->hasTag($tag)) { $this->add($tag); } - $tagId = $this->getTagId($tag); + $tagId = $this->getTagId($tag); } else { $tagId = $tag; } @@ -694,7 +697,7 @@ class Tags implements ITags { \OCP\Util::writeLog('core', __METHOD__.', Tag name is empty', ILogger::DEBUG); return false; } - $tagId = $this->getTagId($tag); + $tagId = $this->getTagId($tag); } else { $tagId = $tag; } @@ -774,7 +777,7 @@ class Tags implements ITags { } // case-insensitive array_search - protected function array_searchi($needle, $haystack, $mem='getName') { + protected function array_searchi($needle, $haystack, $mem = 'getName') { if (!is_array($haystack)) { return false; } @@ -830,10 +833,10 @@ class Tags implements ITags { */ private function tagMap(Tag $tag) { return [ - 'id' => $tag->getId(), - 'name' => $tag->getName(), + 'id' => $tag->getId(), + 'name' => $tag->getName(), 'owner' => $tag->getOwner(), - 'type' => $tag->getType() + 'type' => $tag->getType() ]; } } diff --git a/lib/private/TempManager.php b/lib/private/TempManager.php index 49d4ee94cf6..e49955622c8 100644 --- a/lib/private/TempManager.php +++ b/lib/private/TempManager.php @@ -3,6 +3,7 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * @author Lars <winnetou+github@catolic.de> * @author Lukas Reschke <lukas@statuscode.ch> * @author Martin Mattel <martin.mattel@diemattels.at> @@ -31,30 +32,30 @@ namespace OC; +use bantu\IniGetWrapper\IniGetWrapper; use OCP\IConfig; -use OCP\ILogger; use OCP\ITempManager; +use Psr\Log\LoggerInterface; class TempManager implements ITempManager { /** @var string[] Current temporary files and folders, used for cleanup */ protected $current = []; /** @var string i.e. /tmp on linux systems */ protected $tmpBaseDir; - /** @var ILogger */ + /** @var LoggerInterface */ protected $log; /** @var IConfig */ protected $config; + /** @var IniGetWrapper */ + protected $iniGetWrapper; /** Prefix */ public const TMP_PREFIX = 'oc_tmp_'; - /** - * @param \OCP\ILogger $logger - * @param \OCP\IConfig $config - */ - public function __construct(ILogger $logger, IConfig $config) { + public function __construct(LoggerInterface $logger, IConfig $config, IniGetWrapper $iniGetWrapper) { $this->log = $logger; $this->config = $config; + $this->iniGetWrapper = $iniGetWrapper; $this->tmpBaseDir = $this->getTempBaseDir(); } @@ -218,7 +219,7 @@ class TempManager implements ITempManager { if ($temp = $this->config->getSystemValue('tempdirectory', null)) { $directories[] = $temp; } - if ($temp = \OC::$server->getIniWrapper()->get('upload_tmp_dir')) { + if ($temp = $this->iniGetWrapper->get('upload_tmp_dir')) { $directories[] = $temp; } if ($temp = getenv('TMP')) { diff --git a/lib/private/Template/Base.php b/lib/private/Template/Base.php index 04d03b6e6f5..c95958ceea1 100644 --- a/lib/private/Template/Base.php +++ b/lib/private/Template/Base.php @@ -4,7 +4,6 @@ * * @author Bart Visscher <bartv@thisnet.nl> * @author Björn Schießle <bjoern@schiessle.org> - * @author Christopher Schäpers <kondou@ts.unde.re> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Julius Härtl <jus@bitgrid.net> @@ -31,6 +30,7 @@ namespace OC\Template; use OCP\Defaults; +use Throwable; class Base { private $template; // The template @@ -92,7 +92,7 @@ class Base { /** * Assign variables * @param string $key key - * @param array|bool|integer|string $value value + * @param array|bool|integer|string|Throwable $value value * @return bool * * This function assigns a variable. It can be accessed via $_[$key] in diff --git a/lib/private/Template/CSSResourceLocator.php b/lib/private/Template/CSSResourceLocator.php index 750d33fd726..7b33e181f76 100644 --- a/lib/private/Template/CSSResourceLocator.php +++ b/lib/private/Template/CSSResourceLocator.php @@ -66,7 +66,7 @@ class CSSResourceLocator extends ResourceLocator { ) { return; } - $style = substr($style, strpos($style, '/')+1); + $style = substr($style, strpos($style, '/') + 1); $app_path = \OC_App::getAppPath($app); $app_url = \OC_App::getAppWebPath($app); @@ -109,7 +109,7 @@ class CSSResourceLocator extends ResourceLocator { if (is_file($root.'/'.$file)) { if ($this->scssCacher !== null) { if ($this->scssCacher->process($root, $file, $app)) { - $this->append($root, $this->scssCacher->getCachedSCSS($app, $file), \OC::$WEBROOT, true, true); + $this->append($this->serverroot, $this->scssCacher->getCachedSCSS($app, $file), \OC::$WEBROOT, true, true); return true; } else { $this->logger->warning('Failed to compile and/or save '.$root.'/'.$file, ['app' => 'core']); @@ -145,7 +145,7 @@ class CSSResourceLocator extends ResourceLocator { } } - $this->resources[] = [$webRoot? : \OC::$WEBROOT, $webRoot, $file]; + $this->resources[] = [$webRoot ?: \OC::$WEBROOT, $webRoot, $file]; } } } diff --git a/lib/private/Template/IconsCacher.php b/lib/private/Template/IconsCacher.php index f9497c9a144..9b80711dd29 100644 --- a/lib/private/Template/IconsCacher.php +++ b/lib/private/Template/IconsCacher.php @@ -79,10 +79,10 @@ class IconsCacher { Factory $appDataFactory, IURLGenerator $urlGenerator, ITimeFactory $timeFactory) { - $this->logger = $logger; - $this->appData = $appDataFactory->get('css'); + $this->logger = $logger; + $this->appData = $appDataFactory->get('css'); $this->urlGenerator = $urlGenerator; - $this->timeFactory = $timeFactory; + $this->timeFactory = $timeFactory; try { $this->folder = $this->appData->getFolder('icons'); diff --git a/lib/private/Template/JSCombiner.php b/lib/private/Template/JSCombiner.php index 48f6dadfd6a..e9e4333c380 100644 --- a/lib/private/Template/JSCombiner.php +++ b/lib/private/Template/JSCombiner.php @@ -138,7 +138,7 @@ class JSCombiner { return false; } - foreach ($deps as $file=>$mtime) { + foreach ($deps as $file => $mtime) { if (!file_exists($file) || filemtime($file) > $mtime) { return false; } diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php index e18081f3a8d..69624825ae4 100644 --- a/lib/private/Template/JSConfigHelper.php +++ b/lib/private/Template/JSConfigHelper.php @@ -3,6 +3,7 @@ * @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl> * * @author Abijeet <abijeetpatro@gmail.com> + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Bjoern Schiessle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Joas Schilling <coding@schilljs.com> @@ -33,6 +34,7 @@ namespace OC\Template; use bantu\IniGetWrapper\IniGetWrapper; use OC\CapabilitiesManager; use OCP\App\IAppManager; +use OCP\Constants; use OCP\Defaults; use OCP\IConfig; use OCP\IGroupManager; @@ -160,7 +162,7 @@ class JSConfigHelper { $defaultInternalExpireDate = $defaultInternalExpireDateEnforced = null; if ($defaultInternalExpireDateEnabled) { $defaultInternalExpireDate = (int)$this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7'); - $defaultInternalExpireDateEnforced = $this->config->getAppValue('core', 'shareapi_internal_enforce_expire_date', 'no') === 'yes'; + $defaultInternalExpireDateEnforced = $this->config->getAppValue('core', 'shareapi_enforce_internal_expire_date', 'no') === 'yes'; } $countOfDataLocation = 0; @@ -189,8 +191,8 @@ class JSConfigHelper { 'enable_avatars' => true, // here for legacy reasons - to not crash existing code that relies on this value 'lost_password_link' => $this->config->getSystemValue('lost_password_link', null), 'modRewriteWorking' => $this->config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true', - 'sharing.maxAutocompleteResults' => (int)$this->config->getSystemValue('sharing.maxAutocompleteResults', 0), - 'sharing.minSearchStringLength' => (int)$this->config->getSystemValue('sharing.minSearchStringLength', 0), + 'sharing.maxAutocompleteResults' => max(0, $this->config->getSystemValueInt('sharing.maxAutocompleteResults', Constants::SHARING_MAX_AUTOCOMPLETE_RESULTS_DEFAULT)), + 'sharing.minSearchStringLength' => $this->config->getSystemValueInt('sharing.minSearchStringLength', 0), 'blacklist_files_regex' => \OCP\Files\FileInfo::BLACKLIST_FILES_REGEX, ]; diff --git a/lib/private/Template/JSResourceLocator.php b/lib/private/Template/JSResourceLocator.php index 50af5fd34cc..2f0fe16f491 100644 --- a/lib/private/Template/JSResourceLocator.php +++ b/lib/private/Template/JSResourceLocator.php @@ -75,7 +75,7 @@ class JSResourceLocator extends ResourceLocator { } $app = substr($script, 0, strpos($script, '/')); - $script = substr($script, strpos($script, '/')+1); + $script = substr($script, strpos($script, '/') + 1); $app_path = \OC_App::getAppPath($app); $app_url = \OC_App::getAppWebPath($app); diff --git a/lib/private/Template/SCSSCacher.php b/lib/private/Template/SCSSCacher.php index 120ed585e16..1c6ca661839 100644 --- a/lib/private/Template/SCSSCacher.php +++ b/lib/private/Template/SCSSCacher.php @@ -30,6 +30,7 @@ namespace OC\Template; +use OC\AppConfig; use OC\Files\AppData\Factory; use OC\Memcache\NullCache; use OCP\AppFramework\Utility\ITimeFactory; @@ -89,6 +90,8 @@ class SCSSCacher { /** @var IMemcache */ private $lockingCache; + /** @var AppConfig */ + private $appConfig; /** * @param ILogger $logger @@ -109,15 +112,16 @@ class SCSSCacher { $serverRoot, ICacheFactory $cacheFactory, IconsCacher $iconsCacher, - ITimeFactory $timeFactory) { - $this->logger = $logger; - $this->appData = $appDataFactory->get('css'); + ITimeFactory $timeFactory, + AppConfig $appConfig) { + $this->logger = $logger; + $this->appData = $appDataFactory->get('css'); $this->urlGenerator = $urlGenerator; - $this->config = $config; - $this->defaults = $defaults; - $this->serverRoot = $serverRoot; + $this->config = $config; + $this->defaults = $defaults; + $this->serverRoot = $serverRoot; $this->cacheFactory = $cacheFactory; - $this->depsCache = $cacheFactory->createDistributed('SCSS-deps-' . md5($this->urlGenerator->getBaseUrl())); + $this->depsCache = $cacheFactory->createDistributed('SCSS-deps-' . md5($this->urlGenerator->getBaseUrl())); $this->isCachedCache = $cacheFactory->createDistributed('SCSS-cached-' . md5($this->urlGenerator->getBaseUrl())); $lockingCache = $cacheFactory->createDistributed('SCSS-locks-' . md5($this->urlGenerator->getBaseUrl())); if (!($lockingCache instanceof IMemcache)) { @@ -126,6 +130,7 @@ class SCSSCacher { $this->lockingCache = $lockingCache; $this->iconsCacher = $iconsCacher; $this->timeFactory = $timeFactory; + $this->appConfig = $appConfig; } /** @@ -141,11 +146,12 @@ class SCSSCacher { $path = explode('/', $root . '/' . $file); $fileNameSCSS = array_pop($path); - $fileNameCSS = $this->prependVersionPrefix($this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileNameSCSS)), $app); + $fileNameCSS = $this->prependVersionPrefix($this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileNameSCSS)), $app); - $path = implode('/', $path); + $path = implode('/', $path); $webDir = $this->getWebDir($path, $app, $this->serverRoot, \OC::$WEBROOT); + $this->logger->debug('SCSSCacher::process ordinary check follows', ['app' => 'scss_cacher']); if (!$this->variablesChanged() && $this->isCached($fileNameCSS, $app)) { // Inject icons vars css if any return $this->injectCssVariablesIfAny(); @@ -161,23 +167,25 @@ class SCSSCacher { $lockKey = $webDir . '/' . $fileNameSCSS; if (!$this->lockingCache->add($lockKey, 'locked!', 120)) { + $this->logger->debug('SCSSCacher::process could not get lock for ' . $lockKey . ' and will wait 10 seconds for cached file to be available', ['app' => 'scss_cacher']); $retry = 0; sleep(1); while ($retry < 10) { + $this->appConfig->clearCachedConfig(); + $this->logger->debug('SCSSCacher::process check in while loop follows', ['app' => 'scss_cacher']); if (!$this->variablesChanged() && $this->isCached($fileNameCSS, $app)) { // Inject icons vars css if any - $this->lockingCache->remove($lockKey); - $this->logger->debug('SCSSCacher: ' .$lockKey.' is now available after '.$retry.'s. Moving on...', ['app' => 'core']); + $this->logger->debug("SCSSCacher::process cached file for app '$app' and file '$fileNameCSS' is now available after $retry s. Moving on...", ['app' => 'scss_cacher']); return $this->injectCssVariablesIfAny(); } - $this->logger->debug('SCSSCacher: scss cache file locked for '.$lockKey, ['app' => 'core']); sleep(1); $retry++; } - $this->logger->debug('SCSSCacher: Giving up scss caching for '.$lockKey, ['app' => 'core']); + $this->logger->debug('SCSSCacher::process Giving up scss caching for ' . $lockKey, ['app' => 'scss_cacher']); return false; } + $this->logger->debug('SCSSCacher::process Lock acquired for ' . $lockKey, ['app' => 'scss_cacher']); try { $cached = $this->cache($path, $fileNameCSS, $fileNameSCSS, $folder, $webDir); } catch (\Exception $e) { @@ -187,6 +195,7 @@ class SCSSCacher { // Cleaning lock $this->lockingCache->remove($lockKey); + $this->logger->debug('SCSSCacher::process Lock removed for ' . $lockKey, ['app' => 'scss_cacher']); // Inject icons vars css if any if ($this->iconsCacher->getCachedCSS() && $this->iconsCacher->getCachedCSS()->getSize() > 0) { @@ -202,7 +211,7 @@ class SCSSCacher { * @return ISimpleFile */ public function getCachedCSS(string $appName, string $fileName): ISimpleFile { - $folder = $this->appData->getFolder($appName); + $folder = $this->appData->getFolder($appName); $cachedFileName = $this->prependVersionPrefix($this->prependBaseurlPrefix($fileName), $appName); return $folder->getFile($cachedFileName); @@ -224,11 +233,13 @@ class SCSSCacher { return true; } } + $this->logger->debug("SCSSCacher::isCached $fileNameCSS isCachedCache is expired or unset", ['app' => 'scss_cacher']); // Creating file cache if none for further checks try { $folder = $this->appData->getFolder($app); } catch (NotFoundException $e) { + $this->logger->debug("SCSSCacher::isCached app data folder for $app could not be fetched", ['app' => 'scss_cacher']); return false; } @@ -238,10 +249,10 @@ class SCSSCacher { $cachedFile = $folder->getFile($fileNameCSS); if ($cachedFile->getSize() > 0) { $depFileName = $fileNameCSS . '.deps'; - $deps = $this->depsCache->get($folder->getName() . '-' . $depFileName); + $deps = $this->depsCache->get($folder->getName() . '-' . $depFileName); if ($deps === null) { $depFile = $folder->getFile($depFileName); - $deps = $depFile->getContent(); + $deps = $depFile->getContent(); // Set to memcache for next run $this->depsCache->set($folder->getName() . '-' . $depFileName, $deps); } @@ -249,16 +260,20 @@ class SCSSCacher { foreach ((array) $deps as $file => $mtime) { if (!file_exists($file) || filemtime($file) > $mtime) { + $this->logger->debug("SCSSCacher::isCached $fileNameCSS is not considered as cached due to deps file $file", ['app' => 'scss_cacher']); return false; } } + $this->logger->debug("SCSSCacher::isCached $fileNameCSS dependencies successfully cached for 5 minutes", ['app' => 'scss_cacher']); + // It would probably make sense to adjust this timeout to something higher and see if that has some effect then $this->isCachedCache->set($key, $this->timeFactory->getTime() + 5 * 60); return true; } - + $this->logger->debug("SCSSCacher::isCached $fileNameCSS is not considered as cached cacheValue: $cacheValue", ['app' => 'scss_cacher']); return false; } catch (NotFoundException $e) { + $this->logger->debug("SCSSCacher::isCached NotFoundException " . $e->getMessage(), ['app' => 'scss_cacher']); return false; } } @@ -268,8 +283,10 @@ class SCSSCacher { * @return bool */ private function variablesChanged(): bool { - $injectedVariables = $this->getInjectedVariables(); - if ($this->config->getAppValue('core', 'theming.variables') !== md5($injectedVariables)) { + $cachedVariables = $this->config->getAppValue('core', 'theming.variables', ''); + $injectedVariables = $this->getInjectedVariables($cachedVariables); + if ($cachedVariables !== md5($injectedVariables)) { + $this->logger->debug('SCSSCacher::variablesChanged storedVariables: ' . json_encode($this->config->getAppValue('core', 'theming.variables')) . ' currentInjectedVariables: ' . json_encode($injectedVariables), ['app' => 'scss_cacher']); $this->config->setAppValue('core', 'theming.variables', md5($injectedVariables)); $this->resetCache(); return true; @@ -328,7 +345,7 @@ class SCSSCacher { '@import "functions.scss";' . '@import "' . $fileNameSCSS . '";'); } catch (ParserException $e) { - $this->logger->logException($e, ['app' => 'core']); + $this->logger->logException($e, ['app' => 'scss_cacher']); return false; } @@ -350,11 +367,11 @@ class SCSSCacher { $depFile->putContent($deps); $this->depsCache->set($folder->getName() . '-' . $depFileName, $deps); $gzipFile->putContent(gzencode($data, 9)); - $this->logger->debug('SCSSCacher: ' . $webDir . '/' . $fileNameSCSS . ' compiled and successfully cached', ['app' => 'core']); + $this->logger->debug('SCSSCacher::cache ' . $webDir . '/' . $fileNameSCSS . ' compiled and successfully cached', ['app' => 'scss_cacher']); return true; } catch (NotPermittedException $e) { - $this->logger->error('SCSSCacher: unable to cache: ' . $fileNameSCSS); + $this->logger->error('SCSSCacher::cache unable to cache: ' . $fileNameSCSS, ['app' => 'scss_cacher']); return false; } @@ -365,9 +382,12 @@ class SCSSCacher { * We need to regenerate all files when variables change */ public function resetCache() { + $this->logger->debug('SCSSCacher::resetCache', ['app' => 'scss_cacher']); if (!$this->lockingCache->add('resetCache', 'locked!', 120)) { + $this->logger->debug('SCSSCacher::resetCache Locked', ['app' => 'scss_cacher']); return; } + $this->logger->debug('SCSSCacher::resetCache Lock acquired', ['app' => 'scss_cacher']); $this->injectedVariables = null; // do not clear locks @@ -380,18 +400,19 @@ class SCSSCacher { try { $file->delete(); } catch (NotPermittedException $e) { - $this->logger->logException($e, ['message' => 'SCSSCacher: unable to delete file: ' . $file->getName()]); + $this->logger->logException($e, ['message' => 'SCSSCacher::resetCache unable to delete file: ' . $file->getName(), 'app' => 'scss_cacher']); } } } - $this->logger->debug('SCSSCacher: css cache cleared!'); + $this->logger->debug('SCSSCacher::resetCache css cache cleared!', ['app' => 'scss_cacher']); $this->lockingCache->remove('resetCache'); + $this->logger->debug('SCSSCacher::resetCache Locking removed', ['app' => 'scss_cacher']); } /** * @return string SCSS code for variables from OC_Defaults */ - private function getInjectedVariables(): string { + private function getInjectedVariables(string $cache = ''): string { if ($this->injectedVariables !== null) { return $this->injectedVariables; } @@ -400,13 +421,22 @@ class SCSSCacher { $variables .= '$' . $key . ': ' . $value . ' !default;'; } + /* + * If we are trying to return the same variables as that are cached + * Then there is no need to do the compile step + */ + if ($cache === md5($variables)) { + $this->injectedVariables = $variables; + return $variables; + } + // check for valid variables / otherwise fall back to defaults try { $scss = new Compiler(); $scss->compile($variables); $this->injectedVariables = $variables; } catch (ParserException $e) { - $this->logger->logException($e, ['app' => 'core']); + $this->logger->logException($e, ['app' => 'scss_cacher']); } return $variables; @@ -419,7 +449,7 @@ class SCSSCacher { * @return string */ private function rebaseUrls(string $css, string $webDir): string { - $re = '/url\([\'"]([^\/][\.\w?=\/-]*)[\'"]\)/x'; + $re = '/url\([\'"]([^\/][\.\w?=\/-]*)[\'"]\)/x'; $subst = 'url(\'' . $webDir . '/$1\')'; return preg_replace($re, $subst, $css); @@ -433,8 +463,8 @@ class SCSSCacher { */ public function getCachedSCSS(string $appName, string $fileName): string { $tmpfileLoc = explode('/', $fileName); - $fileName = array_pop($tmpfileLoc); - $fileName = $this->prependVersionPrefix($this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileName)), $appName); + $fileName = array_pop($tmpfileLoc); + $fileName = $this->prependVersionPrefix($this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileName)), $appName); return substr($this->urlGenerator->linkToRoute('core.Css.getCss', [ 'fileName' => $fileName, diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index 69eb26ab8b2..f8f6536cee5 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -3,7 +3,6 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Bart Visscher <bartv@thisnet.nl> - * @author Bjoern Schiessle <bjoern@schiessle.org> * @author Christopher Schäpers <kondou@ts.unde.re> * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Clark Tomlinson <fallen013@gmail.com> @@ -44,38 +43,52 @@ namespace OC; -use OC\AppFramework\Http\Request; +use bantu\IniGetWrapper\IniGetWrapper; +use OC\Search\SearchQuery; use OC\Template\JSCombiner; use OC\Template\JSConfigHelper; use OC\Template\SCSSCacher; use OCP\AppFramework\Http\TemplateResponse; use OCP\Defaults; +use OCP\IConfig; use OCP\IInitialStateService; +use OCP\INavigationManager; use OCP\Support\Subscription\IRegistry; +use OCP\Util; class TemplateLayout extends \OC_Template { private static $versionHash = ''; - /** - * @var \OCP\IConfig - */ + /** @var IConfig */ private $config; + /** @var IInitialStateService */ + private $initialState; + + /** @var INavigationManager */ + private $navigationManager; + /** * @param string $renderAs * @param string $appId application id */ public function __construct($renderAs, $appId = '') { - // yes - should be injected .... - $this->config = \OC::$server->getConfig(); + /** @var IConfig */ + $this->config = \OC::$server->get(IConfig::class); + + /** @var IInitialStateService */ + $this->initialState = \OC::$server->get(IInitialStateService::class); - if (\OCP\Util::isIE()) { - \OC_Util::addStyle('ie'); + if (Util::isIE()) { + Util::addStyle('ie'); } // Decide which page we show if ($renderAs === TemplateResponse::RENDER_AS_USER) { + /** @var INavigationManager */ + $this->navigationManager = \OC::$server->get(INavigationManager::class); + parent::__construct('core', 'layout.user'); if (in_array(\OC_App::getCurrentApp(), ['settings','admin', 'help']) !== false) { $this->assign('bodyid', 'body-settings'); @@ -83,13 +96,19 @@ class TemplateLayout extends \OC_Template { $this->assign('bodyid', 'body-user'); } + $this->initialState->provideInitialState('core', 'active-app', $this->navigationManager->getActiveEntry()); + $this->initialState->provideInitialState('unified-search', 'limit-default', SearchQuery::LIMIT_DEFAULT); + Util::addScript('dist/unified-search', null, true); + // Add navigation entry $this->assign('application', ''); $this->assign('appid', $appId); - $navigation = \OC::$server->getNavigationManager()->getAll(); + + $navigation = $this->navigationManager->getAll(); $this->assign('navigation', $navigation); - $settingsNavigation = \OC::$server->getNavigationManager()->getAll('settings'); + $settingsNavigation = $this->navigationManager->getAll('settings'); $this->assign('settingsnavigation', $settingsNavigation); + foreach ($navigation as $entry) { if ($entry['active']) { $this->assign('application', $entry['name']); @@ -110,7 +129,7 @@ class TemplateLayout extends \OC_Template { if (\OC_User::getUser() === false) { $this->assign('userAvatarSet', false); } else { - $this->assign('userAvatarSet', \OC::$server->getAvatarManager()->getAvatar(\OC_User::getUser())->exists()); + $this->assign('userAvatarSet', true); $this->assign('userAvatarVersion', $this->config->getUserValue(\OC_User::getUser(), 'avatar', 'version', 0)); } @@ -174,21 +193,24 @@ class TemplateLayout extends \OC_Template { $jsFiles = self::findJavascriptFiles(\OC_Util::$scripts); $this->assign('jsfiles', []); if ($this->config->getSystemValue('installed', false) && $renderAs != TemplateResponse::RENDER_AS_ERROR) { + // this is on purpose outside of the if statement below so that the initial state is prefilled (done in the getConfig() call) + // see https://github.com/nextcloud/server/pull/22636 for details + $jsConfigHelper = new JSConfigHelper( + \OC::$server->getL10N('lib'), + \OC::$server->query(Defaults::class), + \OC::$server->getAppManager(), + \OC::$server->getSession(), + \OC::$server->getUserSession()->getUser(), + $this->config, + \OC::$server->getGroupManager(), + \OC::$server->get(IniGetWrapper::class), + \OC::$server->getURLGenerator(), + \OC::$server->getCapabilitiesManager(), + \OC::$server->query(IInitialStateService::class) + ); + $config = $jsConfigHelper->getConfig(); if (\OC::$server->getContentSecurityPolicyNonceManager()->browserSupportsCspV3()) { - $jsConfigHelper = new JSConfigHelper( - \OC::$server->getL10N('lib'), - \OC::$server->query(Defaults::class), - \OC::$server->getAppManager(), - \OC::$server->getSession(), - \OC::$server->getUserSession()->getUser(), - $this->config, - \OC::$server->getGroupManager(), - \OC::$server->getIniWrapper(), - \OC::$server->getURLGenerator(), - \OC::$server->getCapabilitiesManager(), - \OC::$server->query(IInitialStateService::class) - ); - $this->assign('inline_ocjs', $jsConfigHelper->getConfig()); + $this->assign('inline_ocjs', $config); } else { $this->append('jsfiles', \OC::$server->getURLGenerator()->linkToRoute('core.OCJS.getConfig', ['v' => self::$versionHash])); } @@ -241,9 +263,7 @@ class TemplateLayout extends \OC_Template { } } - /** @var InitialStateService $initialState */ - $initialState = \OC::$server->query(InitialStateService::class); - $this->assign('initialStates', $initialState->getInitialStates()); + $this->assign('initialStates', $this->initialState->getInitialStates()); } /** diff --git a/lib/private/URLGenerator.php b/lib/private/URLGenerator.php index b432b0b69ca..f0de31568f4 100644 --- a/lib/private/URLGenerator.php +++ b/lib/private/URLGenerator.php @@ -39,6 +39,7 @@ declare(strict_types=1); namespace OC; +use OC\Route\Router; use OCA\Theming\ThemingDefaults; use OCP\ICacheFactory; use OCP\IConfig; @@ -56,31 +57,30 @@ class URLGenerator implements IURLGenerator { private $cacheFactory; /** @var IRequest */ private $request; + /** @var Router */ + private $router; - /** - * @param IConfig $config - * @param ICacheFactory $cacheFactory - * @param IRequest $request - */ public function __construct(IConfig $config, ICacheFactory $cacheFactory, - IRequest $request) { + IRequest $request, + Router $router) { $this->config = $config; $this->cacheFactory = $cacheFactory; $this->request = $request; + $this->router = $router; } /** * Creates an url using a defined route - * @param string $route - * @param array $parameters args with param=>value, will be appended to the returned url + * + * @param string $routeName + * @param array $arguments args with param=>value, will be appended to the returned url * @return string the url * * Returns a url to the given route. */ - public function linkToRoute(string $route, array $parameters = []): string { - // TODO: mock router - return \OC::$server->getRouter()->generate($route, $parameters); + public function linkToRoute(string $routeName, array $arguments = []): string { + return $this->router->generate($routeName, $arguments); } /** @@ -96,7 +96,7 @@ class URLGenerator implements IURLGenerator { } public function linkToOCSRouteAbsolute(string $routeName, array $arguments = []): string { - $route = \OC::$server->getRouter()->generate('ocs.'.$routeName, $arguments, false); + $route = $this->router->generate('ocs.'.$routeName, $arguments, false); $indexPhpPos = strpos($route, '/index.php/'); if ($indexPhpPos !== false) { @@ -111,7 +111,8 @@ class URLGenerator implements IURLGenerator { /** * Creates an url - * @param string $app app + * + * @param string $appName app * @param string $file file * @param array $args array with param=>value, will be appended to the returned url * The value of $args will be urlencoded @@ -119,24 +120,24 @@ class URLGenerator implements IURLGenerator { * * Returns a url to the given app and file. */ - public function linkTo(string $app, string $file, array $args = []): string { + public function linkTo(string $appName, string $file, array $args = []): string { $frontControllerActive = ($this->config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true'); - if ($app !== '') { - $app_path = \OC_App::getAppPath($app); + if ($appName !== '') { + $app_path = \OC_App::getAppPath($appName); // Check if the app is in the app folder if ($app_path && file_exists($app_path . '/' . $file)) { if (substr($file, -3) === 'php') { - $urlLinkTo = \OC::$WEBROOT . '/index.php/apps/' . $app; + $urlLinkTo = \OC::$WEBROOT . '/index.php/apps/' . $appName; if ($frontControllerActive) { - $urlLinkTo = \OC::$WEBROOT . '/apps/' . $app; + $urlLinkTo = \OC::$WEBROOT . '/apps/' . $appName; } $urlLinkTo .= ($file !== 'index.php') ? '/' . $file : ''; } else { - $urlLinkTo = \OC_App::getAppWebPath($app) . '/' . $file; + $urlLinkTo = \OC_App::getAppWebPath($appName) . '/' . $file; } } else { - $urlLinkTo = \OC::$WEBROOT . '/' . $app . '/' . $file; + $urlLinkTo = \OC::$WEBROOT . '/' . $appName . '/' . $file; } } else { if (file_exists(\OC::$SERVERROOT . '/core/' . $file)) { @@ -159,16 +160,17 @@ class URLGenerator implements IURLGenerator { /** * Creates path to an image - * @param string $app app - * @param string $image image name + * + * @param string $appName app + * @param string $file image name * @throws \RuntimeException If the image does not exist * @return string the url * * Returns the path to the image. */ - public function imagePath(string $app, string $image): string { + public function imagePath(string $appName, string $file): string { $cache = $this->cacheFactory->createDistributed('imagePath-'.md5($this->getBaseUrl()).'-'); - $cacheKey = $app.'-'.$image; + $cacheKey = $appName.'-'.$file; if ($key = $cache->get($cacheKey)) { return $key; } @@ -177,9 +179,9 @@ class URLGenerator implements IURLGenerator { $theme = \OC_Util::getTheme(); //if a theme has a png but not an svg always use the png - $basename = substr(basename($image),0,-4); + $basename = substr(basename($file),0,-4); - $appPath = \OC_App::getAppPath($app); + $appPath = \OC_App::getAppPath($appName); // Check if the app is in the app folder $path = ''; @@ -188,42 +190,42 @@ class URLGenerator implements IURLGenerator { if ($themingEnabled) { $themingDefaults = \OC::$server->getThemingDefaults(); if ($themingDefaults instanceof ThemingDefaults) { - $themingImagePath = $themingDefaults->replaceImagePath($app, $image); + $themingImagePath = $themingDefaults->replaceImagePath($appName, $file); } } - if (file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$app/img/$image")) { - $path = \OC::$WEBROOT . "/themes/$theme/apps/$app/img/$image"; - } elseif (!file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$app/img/$basename.svg") - && file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$app/img/$basename.png")) { - $path = \OC::$WEBROOT . "/themes/$theme/apps/$app/img/$basename.png"; - } elseif (!empty($app) and file_exists(\OC::$SERVERROOT . "/themes/$theme/$app/img/$image")) { - $path = \OC::$WEBROOT . "/themes/$theme/$app/img/$image"; - } elseif (!empty($app) and (!file_exists(\OC::$SERVERROOT . "/themes/$theme/$app/img/$basename.svg") - && file_exists(\OC::$SERVERROOT . "/themes/$theme/$app/img/$basename.png"))) { - $path = \OC::$WEBROOT . "/themes/$theme/$app/img/$basename.png"; - } elseif (file_exists(\OC::$SERVERROOT . "/themes/$theme/core/img/$image")) { - $path = \OC::$WEBROOT . "/themes/$theme/core/img/$image"; + if (file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$appName/img/$file")) { + $path = \OC::$WEBROOT . "/themes/$theme/apps/$appName/img/$file"; + } elseif (!file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$appName/img/$basename.svg") + && file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$appName/img/$basename.png")) { + $path = \OC::$WEBROOT . "/themes/$theme/apps/$appName/img/$basename.png"; + } elseif (!empty($appName) and file_exists(\OC::$SERVERROOT . "/themes/$theme/$appName/img/$file")) { + $path = \OC::$WEBROOT . "/themes/$theme/$appName/img/$file"; + } elseif (!empty($appName) and (!file_exists(\OC::$SERVERROOT . "/themes/$theme/$appName/img/$basename.svg") + && file_exists(\OC::$SERVERROOT . "/themes/$theme/$appName/img/$basename.png"))) { + $path = \OC::$WEBROOT . "/themes/$theme/$appName/img/$basename.png"; + } elseif (file_exists(\OC::$SERVERROOT . "/themes/$theme/core/img/$file")) { + $path = \OC::$WEBROOT . "/themes/$theme/core/img/$file"; } elseif (!file_exists(\OC::$SERVERROOT . "/themes/$theme/core/img/$basename.svg") && file_exists(\OC::$SERVERROOT . "/themes/$theme/core/img/$basename.png")) { - $path = \OC::$WEBROOT . "/themes/$theme/core/img/$basename.png"; + $path = \OC::$WEBROOT . "/themes/$theme/core/img/$basename.png"; } elseif ($themingEnabled && $themingImagePath) { $path = $themingImagePath; - } elseif ($appPath && file_exists($appPath . "/img/$image")) { - $path = \OC_App::getAppWebPath($app) . "/img/$image"; + } elseif ($appPath && file_exists($appPath . "/img/$file")) { + $path = \OC_App::getAppWebPath($appName) . "/img/$file"; } elseif ($appPath && !file_exists($appPath . "/img/$basename.svg") && file_exists($appPath . "/img/$basename.png")) { - $path = \OC_App::getAppWebPath($app) . "/img/$basename.png"; - } elseif (!empty($app) and file_exists(\OC::$SERVERROOT . "/$app/img/$image")) { - $path = \OC::$WEBROOT . "/$app/img/$image"; - } elseif (!empty($app) and (!file_exists(\OC::$SERVERROOT . "/$app/img/$basename.svg") - && file_exists(\OC::$SERVERROOT . "/$app/img/$basename.png"))) { - $path = \OC::$WEBROOT . "/$app/img/$basename.png"; - } elseif (file_exists(\OC::$SERVERROOT . "/core/img/$image")) { - $path = \OC::$WEBROOT . "/core/img/$image"; + $path = \OC_App::getAppWebPath($appName) . "/img/$basename.png"; + } elseif (!empty($appName) and file_exists(\OC::$SERVERROOT . "/$appName/img/$file")) { + $path = \OC::$WEBROOT . "/$appName/img/$file"; + } elseif (!empty($appName) and (!file_exists(\OC::$SERVERROOT . "/$appName/img/$basename.svg") + && file_exists(\OC::$SERVERROOT . "/$appName/img/$basename.png"))) { + $path = \OC::$WEBROOT . "/$appName/img/$basename.png"; + } elseif (file_exists(\OC::$SERVERROOT . "/core/img/$file")) { + $path = \OC::$WEBROOT . "/core/img/$file"; } elseif (!file_exists(\OC::$SERVERROOT . "/core/img/$basename.svg") && file_exists(\OC::$SERVERROOT . "/core/img/$basename.png")) { - $path = \OC::$WEBROOT . "/themes/$theme/core/img/$basename.png"; + $path = \OC::$WEBROOT . "/themes/$theme/core/img/$basename.png"; } if ($path !== '') { @@ -231,7 +233,7 @@ class URLGenerator implements IURLGenerator { return $path; } - throw new RuntimeException('image not found: image:' . $image . ' webroot:' . \OC::$WEBROOT . ' serverroot:' . \OC::$SERVERROOT); + throw new RuntimeException('image not found: image:' . $file . ' webroot:' . \OC::$WEBROOT . ' serverroot:' . \OC::$SERVERROOT); } diff --git a/lib/private/Updater.php b/lib/private/Updater.php index 4b5d02aeb64..ec0a50cc6ca 100644 --- a/lib/private/Updater.php +++ b/lib/private/Updater.php @@ -9,6 +9,7 @@ * @author Frank Karlitschek <frank@karlitschek.de> * @author Georg Ehrke <oc.list@georgehrke.com> * @author Joas Schilling <coding@schilljs.com> + * @author Julius Härtl <jus@bitgrid.net> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> @@ -16,7 +17,7 @@ * @author Steffen Lindner <mail@steffen-lindner.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Victor Dubiniuk <dubiniuk@owncloud.com> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -192,8 +193,12 @@ class Updater extends BasicEmitter { $currentVendor = $this->config->getAppValue('core', 'vendor', ''); // Vendor was not set correctly on install, so we have to white-list known versions - if ($currentVendor === '' && isset($allowedPreviousVersions['owncloud'][$oldVersion])) { + if ($currentVendor === '' && ( + isset($allowedPreviousVersions['owncloud'][$oldVersion]) || + isset($allowedPreviousVersions['owncloud'][$majorMinor]) + )) { $currentVendor = 'owncloud'; + $this->config->setAppValue('core', 'vendor', $currentVendor); } if ($currentVendor === 'nextcloud') { @@ -300,47 +305,6 @@ class Updater extends BasicEmitter { } /** - * @param string $version the oc version to check app compatibility with - */ - protected function checkAppUpgrade($version) { - $apps = \OC_App::getEnabledApps(); - $this->emit('\OC\Updater', 'appUpgradeCheckBefore'); - - $appManager = \OC::$server->getAppManager(); - foreach ($apps as $appId) { - $info = \OC_App::getAppInfo($appId); - $compatible = \OC_App::isAppCompatible($version, $info); - $isShipped = $appManager->isShipped($appId); - - if ($compatible && $isShipped && \OC_App::shouldUpgrade($appId)) { - /** - * FIXME: The preupdate check is performed before the database migration, otherwise database changes - * are not possible anymore within it. - Consider this when touching the code. - * @link https://github.com/owncloud/core/issues/10980 - * @see \OC_App::updateApp - */ - if (file_exists(\OC_App::getAppPath($appId) . '/appinfo/preupdate.php')) { - $this->includePreUpdate($appId); - } - if (file_exists(\OC_App::getAppPath($appId) . '/appinfo/database.xml')) { - $this->emit('\OC\Updater', 'appSimulateUpdate', [$appId]); - \OC_DB::simulateUpdateDbFromStructure(\OC_App::getAppPath($appId) . '/appinfo/database.xml'); - } - } - } - - $this->emit('\OC\Updater', 'appUpgradeCheck'); - } - - /** - * Includes the pre-update file. Done here to prevent namespace mixups. - * @param string $appId - */ - private function includePreUpdate($appId) { - include \OC_App::getAppPath($appId) . '/appinfo/preupdate.php'; - } - - /** * upgrades all apps within a major ownCloud upgrade. Also loads "priority" * (types authentication, filesystem, logging, in that order) afterwards. * @@ -401,7 +365,7 @@ class Updater extends BasicEmitter { $disabledApps = []; $appManager = \OC::$server->getAppManager(); foreach ($apps as $app) { - // check if the app is compatible with this version of ownCloud + // check if the app is compatible with this version of Nextcloud $info = OC_App::getAppInfo($app); if ($info === null || !OC_App::isAppCompatible($version, $info)) { if ($appManager->isShipped($app)) { diff --git a/lib/private/User/Backend.php b/lib/private/User/Backend.php index 477448279a5..c87dc5d2d50 100644 --- a/lib/private/User/Backend.php +++ b/lib/private/User/Backend.php @@ -39,14 +39,14 @@ abstract class Backend implements UserInterface { /** * actions that user backends can define */ - public const CREATE_USER = 1; // 1 << 0 - public const SET_PASSWORD = 16; // 1 << 4 - public const CHECK_PASSWORD = 256; // 1 << 8 - public const GET_HOME = 4096; // 1 << 12 - public const GET_DISPLAYNAME = 65536; // 1 << 16 - public const SET_DISPLAYNAME = 1048576; // 1 << 20 - public const PROVIDE_AVATAR = 16777216; // 1 << 24 - public const COUNT_USERS = 268435456; // 1 << 28 + public const CREATE_USER = 1; // 1 << 0 + public const SET_PASSWORD = 16; // 1 << 4 + public const CHECK_PASSWORD = 256; // 1 << 8 + public const GET_HOME = 4096; // 1 << 12 + public const GET_DISPLAYNAME = 65536; // 1 << 16 + public const SET_DISPLAYNAME = 1048576; // 1 << 20 + public const PROVIDE_AVATAR = 16777216; // 1 << 24 + public const COUNT_USERS = 268435456; // 1 << 28 protected $possibleActions = [ self::CREATE_USER => 'createUser', diff --git a/lib/private/User/Database.php b/lib/private/User/Database.php index e88549c8d81..7c936acd0bd 100644 --- a/lib/private/User/Database.php +++ b/lib/private/User/Database.php @@ -26,7 +26,7 @@ declare(strict_types=1); * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -293,14 +293,14 @@ class Database extends ABackend implements /** * Check if the password is correct * - * @param string $uid The username + * @param string $loginName The loginname * @param string $password The password * @return string * * Check if the password is correct without logging in the user * returns the user id or false */ - public function checkPassword(string $uid, string $password) { + public function checkPassword(string $loginName, string $password) { $this->fixDI(); $qb = $this->dbConn->getQueryBuilder(); @@ -308,7 +308,7 @@ class Database extends ABackend implements ->from($this->table) ->where( $qb->expr()->eq( - 'uid_lower', $qb->createNamedParameter(mb_strtolower($uid)) + 'uid_lower', $qb->createNamedParameter(mb_strtolower($loginName)) ) ); $result = $qb->execute(); @@ -320,7 +320,7 @@ class Database extends ABackend implements $newHash = ''; if (\OC::$server->getHasher()->verify($password, $storedHash, $newHash)) { if (!empty($newHash)) { - $this->updatePassword($uid, $newHash); + $this->updatePassword($loginName, $newHash); } return (string)$row['uid']; } diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php index 303050a7716..1d58c68268c 100644 --- a/lib/private/User/Manager.php +++ b/lib/private/User/Manager.php @@ -34,6 +34,7 @@ namespace OC\User; +use OC\HintException; use OC\Hooks\PublicEmitter; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\EventDispatcher\IEventDispatcher; @@ -42,8 +43,9 @@ use OCP\IGroup; use OCP\IUser; use OCP\IUserBackend; use OCP\IUserManager; +use OCP\Support\Subscription\IRegistry; use OCP\User\Backend\IGetRealUIDBackend; -use OCP\User\Events\CreateUserEvent; +use OCP\User\Events\BeforeUserCreatedEvent; use OCP\User\Events\UserCreatedEvent; use OCP\UserInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -297,6 +299,12 @@ class Manager extends PublicEmitter implements IUserManager { * @return bool|IUser the created user or false */ public function createUser($uid, $password) { + // DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency + if (\OC::$server->get(IRegistry::class)->delegateIsHardUserLimitReached()) { + $l = \OC::$server->getL10N('lib'); + throw new HintException($l->t('The user limit has been reached and the user was not created.')); + } + $localBackends = []; foreach ($this->backends as $backend) { if ($backend instanceof Database) { @@ -365,14 +373,16 @@ class Manager extends PublicEmitter implements IUserManager { throw new \InvalidArgumentException($l->t('The username is already being used')); } + /** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */ $this->emit('\OC\User', 'preCreateUser', [$uid, $password]); - $this->eventDispatcher->dispatchTyped(new CreateUserEvent($uid, $password)); + $this->eventDispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password)); $state = $backend->createUser($uid, $password); if ($state === false) { throw new \InvalidArgumentException($l->t('Could not create user')); } $user = $this->getUserObject($uid, $backend); if ($user instanceof IUser) { + /** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */ $this->emit('\OC\User', 'postCreateUser', [$user, $password]); $this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password)); } @@ -547,6 +557,7 @@ class Manager extends PublicEmitter implements IUserManager { /** * @param \Closure $callback + * @psalm-param \Closure(\OCP\IUser):?bool $callback * @since 11.0.0 */ public function callForSeenUsers(\Closure $callback) { diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 3996869c692..70c7f11c45f 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -11,6 +11,7 @@ * @author Greta Doci <gretadoci@gmail.com> * @author Joas Schilling <coding@schilljs.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Lionel Elie Mamane <lionel@mamane.lu> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> @@ -18,7 +19,7 @@ * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Sandro Lutz <sandro.lutz@temparus.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -80,7 +81,7 @@ use Symfony\Component\EventDispatcher\GenericEvent; * - preUnassignedUserId(string $uid) * - postUnassignedUserId(string $uid) * - preLogin(string $user, string $password) - * - postLogin(\OC\User\User $user, string $password) + * - postLogin(\OC\User\User $user, string $loginName, string $password, boolean $isTokenLogin) * - preRememberedLogin(string $uid) * - postRememberedLogin(\OC\User\User $user) * - logout() @@ -400,11 +401,13 @@ class Session implements IUserSession, Emitter { $this->dispatcher->dispatchTyped(new PostLoginEvent( $user, + $loginDetails['loginName'], $loginDetails['password'], $isToken )); $this->manager->emit('\OC\User', 'postLogin', [ $user, + $loginDetails['loginName'], $loginDetails['password'], $isToken, ]); @@ -811,15 +814,15 @@ class Session implements IUserSession, Emitter { */ public function tryTokenLogin(IRequest $request) { $authHeader = $request->getHeader('Authorization'); - if (strpos($authHeader, 'Bearer ') === false) { + if (strpos($authHeader, 'Bearer ') === 0) { + $token = substr($authHeader, 7); + } else { // No auth header, let's try session id try { $token = $this->session->getId(); } catch (SessionNotAvailableException $ex) { return false; } - } else { - $token = substr($authHeader, 7); } if (!$this->loginWithToken($token)) { @@ -829,8 +832,18 @@ class Session implements IUserSession, Emitter { return false; } - // Set the session variable so we know this is an app password - $this->session->set('app_password', $token); + try { + $dbToken = $this->tokenProvider->getToken($token); + } catch (InvalidTokenException $e) { + // Can't really happen but better save than sorry + return true; + } + + // Remember me tokens are not app_passwords + if ($dbToken->getRemember() === IToken::DO_NOT_REMEMBER) { + // Set the session variable so we know this is an app password + $this->session->set('app_password', $token); + } return true; } diff --git a/lib/private/User/User.php b/lib/private/User/User.php index 08bbce4701b..24082926b0d 100644 --- a/lib/private/User/User.php +++ b/lib/private/User/User.php @@ -16,7 +16,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -41,12 +41,18 @@ use OC\Avatar\AvatarManager; use OC\Files\Cache\Storage; use OC\Hooks\Emitter; use OC_Helper; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Group\Events\BeforeUserRemovedEvent; +use OCP\Group\Events\UserRemovedEvent; use OCP\IAvatarManager; use OCP\IConfig; use OCP\IImage; use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserBackend; +use OCP\User\Events\BeforeUserDeletedEvent; +use OCP\User\Events\UserDeletedEvent; +use OCP\User\GetQuotaEvent; use OCP\UserInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; @@ -61,6 +67,9 @@ class User implements IUser { /** @var UserInterface|null */ private $backend; /** @var EventDispatcherInterface */ + private $legacyDispatcher; + + /** @var IEventDispatcher */ private $dispatcher; /** @var bool */ @@ -87,7 +96,7 @@ class User implements IUser { public function __construct(string $uid, ?UserInterface $backend, EventDispatcherInterface $dispatcher, $emitter = null, IConfig $config = null, $urlGenerator = null) { $this->uid = $uid; $this->backend = $backend; - $this->dispatcher = $dispatcher; + $this->legacyDispatcher = $dispatcher; $this->emitter = $emitter; if (is_null($config)) { $config = \OC::$server->getConfig(); @@ -100,6 +109,8 @@ class User implements IUser { if (is_null($this->urlGenerator)) { $this->urlGenerator = \OC::$server->getURLGenerator(); } + // TODO: inject + $this->dispatcher = \OC::$server->query(IEventDispatcher::class); } /** @@ -203,10 +214,13 @@ class User implements IUser { * @return bool */ public function delete() { - $this->dispatcher->dispatch(IUser::class . '::preDelete', new GenericEvent($this)); + /** @deprecated 21.0.0 use BeforeUserDeletedEvent event with the IEventDispatcher instead */ + $this->legacyDispatcher->dispatch(IUser::class . '::preDelete', new GenericEvent($this)); if ($this->emitter) { + /** @deprecated 21.0.0 use BeforeUserDeletedEvent event with the IEventDispatcher instead */ $this->emitter->emit('\OC\User', 'preDelete', [$this]); } + $this->dispatcher->dispatchTyped(new BeforeUserDeletedEvent($this)); // get the home now because it won't return it after user deletion $homePath = $this->getHome(); $result = $this->backend->deleteUser($this->uid); @@ -219,9 +233,9 @@ class User implements IUser { foreach ($groupManager->getUserGroupIds($this) as $groupId) { $group = $groupManager->get($groupId); if ($group) { - \OC_Hook::emit("OC_Group", "pre_removeFromGroup", ["run" => true, "uid" => $this->uid, "gid" => $groupId]); + $this->dispatcher->dispatchTyped(new BeforeUserRemovedEvent($group, $this)); $group->removeUser($this); - \OC_Hook::emit("OC_User", "post_removeFromGroup", ["uid" => $this->uid, "gid" => $groupId]); + $this->dispatcher->dispatchTyped(new UserRemovedEvent($group, $this)); } } // Delete the user's keys in preferences @@ -252,10 +266,13 @@ class User implements IUser { $accountManager = \OC::$server->query(AccountManager::class); $accountManager->deleteUser($this); - $this->dispatcher->dispatch(IUser::class . '::postDelete', new GenericEvent($this)); + /** @deprecated 21.0.0 use UserDeletedEvent event with the IEventDispatcher instead */ + $this->legacyDispatcher->dispatch(IUser::class . '::postDelete', new GenericEvent($this)); if ($this->emitter) { + /** @deprecated 21.0.0 use UserDeletedEvent event with the IEventDispatcher instead */ $this->emitter->emit('\OC\User', 'postDelete', [$this]); } + $this->dispatcher->dispatchTyped(new UserDeletedEvent($this)); } return !($result === false); } @@ -268,7 +285,7 @@ class User implements IUser { * @return bool */ public function setPassword($password, $recoveryPassword = null) { - $this->dispatcher->dispatch(IUser::class . '::preSetPassword', new GenericEvent($this, [ + $this->legacyDispatcher->dispatch(IUser::class . '::preSetPassword', new GenericEvent($this, [ 'password' => $password, 'recoveryPassword' => $recoveryPassword, ])); @@ -277,7 +294,7 @@ class User implements IUser { } if ($this->backend->implementsActions(Backend::SET_PASSWORD)) { $result = $this->backend->setPassword($this->uid, $password); - $this->dispatcher->dispatch(IUser::class . '::postSetPassword', new GenericEvent($this, [ + $this->legacyDispatcher->dispatch(IUser::class . '::postSetPassword', new GenericEvent($this, [ 'password' => $password, 'recoveryPassword' => $recoveryPassword, ])); @@ -398,7 +415,15 @@ class User implements IUser { * @since 9.0.0 */ public function getQuota() { - $quota = $this->config->getUserValue($this->uid, 'files', 'quota', 'default'); + // allow apps to modify the user quota by hooking into the event + $event = new GetQuotaEvent($this); + $this->dispatcher->dispatchTyped($event); + $overwriteQuota = $event->getQuota(); + if ($overwriteQuota) { + $quota = $overwriteQuota; + } else { + $quota = $this->config->getUserValue($this->uid, 'files', 'quota', 'default'); + } if ($quota === 'default') { $quota = $this->config->getAppValue('files', 'default_quota', 'none'); } @@ -455,7 +480,7 @@ class User implements IUser { public function getCloudId() { $uid = $this->getUID(); $server = $this->urlGenerator->getAbsoluteURL('/'); - $server = rtrim($this->removeProtocolFromUrl($server), '/'); + $server = rtrim($this->removeProtocolFromUrl($server), '/'); return \OC::$server->getCloudIdManager()->getCloudId($uid, $server)->getId(); } @@ -474,7 +499,7 @@ class User implements IUser { } public function triggerChange($feature, $value = null, $oldValue = null) { - $this->dispatcher->dispatch(IUser::class . '::changeUser', new GenericEvent($this, [ + $this->legacyDispatcher->dispatch(IUser::class . '::changeUser', new GenericEvent($this, [ 'feature' => $feature, 'value' => $value, 'oldValue' => $oldValue, diff --git a/lib/private/UserStatus/Manager.php b/lib/private/UserStatus/Manager.php new file mode 100644 index 00000000000..aeef8df27f9 --- /dev/null +++ b/lib/private/UserStatus/Manager.php @@ -0,0 +1,106 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020, Georg Ehrke + * + * @author Georg Ehrke <oc.list@georgehrke.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\UserStatus; + +use OCP\ILogger; +use OCP\IServerContainer; +use OCP\UserStatus\IManager; +use OCP\UserStatus\IProvider; +use Psr\Container\ContainerExceptionInterface; + +class Manager implements IManager { + + /** @var IServerContainer */ + private $container; + + /** @var ILogger */ + private $logger; + + /** @var null */ + private $providerClass; + + /** @var IProvider */ + private $provider; + + /** + * Manager constructor. + * + * @param IServerContainer $container + * @param ILogger $logger + */ + public function __construct(IServerContainer $container, + ILogger $logger) { + $this->container = $container; + $this->logger = $logger; + } + + /** + * @inheritDoc + */ + public function getUserStatuses(array $userIds): array { + $this->setupProvider(); + if (!$this->provider) { + return []; + } + + return $this->provider->getUserStatuses($userIds); + } + + /** + * @param string $class + * @since 20.0.0 + * @internal + */ + public function registerProvider(string $class): void { + $this->providerClass = $class; + $this->provider = null; + } + + /** + * Lazily set up provider + */ + private function setupProvider(): void { + if ($this->provider !== null) { + return; + } + if ($this->providerClass === null) { + return; + } + + try { + $provider = $this->container->get($this->providerClass); + } catch (ContainerExceptionInterface $e) { + $this->logger->logException($e, [ + 'message' => 'Could not load user-status provider dynamically: ' . $e->getMessage(), + 'level' => ILogger::ERROR, + ]); + return; + } + + $this->provider = $provider; + } +} diff --git a/lib/private/legacy/OC_API.php b/lib/private/legacy/OC_API.php index 37a496a38fe..16a144269b3 100644 --- a/lib/private/legacy/OC_API.php +++ b/lib/private/legacy/OC_API.php @@ -12,7 +12,6 @@ * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Tom Needham <tom@owncloud.com> - * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * @@ -43,8 +42,9 @@ class OC_API { * respond to a call * @param \OC\OCS\Result $result * @param string $format the format xml|json + * @psalm-taint-escape html */ - public static function respond($result, $format='xml') { + public static function respond($result, $format = 'xml') { $request = \OC::$server->getRequest(); // Send 401 headers if unauthorised diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php index 5851fc01379..32d2569d0a0 100644 --- a/lib/private/legacy/OC_App.php +++ b/lib/private/legacy/OC_App.php @@ -33,7 +33,7 @@ declare(strict_types=1); * @author Sebastian Wessalowski <sebastian@wessalowski.org> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Thomas Tanghus <thomas@tanghus.net> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -80,6 +80,9 @@ class OC_App { /** * clean the appId * + * @psalm-taint-escape file + * @psalm-taint-escape include + * * @param string $app AppId that needs to be cleaned * @return string */ @@ -94,7 +97,7 @@ class OC_App { * @return bool */ public static function isAppLoaded(string $app): bool { - return in_array($app, self::$loadedApps, true); + return isset(self::$loadedApps[$app]); } /** @@ -118,16 +121,19 @@ class OC_App { // Add each apps' folder as allowed class path foreach ($apps as $app) { - $path = self::getAppPath($app); - if ($path !== false) { - self::registerAutoloading($app, $path); + // 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 (($types === [] or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) { + if (!isset(self::$loadedApps[$app]) && ($types === [] || self::isType($app, $types))) { self::loadApp($app); } } @@ -143,7 +149,7 @@ class OC_App { * @throws Exception */ public static function loadApp(string $app) { - self::$loadedApps[] = $app; + self::$loadedApps[$app] = true; $appPath = self::getAppPath($app); if ($appPath === false) { return; @@ -512,6 +518,8 @@ class OC_App { * Get the directory for the given app. * If the app is defined in multiple directories, the first one is taken. (false if not found) * + * @psalm-taint-specialize + * * @param string $appId * @return string|false * @deprecated 11.0.0 use \OC::$server->getAppManager()->getAppPath() @@ -966,6 +974,15 @@ class OC_App { \OC::$server->getAppManager()->clearAppsCache(); $appData = self::getAppInfo($appId); + $ignoreMaxApps = \OC::$server->getConfig()->getSystemValue('app_install_overwrite', []); + $ignoreMax = in_array($appId, $ignoreMaxApps, true); + \OC_App::checkAppDependencies( + \OC::$server->getConfig(), + \OC::$server->getL10N('core'), + $appData, + $ignoreMax + ); + self::registerAutoloading($appId, $appPath, true); self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']); @@ -982,11 +999,6 @@ class OC_App { \OC::$server->getAppManager()->clearAppsCache(); \OC::$server->getAppManager()->getAppVersion($appId, false); - // run upgrade code - if (file_exists($appPath . '/appinfo/update.php')) { - self::loadApp($appId); - include $appPath . '/appinfo/update.php'; - } self::setupBackgroundJobs($appData['background-jobs']); //set remote/public handlers @@ -1118,7 +1130,7 @@ class OC_App { $similarLangFallback = $option['@value']; } elseif (strpos($attributeLang, $similarLang . '_') === 0) { if ($similarLangFallback === false) { - $similarLangFallback = $option['@value']; + $similarLangFallback = $option['@value']; } } } else { diff --git a/lib/private/legacy/OC_DB.php b/lib/private/legacy/OC_DB.php index 1d2e9bd1429..11cd1f4cae0 100644 --- a/lib/private/legacy/OC_DB.php +++ b/lib/private/legacy/OC_DB.php @@ -12,7 +12,7 @@ * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -55,6 +55,7 @@ class OC_DB { * @param bool|null $isManipulation * @throws \OC\DatabaseException * @return OC_DB_StatementWrapper prepared SQL query + * @deprecated 21.0.0 Please use \OCP\IDBConnection::getQueryBuilder() instead * * SQL query via Doctrine prepare(), needs to be execute()'d! */ @@ -68,13 +69,12 @@ class OC_DB { // return the result try { - $result =$connection->prepare($query, $limit, $offset); + $result = $connection->prepare($query, $limit, $offset); } catch (\Doctrine\DBAL\DBALException $e) { throw new \OC\DatabaseException($e->getMessage()); } // differentiate between query and manipulation - $result = new OC_DB_StatementWrapper($result, $isManipulation); - return $result; + return new OC_DB_StatementWrapper($result, $isManipulation); } /** @@ -85,22 +85,27 @@ class OC_DB { * @return bool */ public static function isManipulation($sql) { + $sql = trim($sql); $selectOccurrence = stripos($sql, 'SELECT'); - if ($selectOccurrence !== false && $selectOccurrence < 10) { + if ($selectOccurrence === 0) { return false; } $insertOccurrence = stripos($sql, 'INSERT'); - if ($insertOccurrence !== false && $insertOccurrence < 10) { + if ($insertOccurrence === 0) { return true; } $updateOccurrence = stripos($sql, 'UPDATE'); - if ($updateOccurrence !== false && $updateOccurrence < 10) { + if ($updateOccurrence === 0) { return true; } $deleteOccurrence = stripos($sql, 'DELETE'); - if ($deleteOccurrence !== false && $deleteOccurrence < 10) { + if ($deleteOccurrence === 0) { return true; } + + // This is triggered with "SHOW VERSION" and some more, so until we made a list, we keep this out. + // \OC::$server->getLogger()->logException(new \Exception('Can not detect if query is manipulating: ' . $sql)); + return false; } @@ -112,6 +117,7 @@ class OC_DB { * @param array $parameters * @return OC_DB_StatementWrapper * @throws \OC\DatabaseException + * @deprecated 21.0.0 Please use \OCP\IDBConnection::getQueryBuilder() instead */ public static function executeAudited($stmt, array $parameters = []) { if (is_string($stmt)) { diff --git a/lib/private/legacy/OC_DB_StatementWrapper.php b/lib/private/legacy/OC_DB_StatementWrapper.php index d4072caf28e..667c2de9678 100644 --- a/lib/private/legacy/OC_DB_StatementWrapper.php +++ b/lib/private/legacy/OC_DB_StatementWrapper.php @@ -5,10 +5,10 @@ * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Bart Visscher <bartv@thisnet.nl> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> - * @author Piotr Mrówczyński <mrow4a@yahoo.com> * @author Robin Appelman <robin@icewind.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @@ -66,7 +66,7 @@ class OC_DB_StatementWrapper { * @param array $input * @return \OC_DB_StatementWrapper|int|bool */ - public function execute($input= []) { + public function execute($input = []) { $this->lastArguments = $input; if (count($input) > 0) { $result = $this->statement->execute($input); @@ -105,6 +105,15 @@ class OC_DB_StatementWrapper { } /** + * Closes the cursor, enabling the statement to be executed again. + * + * @deprecated Use Result::free() instead. + */ + public function closeCursor(): void { + $this->statement->closeCursor(); + } + + /** * Binds a PHP variable to a corresponding named or question mark placeholder in the * SQL statement that was use to prepare the statement. * diff --git a/lib/private/legacy/OC_FileChunking.php b/lib/private/legacy/OC_FileChunking.php index 8aef29541fe..1c24f0d8067 100644 --- a/lib/private/legacy/OC_FileChunking.php +++ b/lib/private/legacy/OC_FileChunking.php @@ -11,7 +11,7 @@ * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Thomas Tanghus <thomas@tanghus.net> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -88,7 +88,7 @@ class OC_FileChunking { $cache = $this->getCache(); $chunkcount = (int)$this->info['chunkcount']; - for ($i=($chunkcount-1); $i >= 0; $i--) { + for ($i = ($chunkcount - 1); $i >= 0; $i--) { if (!$cache->hasKey($prefix.$i)) { return false; } @@ -144,7 +144,7 @@ class OC_FileChunking { public function cleanup() { $cache = $this->getCache(); $prefix = $this->getPrefix(); - for ($i=0; $i < $this->info['chunkcount']; $i++) { + for ($i = 0; $i < $this->info['chunkcount']; $i++) { $cache->remove($prefix.$i); } } diff --git a/lib/private/legacy/OC_Files.php b/lib/private/legacy/OC_Files.php index ddb824cd6cd..addee2358dd 100644 --- a/lib/private/legacy/OC_Files.php +++ b/lib/private/legacy/OC_Files.php @@ -12,7 +12,6 @@ * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Julius Härtl <jus@bitgrid.net> * @author Ko- <k.stoffelen@cs.ru.nl> - * @author Lukas Reschke <lukas@statuscode.ch> * @author Michael Gapczynski <GapczynskiM@gmail.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Nicolai Ehemann <en@enlightened.de> @@ -23,7 +22,7 @@ * @author Thibaut GRIDEL <tgridel@free.fr> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Victor Dubiniuk <dubiniuk@owncloud.com> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -41,6 +40,7 @@ * */ +use bantu\IniGetWrapper\IniGetWrapper; use OC\Files\View; use OC\Streamer; use OCP\Lock\ILockingProvider; @@ -108,6 +108,7 @@ class OC_Files { * @param array $params ; 'head' boolean to only send header of the request ; 'range' http range header */ public static function get($dir, $files, $params = null) { + OC_Util::setupFS(); $view = \OC\Files\Filesystem::getView(); $getType = self::FILE; $filename = $dir; @@ -164,7 +165,7 @@ class OC_Files { OC_Util::obEnd(); $streamer->sendHeaders($name); - $executionTime = (int)OC::$server->getIniWrapper()->getNumeric('max_execution_time'); + $executionTime = (int)OC::$server->get(IniGetWrapper::class)->getNumeric('max_execution_time'); if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) { @set_time_limit(0); } @@ -208,18 +209,18 @@ class OC_Files { } catch (\OCP\Lock\LockedException $ex) { self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); OC::$server->getLogger()->logException($ex); - $l = \OC::$server->getL10N('core'); + $l = \OC::$server->getL10N('lib'); $hint = method_exists($ex, 'getHint') ? $ex->getHint() : ''; \OC_Template::printErrorPage($l->t('File is currently busy, please try again later'), $hint, 200); } catch (\OCP\Files\ForbiddenException $ex) { self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); OC::$server->getLogger()->logException($ex); - $l = \OC::$server->getL10N('core'); + $l = \OC::$server->getL10N('lib'); \OC_Template::printErrorPage($l->t('Can\'t read file'), $ex->getMessage(), 200); } catch (\Exception $ex) { self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); OC::$server->getLogger()->logException($ex); - $l = \OC::$server->getL10N('core'); + $l = \OC::$server->getL10N('lib'); $hint = method_exists($ex, 'getHint') ? $ex->getHint() : ''; \OC_Template::printErrorPage($l->t('Can\'t read file'), $hint, 200); } @@ -231,7 +232,7 @@ class OC_Files { * @return array $rangeArray ('from'=>int,'to'=>int), ... */ private static function parseHttpRangeHeader($rangeHeaderPos, $fileSize) { - $rArray=explode(',', $rangeHeaderPos); + $rArray = explode(',', $rangeHeaderPos); $minOffset = 0; $ind = 0; @@ -243,7 +244,7 @@ class OC_Files { if ($ranges[0] < $minOffset) { // case: bytes=500-700,601-999 $ranges[0] = $minOffset; } - if ($ind > 0 && $rangeArray[$ind-1]['to']+1 == $ranges[0]) { // case: bytes=500-600,601-999 + if ($ind > 0 && $rangeArray[$ind - 1]['to'] + 1 == $ranges[0]) { // case: bytes=500-600,601-999 $ind--; $ranges[0] = $rangeArray[$ind]['from']; } @@ -252,7 +253,7 @@ class OC_Files { if (is_numeric($ranges[0]) && is_numeric($ranges[1]) && $ranges[0] < $fileSize && $ranges[0] <= $ranges[1]) { // case: x-x if ($ranges[1] >= $fileSize) { - $ranges[1] = $fileSize-1; + $ranges[1] = $fileSize - 1; } $rangeArray[$ind++] = [ 'from' => $ranges[0], 'to' => $ranges[1], 'size' => $fileSize ]; $minOffset = $ranges[1] + 1; @@ -261,14 +262,14 @@ class OC_Files { } } elseif (is_numeric($ranges[0]) && $ranges[0] < $fileSize) { // case: x- - $rangeArray[$ind++] = [ 'from' => $ranges[0], 'to' => $fileSize-1, 'size' => $fileSize ]; + $rangeArray[$ind++] = [ 'from' => $ranges[0], 'to' => $fileSize - 1, 'size' => $fileSize ]; break; } elseif (is_numeric($ranges[1])) { // case: -x if ($ranges[1] > $fileSize) { $ranges[1] = $fileSize; } - $rangeArray[$ind++] = [ 'from' => $fileSize-$ranges[1], 'to' => $fileSize-1, 'size' => $fileSize ]; + $rangeArray[$ind++] = [ 'from' => $fileSize - $ranges[1], 'to' => $fileSize - 1, 'size' => $fileSize ]; break; } } diff --git a/lib/private/legacy/OC_Helper.php b/lib/private/legacy/OC_Helper.php index 8cd492de117..ccb99a6ebb8 100644 --- a/lib/private/legacy/OC_Helper.php +++ b/lib/private/legacy/OC_Helper.php @@ -7,7 +7,6 @@ * @author Bart Visscher <bartv@thisnet.nl> * @author Björn Schießle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Clark Tomlinson <fallen013@gmail.com> * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Felix Moeller <mail@felixmoeller.de> * @author Jakob Sack <mail@jakobsack.de> @@ -26,7 +25,7 @@ * @author Simon Könnecke <simonkoennecke@gmail.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Thomas Tanghus <thomas@tanghus.net> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -44,6 +43,7 @@ * */ +use bantu\IniGetWrapper\IniGetWrapper; use Symfony\Component\Process\ExecutableFinder; /** @@ -220,7 +220,7 @@ class OC_Helper { // Default check will be done with $path directories : $dirs = explode(PATH_SEPARATOR, $path); // WARNING : We have to check if open_basedir is enabled : - $obd = OC::$server->getIniWrapper()->getString('open_basedir'); + $obd = OC::$server->get(IniGetWrapper::class)->getString('open_basedir'); if ($obd != "none") { $obd_values = explode(PATH_SEPARATOR, $obd); if (count($obd_values) > 0 and $obd_values[0]) { @@ -414,7 +414,7 @@ class OC_Helper { * @return int PHP upload file size limit */ public static function uploadLimit() { - $ini = \OC::$server->getIniWrapper(); + $ini = \OC::$server->get(IniGetWrapper::class); $upload_max_filesize = OCP\Util::computerFileSize($ini->get('upload_max_filesize')); $post_max_size = OCP\Util::computerFileSize($ini->get('post_max_size')); if ((int)$upload_max_filesize === 0 and (int)$post_max_size === 0) { @@ -436,7 +436,7 @@ class OC_Helper { if (!function_exists($function_name)) { return false; } - $ini = \OC::$server->getIniWrapper(); + $ini = \OC::$server->get(IniGetWrapper::class); $disabled = explode(',', $ini->get('disable_functions') ?: ''); $disabled = array_map('trim', $disabled); if (in_array($function_name, $disabled)) { @@ -475,6 +475,9 @@ class OC_Helper { /** * Calculate the disc space for the given path * + * BEWARE: this requires that Util::setupFS() was called + * already ! + * * @param string $path * @param \OCP\Files\FileInfo $rootInfo (optional) * @return array @@ -495,7 +498,8 @@ class OC_Helper { $used = 0; } $quota = \OCP\Files\FileInfo::SPACE_UNLIMITED; - $storage = $rootInfo->getStorage(); + $mount = $rootInfo->getMountPoint(); + $storage = $mount->getStorage(); $sourceStorage = $storage; if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) { $includeExtStorage = false; @@ -544,6 +548,11 @@ class OC_Helper { if ($owner) { $ownerDisplayName = $owner->getDisplayName(); } + if (substr_count($mount->getMountPoint(), '/') < 3) { + $mountPoint = ''; + } else { + [,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4); + } return [ 'free' => $free, @@ -553,6 +562,8 @@ class OC_Helper { 'relative' => $relative, 'owner' => $ownerId, 'ownerDisplayName' => $ownerDisplayName, + 'mountType' => $mount->getMountType(), + 'mountPoint' => trim($mountPoint, '/'), ]; } diff --git a/lib/private/legacy/OC_Hook.php b/lib/private/legacy/OC_Hook.php index 1da03df3a1f..cf0e9881e4c 100644 --- a/lib/private/legacy/OC_Hook.php +++ b/lib/private/legacy/OC_Hook.php @@ -11,7 +11,7 @@ * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Sam Tuke <mail@samtuke.com> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -128,15 +128,15 @@ class OC_Hook { * @param string $signalClass * @param string $signalName */ - public static function clear($signalClass='', $signalName='') { + public static function clear($signalClass = '', $signalName = '') { if ($signalClass) { if ($signalName) { - self::$registered[$signalClass][$signalName]=[]; + self::$registered[$signalClass][$signalName] = []; } else { - self::$registered[$signalClass]=[]; + self::$registered[$signalClass] = []; } } else { - self::$registered=[]; + self::$registered = []; } } diff --git a/lib/private/legacy/OC_Image.php b/lib/private/legacy/OC_Image.php index 4fdbfb3909f..523468701c7 100644 --- a/lib/private/legacy/OC_Image.php +++ b/lib/private/legacy/OC_Image.php @@ -98,7 +98,14 @@ class OC_Image implements \OCP\IImage { * @return bool */ public function valid() { // apparently you can't name a method 'empty'... - return is_resource($this->resource); + if (is_resource($this->resource)) { + return true; + } + if (is_object($this->resource) && get_class($this->resource) === 'GdImage') { + return true; + } + + return false; } /** @@ -305,7 +312,13 @@ class OC_Image implements \OCP\IImage { * @throws \InvalidArgumentException in case the supplied resource does not have the type "gd" */ public function setResource($resource) { - if (get_resource_type($resource) === 'gd') { + // For PHP<8 + if (is_resource($resource) && get_resource_type($resource) === 'gd') { + $this->resource = $resource; + return; + } + // PHP 8 has real objects for GD stuff + if (is_object($resource) && get_class($resource) === 'GdImage') { $this->resource = $resource; return; } @@ -1175,7 +1188,7 @@ if (!function_exists('imagebmp')) { } elseif ($bit == 32) { $bit = 24; } - $bits = pow(2, $bit); + $bits = (int)pow(2, $bit); imagetruecolortopalette($im, true, $bits); $width = imagesx($im); $height = imagesy($im); @@ -1211,7 +1224,7 @@ if (!function_exists('imagebmp')) { } // RLE8 elseif ($compression == 1 && $bit == 8) { for ($j = $height - 1; $j >= 0; $j--) { - $lastIndex = "\0"; + $lastIndex = null; $sameNum = 0; for ($i = 0; $i <= $width; $i++) { $index = imagecolorat($im, $i, $j); diff --git a/lib/private/legacy/OC_JSON.php b/lib/private/legacy/OC_JSON.php index a0b9868a023..3094879af38 100644 --- a/lib/private/legacy/OC_JSON.php +++ b/lib/private/legacy/OC_JSON.php @@ -10,7 +10,7 @@ * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Thomas Tanghus <thomas@tanghus.net> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -99,6 +99,7 @@ class OC_JSON { * Send json error msg * @deprecated Use a AppFramework JSONResponse instead * @suppress PhanDeprecatedFunction + * @psalm-taint-escape html */ public static function error($data = []) { $data['status'] = 'error'; @@ -110,6 +111,7 @@ class OC_JSON { * Send json success msg * @deprecated Use a AppFramework JSONResponse instead * @suppress PhanDeprecatedFunction + * @psalm-taint-escape html */ public static function success($data = []) { $data['status'] = 'success'; diff --git a/lib/private/legacy/OC_Response.php b/lib/private/legacy/OC_Response.php index 491c6913463..f4c2356aecd 100644 --- a/lib/private/legacy/OC_Response.php +++ b/lib/private/legacy/OC_Response.php @@ -10,7 +10,7 @@ * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/legacy/OC_Template.php b/lib/private/legacy/OC_Template.php index 32d185d25fd..baf2356b549 100644 --- a/lib/private/legacy/OC_Template.php +++ b/lib/private/legacy/OC_Template.php @@ -20,7 +20,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -114,10 +114,7 @@ class OC_Template extends \OC\Template\Base { OC_Util::addStyle('server', null, true); OC_Util::addTranslations('core', null, true); - if (\OC::$server->getSystemConfig()->getValue('installed', false)) { - OC_Util::addStyle('search', 'results'); - OC_Util::addScript('search', 'search', true); - OC_Util::addScript('search', 'searchprovider'); + if (\OC::$server->getSystemConfig()->getValue('installed', false) && !\OCP\Util::needUpgrade()) { OC_Util::addScript('merged-template-prepend', null, true); OC_Util::addScript('dist/files_client', null, true); OC_Util::addScript('dist/files_fileinfo', null, true); @@ -164,8 +161,8 @@ class OC_Template extends \OC\Template\Base { * @param string $text the text content for the element. If $text is null then the * element will be written as empty element. So use "" to get a closing tag. */ - public function addHeader($tag, $attributes, $text=null) { - $this->headers[]= [ + public function addHeader($tag, $attributes, $text = null) { + $this->headers[] = [ 'tag' => $tag, 'attributes' => $attributes, 'text' => $text @@ -174,7 +171,7 @@ class OC_Template extends \OC\Template\Base { /** * Process the template - * @return boolean|string + * @return string * * This function process the template. If $this->renderAs is set, it * will produce a full page. @@ -198,7 +195,7 @@ class OC_Template extends \OC\Template\Base { if (strcasecmp($header['tag'], 'script') === 0 && in_array('src', array_map('strtolower', array_keys($header['attributes'])))) { $headers .= ' defer'; } - foreach ($header['attributes'] as $name=>$value) { + foreach ($header['attributes'] as $name => $value) { $headers .= ' '.\OCP\Util::sanitizeHTML($name).'="'.\OCP\Util::sanitizeHTML($value).'"'; } if ($header['text'] !== null) { @@ -328,7 +325,7 @@ class OC_Template extends \OC\Template\Base { $content->assign('errorCode', $exception->getCode()); $content->assign('file', $exception->getFile()); $content->assign('line', $exception->getLine()); - $content->assign('trace', $exception->getTraceAsString()); + $content->assign('exception', $exception); $content->assign('debugMode', \OC::$server->getSystemConfig()->getValue('debug', false)); $content->assign('remoteAddr', $request->getRemoteAddress()); $content->assign('requestID', $request->getId()); diff --git a/lib/private/legacy/OC_User.php b/lib/private/legacy/OC_User.php index 29c78da6efb..c8d9b51eaba 100644 --- a/lib/private/legacy/OC_User.php +++ b/lib/private/legacy/OC_User.php @@ -18,7 +18,7 @@ * @author Roeland Jago Douma <roeland@famdouma.nl> * @author shkdee <louis.traynard@m4x.org> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * diff --git a/lib/private/legacy/OC_Util.php b/lib/private/legacy/OC_Util.php index 82b7abf6c8f..826d0a31129 100644 --- a/lib/private/legacy/OC_Util.php +++ b/lib/private/legacy/OC_Util.php @@ -25,6 +25,7 @@ * @author Joas Schilling <coding@schilljs.com> * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Julius Härtl <jus@bitgrid.net> * @author Kawohl <john@owncloud.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Markus Goetz <markus@woboq.com> @@ -43,7 +44,7 @@ * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Thomas Tanghus <thomas@tanghus.net> * @author Victor Dubiniuk <dubiniuk@owncloud.com> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * @author Volkan Gezer <volkangezer@gmail.com> * * @license AGPL-3.0 @@ -62,12 +63,14 @@ * */ +use bantu\IniGetWrapper\IniGetWrapper; use OC\AppFramework\Http\Request; use OC\Files\Storage\LocalRootStorage; use OCP\IConfig; use OCP\IGroupManager; use OCP\ILogger; use OCP\IUser; +use OCP\IUserSession; class OC_Util { public static $scripts = []; @@ -296,6 +299,16 @@ class OC_Util { self::initLocalStorageRootFS(); } + /** @var \OCP\Files\Config\IMountProviderCollection $mountProviderCollection */ + $mountProviderCollection = \OC::$server->query(\OCP\Files\Config\IMountProviderCollection::class); + $rootMountProviders = $mountProviderCollection->getRootMounts(); + + /** @var \OC\Files\Mount\Manager $mountManager */ + $mountManager = \OC\Files\Filesystem::getMountManager(); + foreach ($rootMountProviders as $rootMountProvider) { + $mountManager->addMount($rootMountProvider); + } + if ($user != '' && !\OC::$server->getUserManager()->userExists($user)) { \OC::$server->getEventLogger()->end('setup_fs'); return false; @@ -543,16 +556,16 @@ class OC_Util { $timestamp = filemtime(OC::$SERVERROOT . '/version.php'); require OC::$SERVERROOT . '/version.php'; - /** @var $timestamp int */ + /** @var int $timestamp */ self::$versionCache['OC_Version_Timestamp'] = $timestamp; - /** @var $OC_Version string */ + /** @var string $OC_Version */ self::$versionCache['OC_Version'] = $OC_Version; - /** @var $OC_VersionString string */ + /** @var string $OC_VersionString */ self::$versionCache['OC_VersionString'] = $OC_VersionString; - /** @var $OC_Build string */ + /** @var string $OC_Build */ self::$versionCache['OC_Build'] = $OC_Build; - /** @var $OC_Channel string */ + /** @var string $OC_Channel */ self::$versionCache['OC_Channel'] = $OC_Channel; } @@ -727,7 +740,7 @@ class OC_Util { $webServerRestart = false; $setup = new \OC\Setup( $config, - \OC::$server->getIniWrapper(), + \OC::$server->get(IniGetWrapper::class), \OC::$server->getL10N('lib'), \OC::$server->query(\OCP\Defaults::class), \OC::$server->getLogger(), @@ -852,7 +865,7 @@ class OC_Util { $missingDependencies = []; $invalidIniSettings = []; - $iniWrapper = \OC::$server->getIniWrapper(); + $iniWrapper = \OC::$server->get(IniGetWrapper::class); foreach ($dependencies['classes'] as $class => $module) { if (!class_exists($class)) { $missingDependencies[] = $module; @@ -899,7 +912,7 @@ class OC_Util { } $errors[] = [ 'error' => $l->t('PHP setting "%s" is not set to "%s".', [$setting[0], var_export($setting[1], true)]), - 'hint' => $l->t('Adjusting this setting in php.ini will make Nextcloud run again') + 'hint' => $l->t('Adjusting this setting in php.ini will make Nextcloud run again') ]; $webServerRestart = true; } @@ -923,9 +936,9 @@ class OC_Util { if (function_exists('xml_parser_create') && LIBXML_LOADED_VERSION < 20700) { $version = LIBXML_LOADED_VERSION; - $major = floor($version/10000); + $major = floor($version / 10000); $version -= ($major * 10000); - $minor = floor($version/100); + $minor = floor($version / 100); $version -= ($minor * 100); $patch = $version; $errors[] = [ @@ -970,6 +983,7 @@ class OC_Util { try { $result = \OC_DB::executeAudited('SHOW SERVER_VERSION'); $data = $result->fetchRow(); + $result->closeCursor(); if (isset($data['server_version'])) { $version = $data['server_version']; if (version_compare($version, '9.0.0', '<')) { @@ -1088,6 +1102,8 @@ class OC_Util { * @suppress PhanDeprecatedFunction */ public static function getDefaultPageUrl() { + /** @var IConfig $config */ + $config = \OC::$server->get(IConfig::class); $urlGenerator = \OC::$server->getURLGenerator(); // Deny the redirect if the URL contains a @ // This prevents unvalidated redirects like ?redirect_url=:user@domain.com @@ -1099,8 +1115,16 @@ class OC_Util { $location = $urlGenerator->getAbsoluteURL($defaultPage); } else { $appId = 'files'; - $config = \OC::$server->getConfig(); - $defaultApps = explode(',', $config->getSystemValue('defaultapp', 'files')); + $defaultApps = explode(',', $config->getSystemValue('defaultapp', 'dashboard,files')); + + /** @var IUserSession $userSession */ + $userSession = \OC::$server->get(IUserSession::class); + $user = $userSession->getUser(); + if ($user) { + $userDefaultApps = explode(',', $config->getUserValue($user->getUID(), 'core', 'defaultapp')); + $defaultApps = array_filter(array_merge($userDefaultApps, $defaultApps)); + } + // find the first app that is enabled for the current user foreach ($defaultApps as $defaultApp) { $defaultApp = OC_App::cleanAppId(strip_tags($defaultApp)); diff --git a/lib/private/legacy/template/functions.php b/lib/private/legacy/template/functions.php index e2a1c476433..4613e36f9f2 100644 --- a/lib/private/legacy/template/functions.php +++ b/lib/private/legacy/template/functions.php @@ -14,7 +14,7 @@ * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> + * @author Vincent Petry <vincent@nextcloud.com> * * @license AGPL-3.0 * @@ -47,12 +47,12 @@ function p($string) { * @param string $opts, additional optional options */ function emit_css_tag($href, $opts = '') { - $s='<link rel="stylesheet"'; + $s = '<link rel="stylesheet"'; if (!empty($href)) { - $s.=' href="' . $href .'"'; + $s .= ' href="' . $href .'"'; } if (!empty($opts)) { - $s.=' '.$opts; + $s .= ' '.$opts; } print_unescaped($s.">\n"); } @@ -75,20 +75,20 @@ function emit_css_loading_tags($obj) { * @param string $src the source URL, ignored when empty * @param string $script_content the inline script content, ignored when empty */ -function emit_script_tag($src, $script_content='') { - $defer_str=' defer'; - $s='<script nonce="' . \OC::$server->getContentSecurityPolicyNonceManager()->getNonce() . '"'; +function emit_script_tag($src, $script_content = '') { + $defer_str = ' defer'; + $s = '<script nonce="' . \OC::$server->getContentSecurityPolicyNonceManager()->getNonce() . '"'; if (!empty($src)) { // emit script tag for deferred loading from $src - $s.=$defer_str.' src="' . $src .'">'; + $s .= $defer_str.' src="' . $src .'">'; } elseif (!empty($script_content)) { // emit script tag for inline script from $script_content without defer (see MDN) - $s.=">\n".$script_content."\n"; + $s .= ">\n".$script_content."\n"; } else { // no $src nor $src_content, really useless empty tag - $s.='>'; + $s .= '>'; } - $s.='</script>'; + $s .= '</script>'; print_unescaped($s."\n"); } @@ -306,9 +306,9 @@ function relative_modified_date($timestamp, $fromTime = null, $dateOnly = false) return $formatter->formatTimeSpan($timestamp, $fromTime); } -function html_select_options($options, $selected, $params=[]) { +function html_select_options($options, $selected, $params = []) { if (!is_array($selected)) { - $selected=[$selected]; + $selected = [$selected]; } if (isset($params['combine']) && $params['combine']) { $options = array_combine($options, $options); |