Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
31 changes: 21 additions & 10 deletions lib/private/Accounts/AccountManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,22 +115,33 @@ protected function testValueLengths(array $properties, bool $throwOnData = false
}
}

protected function testPropertyScope(IAccountProperty $property, array $allowedScopes, bool $throwOnData): void {
if ($throwOnData && !in_array($property->getScope(), $allowedScopes, true)) {
protected function testPropertyScope(IAccountProperty $property, array $allowedScopes): void {
if (!in_array($property->getScope(), $allowedScopes, true)) {
throw new InvalidArgumentException('scope');
}

// Properties that are local-only must not be set to FEDERATED or PUBLISHED scope.
if (
$property->getScope() === self::SCOPE_PRIVATE
&& in_array($property->getName(), [self::PROPERTY_DISPLAYNAME, self::PROPERTY_EMAIL])
in_array($property->getName(), self::UNPUBLISHED_PROPERTIES, true)
&& in_array($property->getScope(), [self::SCOPE_FEDERATED, self::SCOPE_PUBLISHED], true)
) {
if ($throwOnData) {
// v2-private is not available for these fields
throw new InvalidArgumentException('scope');
}

// PUBLISHED scope requires the lookup server upload to be enabled by the admin.
if ($property->getScope() === self::SCOPE_PUBLISHED) {
$lookupServerUploadEnabled = $this->config->getAppValue('files_sharing', 'lookupServerUploadEnabled', 'no') === 'yes';
if (!$lookupServerUploadEnabled) {
throw new InvalidArgumentException('scope');
} else {
// default to local
$property->setScope(self::SCOPE_LOCAL);
}
}

if (
$property->getScope() === self::SCOPE_PRIVATE
&& in_array($property->getName(), [self::PROPERTY_DISPLAYNAME, self::PROPERTY_EMAIL])
) {
// v2-private is not available for these fields
throw new InvalidArgumentException('scope');
} else {
// migrate scope values to the new format
// invalid scopes are mapped to a default value
Expand Down Expand Up @@ -877,7 +888,7 @@ public function updateAccount(IAccount $account): void {
}

foreach ($account->getAllProperties() as $property) {
$this->testPropertyScope($property, self::ALLOWED_SCOPES, true);
$this->testPropertyScope($property, self::ALLOWED_SCOPES);
}

$oldData = $this->getUser($account->getUser(), false);
Expand Down
15 changes: 15 additions & 0 deletions lib/public/Accounts/IAccountManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,21 @@ interface IAccountManager {
*/
public const PROPERTY_PRONOUNS = 'pronouns';

/**
* Properties that are only visible within the local instance and must not be
* published to the global lookup server or shared via federation.
* This matches the frontend's UNPUBLISHED_READABLE_PROPERTIES constant.
Comment thread
miaulalala marked this conversation as resolved.
*
* @since 34.0.0
*/
public const UNPUBLISHED_PROPERTIES = [
self::PROPERTY_BIOGRAPHY,
self::PROPERTY_BIRTHDATE,
self::PROPERTY_HEADLINE,
self::PROPERTY_ORGANISATION,
self::PROPERTY_ROLE,
];

/**
* The list of allowed properties
*
Expand Down
55 changes: 55 additions & 0 deletions tests/lib/Accounts/AccountManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1062,4 +1062,59 @@ public function testSetDefaultPropertyScopes(array $propertyScopes, array $expec
$this->assertEquals($expectedResultScopeValue, $resultScope, "The result scope doesn't follow the value set into the config or defaults correctly.");
}
}

public static function dataUpdateAccountRejectsInvalidScopeForUnpublishedProperty(): array {
$cases = [];
foreach (IAccountManager::UNPUBLISHED_PROPERTIES as $property) {
$cases[] = [$property, IAccountManager::SCOPE_FEDERATED];
$cases[] = [$property, IAccountManager::SCOPE_PUBLISHED];
}
return $cases;
}

#[\PHPUnit\Framework\Attributes\DataProvider('dataUpdateAccountRejectsInvalidScopeForUnpublishedProperty')]
public function testUpdateAccountRejectsInvalidScopeForUnpublishedProperty(string $property, string $scope): void {
$user = $this->createMock(IUser::class);
$account = new Account($user);
$account->setProperty($property, 'some value', $scope, IAccountManager::NOT_VERIFIED);

$manager = $this->getInstance(['getUser', 'updateUser']);
$manager->method('getUser')->with($user, false)->willReturn([]);

$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('scope');
$manager->updateAccount($account);
}

public function testUpdateAccountRejectsPublishedScopeWhenLookupServerDisabled(): void {
$user = $this->createMock(IUser::class);
$account = new Account($user);
$account->setProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED);

$manager = $this->getInstance(['getUser', 'updateUser']);
$manager->method('getUser')->with($user, false)->willReturn([]);
$this->config->method('getAppValue')
->with('files_sharing', 'lookupServerUploadEnabled', 'no')
->willReturn('no');

$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('scope');
$manager->updateAccount($account);
}

public function testUpdateAccountAllowsPublishedScopeWhenLookupServerEnabled(): void {
$user = $this->createMock(IUser::class);
$account = new Account($user);
$account->setProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED);

$manager = $this->getInstance(['getUser', 'updateUser']);
$manager->method('getUser')->with($user, false)->willReturn([]);
$this->config->method('getSystemValueString')->willReturn('');
$this->config->method('getAppValue')
->with('files_sharing', 'lookupServerUploadEnabled', 'no')
->willReturn('yes');
$manager->expects($this->once())->method('updateUser');

$manager->updateAccount($account);
}
}
Loading