summaryrefslogtreecommitdiffstats
path: root/lib/private
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private')
-rw-r--r--lib/private/Accounts/AccountManager.php130
-rw-r--r--lib/private/Accounts/Hooks.php9
-rw-r--r--lib/private/App/AppStore/Fetcher/AppFetcher.php32
-rw-r--r--lib/private/AppConfig.php4
-rw-r--r--lib/private/Collaboration/Collaborators/MailPlugin.php3
-rw-r--r--lib/private/Collaboration/Collaborators/RemotePlugin.php3
-rw-r--r--lib/private/Collaboration/Collaborators/UserPlugin.php5
-rw-r--r--lib/private/Command/ClosureJob.php4
-rw-r--r--lib/private/Command/CommandJob.php2
-rw-r--r--lib/private/Command/CronBus.php6
-rw-r--r--lib/private/DB/MigrationService.php11
-rw-r--r--lib/private/DB/QueryBuilder/QueryBuilder.php55
-rw-r--r--lib/private/Federation/CloudIdManager.php8
-rw-r--r--lib/private/Files/Cache/AbstractCacheEvent.php12
-rw-r--r--lib/private/Files/Cache/Cache.php33
-rw-r--r--lib/private/Files/Cache/CacheEntry.php2
-rw-r--r--lib/private/Files/Storage/Local.php9
-rw-r--r--lib/private/Lock/MemcacheLockingProvider.php2
-rw-r--r--lib/private/Log.php3
-rw-r--r--lib/private/Log/ExceptionSerializer.php3
-rw-r--r--lib/private/Mail/EMailTemplate.php2
-rw-r--r--lib/private/Repair.php24
-rw-r--r--lib/private/Repair/NC20/EncryptionLegacyCipher.php3
-rw-r--r--lib/private/Repair/NC20/EncryptionMigration.php3
-rw-r--r--lib/private/Repair/NC21/ValidatePhoneNumber.php89
-rw-r--r--lib/private/Repair/Owncloud/CleanPreviews.php73
-rw-r--r--lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php132
-rw-r--r--lib/private/Repair/Owncloud/InstallCoreBundle.php80
-rw-r--r--lib/private/Repair/Owncloud/MoveAvatars.php73
-rw-r--r--lib/private/Repair/Owncloud/MoveAvatarsBackgroundJob.php116
-rw-r--r--lib/private/Repair/Owncloud/SaveAccountsTableData.php9
-rw-r--r--lib/private/Repair/Owncloud/UpdateLanguageCodes.php90
-rw-r--r--lib/private/Security/Bruteforce/Capabilities.php4
-rw-r--r--lib/private/Session/CryptoSessionData.php1
-rw-r--r--lib/private/Share/Constants.php5
-rw-r--r--lib/private/Share20/Manager.php5
-rw-r--r--lib/private/Share20/ProviderFactory.php36
-rw-r--r--lib/private/Support/Subscription/Registry.php106
-rw-r--r--lib/private/Updater.php8
-rw-r--r--lib/private/User/Manager.php8
-rw-r--r--lib/private/User/Session.php14
-rw-r--r--lib/private/legacy/OC_API.php1
-rw-r--r--lib/private/legacy/OC_App.php9
-rw-r--r--lib/private/legacy/OC_Files.php1
-rw-r--r--lib/private/legacy/OC_Image.php17
-rw-r--r--lib/private/legacy/OC_JSON.php2
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';