Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions build/psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1769,9 +1769,6 @@
</TooManyArguments>
</file>
<file src="apps/lookup_server_connector/lib/BackgroundJobs/RetryJob.php">
<InvalidArrayOffset occurrences="1">
<code>$publicData[IAccountManager::PROPERTY_DISPLAYNAME]['value']</code>
</InvalidArrayOffset>
<InvalidScalarArgument occurrences="1">
<code>$this-&gt;retries + 1</code>
</InvalidScalarArgument>
Expand Down
3 changes: 3 additions & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
'OCP\\Accounts\\IAccount' => $baseDir . '/lib/public/Accounts/IAccount.php',
'OCP\\Accounts\\IAccountManager' => $baseDir . '/lib/public/Accounts/IAccountManager.php',
'OCP\\Accounts\\IAccountProperty' => $baseDir . '/lib/public/Accounts/IAccountProperty.php',
'OCP\\Accounts\\IAccountPropertyCollection' => $baseDir . '/lib/public/Accounts/IAccountPropertyCollection.php',
'OCP\\Accounts\\PropertyDoesNotExistException' => $baseDir . '/lib/public/Accounts/PropertyDoesNotExistException.php',
'OCP\\Activity\\ActivitySettings' => $baseDir . '/lib/public/Activity/ActivitySettings.php',
'OCP\\Activity\\IConsumer' => $baseDir . '/lib/public/Activity/IConsumer.php',
Expand Down Expand Up @@ -581,7 +582,9 @@
'OC\\Accounts\\Account' => $baseDir . '/lib/private/Accounts/Account.php',
'OC\\Accounts\\AccountManager' => $baseDir . '/lib/private/Accounts/AccountManager.php',
'OC\\Accounts\\AccountProperty' => $baseDir . '/lib/private/Accounts/AccountProperty.php',
'OC\\Accounts\\AccountPropertyCollection' => $baseDir . '/lib/private/Accounts/AccountPropertyCollection.php',
'OC\\Accounts\\Hooks' => $baseDir . '/lib/private/Accounts/Hooks.php',
'OC\\Accounts\\TAccountsHelper' => $baseDir . '/lib/private/Accounts/TAccountsHelper.php',
'OC\\Activity\\ActivitySettingsAdapter' => $baseDir . '/lib/private/Activity/ActivitySettingsAdapter.php',
'OC\\Activity\\Event' => $baseDir . '/lib/private/Activity/Event.php',
'OC\\Activity\\EventMerger' => $baseDir . '/lib/private/Activity/EventMerger.php',
Expand Down
3 changes: 3 additions & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OCP\\Accounts\\IAccount' => __DIR__ . '/../../..' . '/lib/public/Accounts/IAccount.php',
'OCP\\Accounts\\IAccountManager' => __DIR__ . '/../../..' . '/lib/public/Accounts/IAccountManager.php',
'OCP\\Accounts\\IAccountProperty' => __DIR__ . '/../../..' . '/lib/public/Accounts/IAccountProperty.php',
'OCP\\Accounts\\IAccountPropertyCollection' => __DIR__ . '/../../..' . '/lib/public/Accounts/IAccountPropertyCollection.php',
'OCP\\Accounts\\PropertyDoesNotExistException' => __DIR__ . '/../../..' . '/lib/public/Accounts/PropertyDoesNotExistException.php',
'OCP\\Activity\\ActivitySettings' => __DIR__ . '/../../..' . '/lib/public/Activity/ActivitySettings.php',
'OCP\\Activity\\IConsumer' => __DIR__ . '/../../..' . '/lib/public/Activity/IConsumer.php',
Expand Down Expand Up @@ -610,7 +611,9 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Accounts\\Account' => __DIR__ . '/../../..' . '/lib/private/Accounts/Account.php',
'OC\\Accounts\\AccountManager' => __DIR__ . '/../../..' . '/lib/private/Accounts/AccountManager.php',
'OC\\Accounts\\AccountProperty' => __DIR__ . '/../../..' . '/lib/private/Accounts/AccountProperty.php',
'OC\\Accounts\\AccountPropertyCollection' => __DIR__ . '/../../..' . '/lib/private/Accounts/AccountPropertyCollection.php',
'OC\\Accounts\\Hooks' => __DIR__ . '/../../..' . '/lib/private/Accounts/Hooks.php',
'OC\\Accounts\\TAccountsHelper' => __DIR__ . '/../../..' . '/lib/private/Accounts/TAccountsHelper.php',
'OC\\Activity\\ActivitySettingsAdapter' => __DIR__ . '/../../..' . '/lib/private/Activity/ActivitySettingsAdapter.php',
'OC\\Activity\\Event' => __DIR__ . '/../../..' . '/lib/private/Activity/Event.php',
'OC\\Activity\\EventMerger' => __DIR__ . '/../../..' . '/lib/private/Activity/EventMerger.php',
Expand Down
62 changes: 54 additions & 8 deletions lib/private/Accounts/Account.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,17 @@

