diff options
Diffstat (limited to 'lib/private')
46 files changed, 1177 insertions, 70 deletions
diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php index d18555d296c..05feaf87b8f 100644 --- a/lib/private/Accounts/AccountManager.php +++ b/lib/private/Accounts/AccountManager.php @@ -30,10 +30,16 @@ 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\IUser; use Psr\Log\LoggerInterface; @@ -55,9 +61,15 @@ 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; @@ -68,24 +80,70 @@ class AccountManager implements IAccountManager { private $logger; public function __construct(IDBConnection $connection, + IConfig $config, EventDispatcherInterface $eventDispatcher, IJobList $jobList, 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) { @@ -103,6 +161,8 @@ class AccountManager implements IAccountManager { new GenericEvent($user, $data) ); } + + return $data; } /** @@ -116,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(); } /** @@ -153,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 * @@ -173,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; @@ -256,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(); @@ -268,6 +361,9 @@ class AccountManager implements IAccountManager { ] ) ->execute(); + + $this->deleteUserData($user); + $this->writeUserData($user, $data); } /** @@ -276,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(); @@ -284,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 82f55f5a9d1..c5e7c34deaf 100644 --- a/lib/private/Accounts/Hooks.php +++ b/lib/private/Accounts/Hooks.php @@ -24,6 +24,7 @@ namespace OC\Accounts; +use OCP\Accounts\IAccountManager; use OCP\IUser; use Psr\Log\LoggerInterface; @@ -61,14 +62,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; diff --git a/lib/private/App/AppStore/Fetcher/AppFetcher.php b/lib/private/App/AppStore/Fetcher/AppFetcher.php index 21ec38ead30..4dc517879e8 100644 --- a/lib/private/App/AppStore/Fetcher/AppFetcher.php +++ b/lib/private/App/AppStore/Fetcher/AppFetcher.php @@ -101,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/AppConfig.php b/lib/private/AppConfig.php index 9e36ad0cd57..a671848245e 100644 --- a/lib/private/AppConfig.php +++ b/lib/private/AppConfig.php @@ -348,10 +348,10 @@ 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(); 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/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/UserPlugin.php b/lib/private/Collaboration/Collaborators/UserPlugin.php index cd4963197ae..0b51a29ae25 100644 --- a/lib/private/Collaboration/Collaborators/UserPlugin.php +++ b/lib/private/Collaboration/Collaborators/UserPlugin.php @@ -162,6 +162,7 @@ class UserPlugin implements ISearchPlugin { 'shareType' => IShare::TYPE_USER, 'shareWith' => $uid, ], + 'shareWithDisplayNameUnique' => !empty($userEmail) ? $userEmail : $uid, 'status' => $status, ]; } else { @@ -186,6 +187,7 @@ class UserPlugin implements ISearchPlugin { 'shareType' => IShare::TYPE_USER, 'shareWith' => $uid, ], + 'shareWithDisplayNameUnique' => !empty($userEmail) ? $userEmail : $uid, 'status' => $status, ]; } @@ -207,6 +209,8 @@ 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 = [ @@ -227,6 +231,7 @@ class UserPlugin implements ISearchPlugin { '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..1f8470005e4 100644 --- a/lib/private/Command/ClosureJob.php +++ b/lib/private/Command/ClosureJob.php @@ -23,12 +23,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..6b1e79f93de 100644 --- a/lib/private/Command/CommandJob.php +++ b/lib/private/Command/CommandJob.php @@ -30,7 +30,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..32f19c913e8 100644 --- a/lib/private/Command/CronBus.php +++ b/lib/private/Command/CronBus.php @@ -26,7 +26,6 @@ namespace OC\Command; use OCP\Command\ICommand; -use SuperClosure\Serializer; class CronBus extends AsyncBus { /** @@ -68,10 +67,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/DB/MigrationService.php b/lib/private/DB/MigrationService.php index 9c94cbc61fa..c1a6b82dccd 100644 --- a/lib/private/DB/MigrationService.php +++ b/lib/private/DB/MigrationService.php @@ -27,6 +27,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,7 +125,7 @@ class MigrationService { return false; } - if ($this->connection->tableExists('migrations')) { + if ($this->connection->tableExists('migrations') && \OC::$server->getConfig()->getAppValue('core', 'vendor', '') !== 'owncloud') { $this->migrationTableCreated = true; return false; } @@ -421,7 +422,13 @@ class MigrationService { // read known migrations $toBeExecuted = $this->getMigrationsToExecute($to); foreach ($toBeExecuted as $version) { - $this->executeStep($version, $schemaOnly); + 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); + } } } diff --git a/lib/private/DB/QueryBuilder/QueryBuilder.php b/lib/private/DB/QueryBuilder/QueryBuilder.php index c2686f7b4a0..a5ea08127c7 100644 --- a/lib/private/DB/QueryBuilder/QueryBuilder.php +++ b/lib/private/DB/QueryBuilder/QueryBuilder.php @@ -31,6 +31,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; @@ -193,23 +194,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', ]); } diff --git a/lib/private/Federation/CloudIdManager.php b/lib/private/Federation/CloudIdManager.php index d99fc350520..02e3c7cd513 100644 --- a/lib/private/Federation/CloudIdManager.php +++ b/lib/private/Federation/CloudIdManager.php @@ -86,7 +86,13 @@ class CloudIdManager implements ICloudIdManager { if (isset($entry['CLOUD'])) { foreach ($entry['CLOUD'] as $cloudID) { if ($cloudID === $cloudId) { - return $entry['FN']; + // 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; + } } } } 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 1d3b4da3bce..be44d461933 100644 --- a/lib/private/Files/Cache/Cache.php +++ b/lib/private/Files/Cache/Cache.php @@ -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); } @@ -284,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(); @@ -308,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) { @@ -399,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); } } @@ -536,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())); } } @@ -677,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); } diff --git a/lib/private/Files/Cache/CacheEntry.php b/lib/private/Files/Cache/CacheEntry.php index a0ba9020909..5f31ecfbec3 100644 --- a/lib/private/Files/Cache/CacheEntry.php +++ b/lib/private/Files/Cache/CacheEntry.php @@ -67,7 +67,7 @@ class CacheEntry implements ICacheEntry { public function getPath() { - return $this->data['path']; + return (string)$this->data['path']; } diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php index 591ee96c99b..f0a81f48930 100644 --- a/lib/private/Files/Storage/Local.php +++ b/lib/private/Files/Storage/Local.php @@ -146,8 +146,8 @@ class Local extends \OC\Files\Storage\Common { public function stat($path) { $fullPath = $this->getSourcePath($path); clearstatcache(true, $fullPath); - $statResult = stat($fullPath); - if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) { + $statResult = @stat($fullPath); + if (PHP_INT_SIZE === 4 && $statResult && !$this->is_dir($path)) { $filesize = $this->filesize($path); $statResult['size'] = $filesize; $statResult[7] = $filesize; @@ -159,9 +159,7 @@ class Local extends \OC\Files\Storage\Common { * @inheritdoc */ public function getMetaData($path) { - $fullPath = $this->getSourcePath($path); - clearstatcache(true, $fullPath); - $stat = @stat($fullPath); + $stat = $this->stat($path); if (!$stat) { return null; } @@ -180,6 +178,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; diff --git a/lib/private/Lock/MemcacheLockingProvider.php b/lib/private/Lock/MemcacheLockingProvider.php index 6b01f0aafc6..439894e901f 100644 --- a/lib/private/Lock/MemcacheLockingProvider.php +++ b/lib/private/Lock/MemcacheLockingProvider.php @@ -61,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/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/ExceptionSerializer.php b/lib/private/Log/ExceptionSerializer.php index a2bc1963003..339a6604ed1 100644 --- a/lib/private/Log/ExceptionSerializer.php +++ b/lib/private/Log/ExceptionSerializer.php @@ -86,6 +86,9 @@ class ExceptionSerializer { // files_external: UserStoragesController 'update', + + // Preview providers, don't log big data strings + 'imagecreatefromstring', ]; public const methodsWithSensitiveParametersByClass = [ diff --git a/lib/private/Mail/EMailTemplate.php b/lib/private/Mail/EMailTemplate.php index 37e02ce4504..a3f266bf328 100644 --- a/lib/private/Mail/EMailTemplate.php +++ b/lib/private/Mail/EMailTemplate.php @@ -293,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> diff --git a/lib/private/Repair.php b/lib/private/Repair.php index ec748355567..4793485a384 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -34,6 +34,7 @@ namespace OC; +use OC\App\AppStore\Bundles\BundleFetcher; use OC\Avatar\AvatarManager; use OC\Repair\AddBruteForceCleanupJob; use OC\Repair\AddCleanupUpdaterBackupsJob; @@ -42,7 +43,11 @@ 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; @@ -53,6 +58,7 @@ 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; @@ -150,7 +156,22 @@ class Repair implements IOutput { 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)), @@ -177,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), ]; } diff --git a/lib/private/Repair/NC20/EncryptionLegacyCipher.php b/lib/private/Repair/NC20/EncryptionLegacyCipher.php index 0be34679fcb..f887feefc98 100644 --- a/lib/private/Repair/NC20/EncryptionLegacyCipher.php +++ b/lib/private/Repair/NC20/EncryptionLegacyCipher.php @@ -58,7 +58,8 @@ class EncryptionLegacyCipher implements IRepairStep { return; } - if ($this->manager->isEnabled()) { + $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 index 1eba09f413a..d5879073d59 100644 --- a/lib/private/Repair/NC20/EncryptionMigration.php +++ b/lib/private/Repair/NC20/EncryptionMigration.php @@ -58,7 +58,8 @@ class EncryptionMigration implements IRepairStep { return; } - if ($this->manager->isEnabled()) { + $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/NC21/ValidatePhoneNumber.php b/lib/private/Repair/NC21/ValidatePhoneNumber.php new file mode 100644 index 00000000000..6e25ff26b7e --- /dev/null +++ b/lib/private/Repair/NC21/ValidatePhoneNumber.php @@ -0,0 +1,89 @@ +<?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/Owncloud/CleanPreviews.php b/lib/private/Repair/Owncloud/CleanPreviews.php new file mode 100644 index 00000000000..5c183451d67 --- /dev/null +++ b/lib/private/Repair/Owncloud/CleanPreviews.php @@ -0,0 +1,73 @@ +<?php +/** + * @copyright 2016 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\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..e8d89c9c7a0 --- /dev/null +++ b/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php @@ -0,0 +1,132 @@ +<?php +/** + * @copyright 2016 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\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..6d07ec9b962 --- /dev/null +++ b/lib/private/Repair/Owncloud/InstallCoreBundle.php @@ -0,0 +1,80 @@ +<?php +/** + * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> + * + * @author Lukas Reschke <lukas@statuscode.ch> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +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..53f3097aeec --- /dev/null +++ b/lib/private/Repair/Owncloud/MoveAvatars.php @@ -0,0 +1,73 @@ +<?php +/** + * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Morris Jobke <hey@morrisjobke.de> + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +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..ec076d116c5 --- /dev/null +++ b/lib/private/Repair/Owncloud/MoveAvatarsBackgroundJob.php @@ -0,0 +1,116 @@ +<?php +/** + * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Joas Schilling <coding@schilljs.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +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..89c331a7d71 100644 --- a/lib/private/Repair/Owncloud/SaveAccountsTableData.php +++ b/lib/private/Repair/Owncloud/SaveAccountsTableData.php @@ -78,6 +78,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..b7da0b00528 --- /dev/null +++ b/lib/private/Repair/Owncloud/UpdateLanguageCodes.php @@ -0,0 +1,90 @@ +<?php +/** + * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de> + * + * @author Joas Schilling <coding@schilljs.com> + * @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\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/Security/Bruteforce/Capabilities.php b/lib/private/Security/Bruteforce/Capabilities.php index eab55db1c90..fcf05efbd7e 100644 --- a/lib/private/Security/Bruteforce/Capabilities.php +++ b/lib/private/Security/Bruteforce/Capabilities.php @@ -46,6 +46,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/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/Share/Constants.php b/lib/private/Share/Constants.php index 2310859c5be..77ed3176277 100644 --- a/lib/private/Share/Constants.php +++ b/lib/private/Share/Constants.php @@ -70,6 +70,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/Share20/Manager.php b/lib/private/Share20/Manager.php index e9895edf95a..6b782237b8c 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -248,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'); @@ -1887,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 2d4c4e6d40a..9f93df46ac0 100644 --- a/lib/private/Share20/ProviderFactory.php +++ b/lib/private/Share20/ProviderFactory.php @@ -44,6 +44,7 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\IServerContainer; use OCP\Share\IProviderFactory; use OCP\Share\IShare; +use OCP\Share\IShareProvider; /** * Class ProviderFactory @@ -67,6 +68,10 @@ class ProviderFactory implements IProviderFactory { /** @var \OCA\Talk\Share\RoomShareProvider */ private $roomShareProvider = null; + private $registeredShareProviders = []; + + private $shareProviders = []; + /** * IProviderFactory constructor. * @@ -76,6 +81,10 @@ class ProviderFactory implements IProviderFactory { $this->serverContainer = $serverContainer; } + public function registerProvider(string $shareProviderClass): void { + $this->registeredShareProviders[] = $shareProviderClass; + } + /** * Create the default share provider. * @@ -257,6 +266,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') { @@ -269,6 +282,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.'); } @@ -295,6 +318,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'); } @@ -320,6 +345,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/Support/Subscription/Registry.php b/lib/private/Support/Subscription/Registry.php index 3d6a9b09d84..72bae2adc8e 100644 --- a/lib/private/Support/Subscription/Registry.php +++ b/lib/private/Support/Subscription/Registry.php @@ -28,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 { @@ -49,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 { @@ -127,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/Updater.php b/lib/private/Updater.php index 437ba38362c..998fb4f2211 100644 --- a/lib/private/Updater.php +++ b/lib/private/Updater.php @@ -192,8 +192,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') { @@ -360,7 +364,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/Manager.php b/lib/private/User/Manager.php index 1201a456ce2..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,6 +43,7 @@ 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\BeforeUserCreatedEvent; use OCP\User\Events\UserCreatedEvent; @@ -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) { diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 37d518b6123..c2294cb1612 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -832,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/legacy/OC_API.php b/lib/private/legacy/OC_API.php index 5e4a46ab4d7..cba60826196 100644 --- a/lib/private/legacy/OC_API.php +++ b/lib/private/legacy/OC_API.php @@ -43,6 +43,7 @@ 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') { $request = \OC::$server->getRequest(); diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php index 0b9ae8c8c53..fdbbb1be907 100644 --- a/lib/private/legacy/OC_App.php +++ b/lib/private/legacy/OC_App.php @@ -974,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']); diff --git a/lib/private/legacy/OC_Files.php b/lib/private/legacy/OC_Files.php index 8b3ce2d0291..90aff098cd6 100644 --- a/lib/private/legacy/OC_Files.php +++ b/lib/private/legacy/OC_Files.php @@ -109,6 +109,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; diff --git a/lib/private/legacy/OC_Image.php b/lib/private/legacy/OC_Image.php index 3e9812c99f2..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; } diff --git a/lib/private/legacy/OC_JSON.php b/lib/private/legacy/OC_JSON.php index a0b9868a023..1597955135e 100644 --- a/lib/private/legacy/OC_JSON.php +++ b/lib/private/legacy/OC_JSON.php @@ -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'; |