namespace OC\Accounts;

use Generator;
use OCP\Accounts\IAccount;
use OCP\Accounts\IAccountProperty;
use OCP\Accounts\IAccountPropertyCollection;
use OCP\Accounts\PropertyDoesNotExistException;
use OCP\IUser;

class Account implements IAccount {
use TAccountsHelper;

/** @var IAccountProperty[] */
/** @var IAccountPropertyCollection[]|IAccountProperty[] */
private $properties = [];

/** @var IUser */
Expand All @@ -45,31 +48,59 @@ public function __construct(IUser $user) {
}

public function setProperty(string $property, string $value, string $scope, string $verified, string $verificationData = ''): IAccount {
if ($this->isCollection($property)) {
throw new \InvalidArgumentException('setProperty cannot set an IAccountsPropertyCollection');
}
$this->properties[$property] = new AccountProperty($property, $value, $scope, $verified, $verificationData);
return $this;
}

public function getProperty(string $property): IAccountProperty {
if (!array_key_exists($property, $this->properties)) {
if ($this->isCollection($property)) {
throw new \InvalidArgumentException('getProperty cannot retrieve an IAccountsPropertyCollection');
}
if (!array_key_exists($property, $this->properties) || !$this->properties[$property] instanceof IAccountProperty) {
throw new PropertyDoesNotExistException($property);
}
return $this->properties[$property];
}

public function getProperties(): array {
return $this->properties;
return array_filter($this->properties, function ($obj) {
return $obj instanceof IAccountProperty;
});
}

public function getAllProperties(): Generator {
foreach ($this->properties as $propertyObject) {
if ($propertyObject instanceof IAccountProperty) {
yield $propertyObject;
} elseif ($propertyObject instanceof IAccountPropertyCollection) {
foreach ($propertyObject->getProperties() as $property) {
yield $property;
}
}
}
}

public function getFilteredProperties(string $scope = null, string $verified = null): array {
return \array_filter($this->properties, function (IAccountProperty $obj) use ($scope, $verified) {
$result = $incrementals = [];
/** @var IAccountProperty $obj */
foreach ($this->getAllProperties() as $obj) {
if ($scope !== null && $scope !== $obj->getScope()) {
return false;
continue;
}
if ($verified !== null && $verified !== $obj->getVerified()) {
return false;
continue;
}
return true;
});
$index = $obj->getName();
if ($this->isCollection($index)) {
$incrementals[$index] = ($incrementals[$index] ?? -1) + 1;
$index .= '#' . $incrementals[$index];
}
$result[$index] = $obj;
}
return $result;
}

public function jsonSerialize() {
Expand All @@ -79,4 +110,19 @@ public function jsonSerialize() {
public function getUser(): IUser {
return $this->user;
}

public function setPropertyCollection(IAccountPropertyCollection $propertyCollection): IAccount {
$this->properties[$propertyCollection->getName()] = $propertyCollection;
return $this;
}

public function getPropertyCollection(string $propertyCollection): IAccountPropertyCollection {
if (!array_key_exists($propertyCollection, $this->properties)) {
throw new PropertyDoesNotExistException($propertyCollection);
}
if (!$this->properties[$propertyCollection] instanceof IAccountPropertyCollection) {
throw new \RuntimeException('Requested collection is not an IAccountPropertyCollection');
}
return $this->properties[$propertyCollection];
}
}
138 changes: 89 additions & 49 deletions lib/private/Accounts/AccountManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
use function array_flip;
use function json_decode;
use function json_last_error;

Expand All @@ -57,6 +58,7 @@
* @package OC\Accounts
*/
class AccountManager implements IAccountManager {
use TAccountsHelper;

/** @var IDBConnection database connection */
private $connection;
Expand Down Expand Up @@ -139,6 +141,61 @@ protected function parseWebsite(string $input): string {
return $input;
}

protected function sanitizeLength(array &$propertyData, bool $throwOnData = false): void {
if (isset($propertyData['value']) && strlen($propertyData['value']) > 2048) {
if ($throwOnData) {
throw new \InvalidArgumentException();
} else {
$propertyData['value'] = '';
}
}
}

protected function testValueLengths(array &$data, bool $throwOnData = false): void {
try {
foreach ($data as $propertyName => &$propertyData) {
if ($this->isCollection($propertyName)) {
$this->testValueLengths($propertyData, $throwOnData);
} else {
$this->sanitizeLength($propertyData, $throwOnData);
}
}
} catch (\InvalidArgumentException $e) {
throw new \InvalidArgumentException($propertyName);
}
}

protected function testPropertyScopes(array &$data, array $allowedScopes, bool $throwOnData = false, string $parentPropertyName = null): void {
foreach ($data as $propertyNameOrIndex => &$propertyData) {
if ($this->isCollection($propertyNameOrIndex)) {
$this->testPropertyScopes($propertyData, $allowedScopes, $throwOnData);
} elseif (isset($propertyData['scope'])) {
$effectivePropertyName = $parentPropertyName ?? $propertyNameOrIndex;

if ($throwOnData && !in_array($propertyData['scope'], $allowedScopes, true)) {
throw new \InvalidArgumentException('scope');
}

if (
$propertyData['scope'] === self::SCOPE_PRIVATE
&& ($effectivePropertyName === self::PROPERTY_DISPLAYNAME || $effectivePropertyName === self::PROPERTY_EMAIL)
) {
if ($throwOnData) {
// v2-private is not available for these fields
throw new \InvalidArgumentException('scope');
} else {
// default to local
$data[$propertyNameOrIndex]['scope'] = self::SCOPE_LOCAL;
}
} else {
// migrate scope values to the new format
// invalid scopes are mapped to a default value
$data[$propertyNameOrIndex]['scope'] = AccountProperty::mapScopeToV2($propertyData['scope']);
}
}
}
}

/**
* update user record
*
Expand Down Expand Up @@ -166,16 +223,7 @@ public function updateUser(IUser $user, array $data, bool $throwOnData = false):
}
}

// set a max length
foreach ($data as $propertyName => $propertyData) {
if (isset($data[$propertyName]) && isset($data[$propertyName]['value']) && strlen($data[$propertyName]['value']) > 2048) {
if ($throwOnData) {
throw new \InvalidArgumentException($propertyName);
} else {
$data[$propertyName]['value'] = '';
}
}
}
$this->testValueLengths($data);

if (isset($data[self::PROPERTY_WEBSITE]) && $data[self::PROPERTY_WEBSITE]['value'] !== '') {
try {
Expand All @@ -198,31 +246,7 @@ public function updateUser(IUser $user, array $data, bool $throwOnData = false):
self::VISIBILITY_PUBLIC,
];

// validate and convert scope values
foreach ($data as $propertyName => $propertyData) {
if (isset($propertyData['scope'])) {
if ($throwOnData && !in_array($propertyData['scope'], $allowedScopes, true)) {
throw new \InvalidArgumentException('scope');
}

if (
$propertyData['scope'] === self::SCOPE_PRIVATE
&& ($propertyName === self::PROPERTY_DISPLAYNAME || $propertyName === self::PROPERTY_EMAIL)
) {
if ($throwOnData) {
// v2-private is not available for these fields
throw new \InvalidArgumentException('scope');
} else {
// default to local
$data[$propertyName]['scope'] = self::SCOPE_LOCAL;
}
} else {
// migrate scope values to the new format
// invalid scopes are mapped to a default value
$data[$propertyName]['scope'] = AccountProperty::mapScopeToV2($propertyData['scope']);
}
}
}
$this->testPropertyScopes($data, $allowedScopes, $throwOnData);

if (empty($userData)) {
$this->insertNewUser($user, $data);
Expand Down Expand Up @@ -276,12 +300,9 @@ public function deleteUserData(IUser $user): void {
/**
* get stored data from a given user
*
* @param IUser $user
* @return array
*
* @deprecated use getAccount instead to make sure migrated properties work correctly
*/
public function getUser(IUser $user) {
public function getUser(IUser $user, bool $insertIfNotExists = true): array {
$uid = $user->getUID();
$query = $this->connection->getQueryBuilder();
$query->select('data')
Expand All @@ -294,7 +315,9 @@ public function getUser(IUser $user) {

if (empty($accountData)) {
$userData = $this->buildDefaultUserRecord($user);
$this->insertNewUser($user, $userData);
if ($insertIfNotExists) {
$this->insertNewUser($user, $userData);
}
return $userData;
}

Expand All @@ -305,9 +328,7 @@ public function getUser(IUser $user) {
return $this->buildDefaultUserRecord($user);
}

$userDataArray = $this->addMissingDefaultValues($userDataArray);

return $userDataArray;
return $this->addMissingDefaultValues($userDataArray);
}

public function searchUsers(string $property, array $values): array {
Expand All @@ -324,12 +345,23 @@ public function searchUsers(string $property, array $values): array {
$result = $query->execute();

while ($row = $result->fetch()) {
$matches[$row['value']] = $row['uid'];
$matches[$row['uid']] = $row['value'];
}
$result->closeCursor();
}

return $matches;
$result = array_merge($matches, $this->searchUsersForRelatedCollection($property, $values));

return array_flip($result);
}

protected function searchUsersForRelatedCollection(string $property, array $values): array {
switch ($property) {
case IAccountManager::PROPERTY_EMAIL:
return array_flip($this->searchUsers(IAccountManager::COLLECTION_EMAIL, $values));
default:
return [];
}
}

/**
Expand All @@ -340,7 +372,7 @@ public function searchUsers(string $property, array $values): array {
* @param IUser $user
* @return array
*/
protected function checkEmailVerification($oldData, $newData, IUser $user) {
protected function checkEmailVerification($oldData, $newData, IUser $user): array {
if ($oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']) {
$this->jobList->add(VerifyUserData::class,
[
Expand Down Expand Up @@ -381,7 +413,7 @@ protected function addMissingDefaultValues(array $userData) {
* @param array $newData
* @return array
*/
protected function updateVerifyStatus($oldData, $newData) {
protected function updateVerifyStatus(array $oldData, array $newData): array {

// which account was already verified successfully?
$twitterVerified = isset($oldData[self::PROPERTY_TWITTER]['verified']) && $oldData[self::PROPERTY_TWITTER]['verified'] === self::VERIFIED;
Expand Down Expand Up @@ -481,12 +513,20 @@ protected function writeUserData(IUser $user, array $data): void {
'value' => $query->createParameter('value'),
]
);
$this->writeUserDataProperties($query, $data);
}

protected function writeUserDataProperties(IQueryBuilder $query, array $data, string $parentPropertyName = null): void {
foreach ($data as $propertyName => $property) {
if ($propertyName === self::PROPERTY_AVATAR) {
if ($this->isCollection($propertyName)) {
$this->writeUserDataProperties($query, $property, $propertyName);
continue;
}
if (($parentPropertyName ?? $propertyName) === self::PROPERTY_AVATAR) {
continue;
}

$query->setParameter('name', $propertyName)
$query->setParameter('name', $parentPropertyName ?? $propertyName)
->setParameter('value', $property['value'] ?? '');
$query->execute();
}
Expand Down
Loading