Merge pull request #26243 from nextcloud/enh/noid/avatar-privacy-new-scope

Avatar privacy and new scope
This commit is contained in:
Joas Schilling 2021-03-29 09:01:12 +02:00 committed by GitHub
commit 602de272c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1266 additions and 277 deletions

View File

@ -71,8 +71,8 @@ class Converter {
foreach ($userData as $property => $value) { foreach ($userData as $property => $value) {
$shareWithTrustedServers = $shareWithTrustedServers =
$value['scope'] === AccountManager::VISIBILITY_CONTACTS_ONLY || $value['scope'] === AccountManager::SCOPE_FEDERATED ||
$value['scope'] === AccountManager::VISIBILITY_PUBLIC; $value['scope'] === AccountManager::SCOPE_PUBLISHED;
$emptyValue = !isset($value['value']) || $value['value'] === ''; $emptyValue = !isset($value['value']) || $value['value'] === '';

View File

@ -53,36 +53,36 @@ class ConverterTest extends TestCase {
IAccountManager::PROPERTY_DISPLAYNAME => IAccountManager::PROPERTY_DISPLAYNAME =>
[ [
'value' => $user->getDisplayName(), 'value' => $user->getDisplayName(),
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY, 'scope' => AccountManager::SCOPE_FEDERATED,
], ],
IAccountManager::PROPERTY_ADDRESS => IAccountManager::PROPERTY_ADDRESS =>
[ [
'value' => '', 'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE, 'scope' => AccountManager::SCOPE_LOCAL,
], ],
IAccountManager::PROPERTY_WEBSITE => IAccountManager::PROPERTY_WEBSITE =>
[ [
'value' => '', 'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE, 'scope' => AccountManager::SCOPE_LOCAL,
], ],
IAccountManager::PROPERTY_EMAIL => IAccountManager::PROPERTY_EMAIL =>
[ [
'value' => $user->getEMailAddress(), 'value' => $user->getEMailAddress(),
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY, 'scope' => AccountManager::SCOPE_FEDERATED,
], ],
IAccountManager::PROPERTY_AVATAR => IAccountManager::PROPERTY_AVATAR =>
[ [
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY 'scope' => AccountManager::SCOPE_FEDERATED
], ],
IAccountManager::PROPERTY_PHONE => IAccountManager::PROPERTY_PHONE =>
[ [
'value' => '', 'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE, 'scope' => AccountManager::SCOPE_LOCAL,
], ],
IAccountManager::PROPERTY_TWITTER => IAccountManager::PROPERTY_TWITTER =>
[ [
'value' => '', 'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE, 'scope' => AccountManager::SCOPE_LOCAL,
], ],
] ]
); );

View File

@ -136,36 +136,36 @@ class SyncServiceTest extends TestCase {
IAccountManager::PROPERTY_DISPLAYNAME => IAccountManager::PROPERTY_DISPLAYNAME =>
[ [
'value' => $user->getDisplayName(), 'value' => $user->getDisplayName(),
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY, 'scope' => AccountManager::SCOPE_FEDERATED,
], ],
IAccountManager::PROPERTY_ADDRESS => IAccountManager::PROPERTY_ADDRESS =>
[ [
'value' => '', 'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE, 'scope' => AccountManager::SCOPE_LOCAL,
], ],
IAccountManager::PROPERTY_WEBSITE => IAccountManager::PROPERTY_WEBSITE =>
[ [
'value' => '', 'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE, 'scope' => AccountManager::SCOPE_LOCAL,
], ],
IAccountManager::PROPERTY_EMAIL => IAccountManager::PROPERTY_EMAIL =>
[ [
'value' => $user->getEMailAddress(), 'value' => $user->getEMailAddress(),
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY, 'scope' => AccountManager::SCOPE_FEDERATED,
], ],
IAccountManager::PROPERTY_AVATAR => IAccountManager::PROPERTY_AVATAR =>
[ [
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY 'scope' => AccountManager::SCOPE_FEDERATED
], ],
IAccountManager::PROPERTY_PHONE => IAccountManager::PROPERTY_PHONE =>
[ [
'value' => '', 'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE, 'scope' => AccountManager::SCOPE_LOCAL,
], ],
IAccountManager::PROPERTY_TWITTER => IAccountManager::PROPERTY_TWITTER =>
[ [
'value' => '', 'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE, 'scope' => AccountManager::SCOPE_LOCAL,
], ],
] ]
); );

View File

@ -343,7 +343,7 @@ class ShareController extends AuthPublicShareController {
$ownerAccount = $this->accountManager->getAccount($owner); $ownerAccount = $this->accountManager->getAccount($owner);
$ownerName = $ownerAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME); $ownerName = $ownerAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME);
if ($ownerName->getScope() === IAccountManager::VISIBILITY_PUBLIC) { if ($ownerName->getScope() === IAccountManager::SCOPE_PUBLISHED) {
$shareTmpl['owner'] = $owner->getUID(); $shareTmpl['owner'] = $owner->getUID();
$shareTmpl['shareOwner'] = $owner->getDisplayName(); $shareTmpl['shareOwner'] = $owner->getDisplayName();
} }

View File

@ -234,7 +234,7 @@ class ShareControllerTest extends \Test\TestCase {
$accountName = $this->createMock(IAccountProperty::class); $accountName = $this->createMock(IAccountProperty::class);
$accountName->method('getScope') $accountName->method('getScope')
->willReturn(IAccountManager::VISIBILITY_PUBLIC); ->willReturn(IAccountManager::SCOPE_PUBLISHED);
$account = $this->createMock(IAccount::class); $account = $this->createMock(IAccount::class);
$account->method('getProperty') $account->method('getProperty')
->with(IAccountManager::PROPERTY_DISPLAYNAME) ->with(IAccountManager::PROPERTY_DISPLAYNAME)
@ -381,7 +381,7 @@ class ShareControllerTest extends \Test\TestCase {
$accountName = $this->createMock(IAccountProperty::class); $accountName = $this->createMock(IAccountProperty::class);
$accountName->method('getScope') $accountName->method('getScope')
->willReturn(IAccountManager::VISIBILITY_PRIVATE); ->willReturn(IAccountManager::SCOPE_LOCAL);
$account = $this->createMock(IAccount::class); $account = $this->createMock(IAccount::class);
$account->method('getProperty') $account->method('getProperty')
->with(IAccountManager::PROPERTY_DISPLAYNAME) ->with(IAccountManager::PROPERTY_DISPLAYNAME)
@ -528,7 +528,7 @@ class ShareControllerTest extends \Test\TestCase {
$accountName = $this->createMock(IAccountProperty::class); $accountName = $this->createMock(IAccountProperty::class);
$accountName->method('getScope') $accountName->method('getScope')
->willReturn(IAccountManager::VISIBILITY_PUBLIC); ->willReturn(IAccountManager::SCOPE_PUBLISHED);
$account = $this->createMock(IAccount::class); $account = $this->createMock(IAccount::class);
$account->method('getProperty') $account->method('getProperty')
->with(IAccountManager::PROPERTY_DISPLAYNAME) ->with(IAccountManager::PROPERTY_DISPLAYNAME)
@ -688,7 +688,7 @@ class ShareControllerTest extends \Test\TestCase {
$accountName = $this->createMock(IAccountProperty::class); $accountName = $this->createMock(IAccountProperty::class);
$accountName->method('getScope') $accountName->method('getScope')
->willReturn(IAccountManager::VISIBILITY_PUBLIC); ->willReturn(IAccountManager::SCOPE_PUBLISHED);
$account = $this->createMock(IAccount::class); $account = $this->createMock(IAccount::class);
$account->method('getProperty') $account->method('getProperty')
->with(IAccountManager::PROPERTY_DISPLAYNAME) ->with(IAccountManager::PROPERTY_DISPLAYNAME)

View File

@ -193,7 +193,7 @@ class RetryJob extends Job {
$publicData = []; $publicData = [];
foreach ($account->getProperties() as $property) { foreach ($account->getProperties() as $property) {
if ($property->getScope() === IAccountManager::VISIBILITY_PUBLIC) { if ($property->getScope() === IAccountManager::SCOPE_PUBLISHED) {
$publicData[$property->getName()] = $property->getValue(); $publicData[$property->getName()] = $property->getValue();
} }
} }

View File

@ -8,6 +8,7 @@ $baseDir = $vendorDir;
return array( return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'OCA\\Provisioning_API\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php', 'OCA\\Provisioning_API\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
'OCA\\Provisioning_API\\Capabilities' => $baseDir . '/../lib/Capabilities.php',
'OCA\\Provisioning_API\\Controller\\AUserData' => $baseDir . '/../lib/Controller/AUserData.php', 'OCA\\Provisioning_API\\Controller\\AUserData' => $baseDir . '/../lib/Controller/AUserData.php',
'OCA\\Provisioning_API\\Controller\\AppConfigController' => $baseDir . '/../lib/Controller/AppConfigController.php', 'OCA\\Provisioning_API\\Controller\\AppConfigController' => $baseDir . '/../lib/Controller/AppConfigController.php',
'OCA\\Provisioning_API\\Controller\\AppsController' => $baseDir . '/../lib/Controller/AppsController.php', 'OCA\\Provisioning_API\\Controller\\AppsController' => $baseDir . '/../lib/Controller/AppsController.php',

View File

@ -23,6 +23,7 @@ class ComposerStaticInitProvisioning_API
public static $classMap = array ( public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'OCA\\Provisioning_API\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php', 'OCA\\Provisioning_API\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
'OCA\\Provisioning_API\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php',
'OCA\\Provisioning_API\\Controller\\AUserData' => __DIR__ . '/..' . '/../lib/Controller/AUserData.php', 'OCA\\Provisioning_API\\Controller\\AUserData' => __DIR__ . '/..' . '/../lib/Controller/AUserData.php',
'OCA\\Provisioning_API\\Controller\\AppConfigController' => __DIR__ . '/..' . '/../lib/Controller/AppConfigController.php', 'OCA\\Provisioning_API\\Controller\\AppConfigController' => __DIR__ . '/..' . '/../lib/Controller/AppConfigController.php',
'OCA\\Provisioning_API\\Controller\\AppsController' => __DIR__ . '/..' . '/../lib/Controller/AppsController.php', 'OCA\\Provisioning_API\\Controller\\AppsController' => __DIR__ . '/..' . '/../lib/Controller/AppsController.php',

View File

@ -29,6 +29,7 @@
namespace OCA\Provisioning_API\AppInfo; namespace OCA\Provisioning_API\AppInfo;
use OC\Group\Manager as GroupManager; use OC\Group\Manager as GroupManager;
use OCA\Provisioning_API\Capabilities;
use OCA\Provisioning_API\Listener\UserDeletedListener; use OCA\Provisioning_API\Listener\UserDeletedListener;
use OCA\Provisioning_API\Middleware\ProvisioningApiMiddleware; use OCA\Provisioning_API\Middleware\ProvisioningApiMiddleware;
use OCA\Settings\Mailer\NewUserMailHelper; use OCA\Settings\Mailer\NewUserMailHelper;
@ -92,6 +93,7 @@ class Application extends App implements IBootstrap {
); );
}); });
$context->registerMiddleware(ProvisioningApiMiddleware::class); $context->registerMiddleware(ProvisioningApiMiddleware::class);
$context->registerCapability(Capabilities::class);
} }
public function boot(IBootContext $context): void { public function boot(IBootContext $context): void {

View File

@ -0,0 +1,62 @@
<?php
/**
* @copyright Copyright (c) 2021 Vincent Petry <vincent@nextcloud.com>
*
* @author Vincent Petry <vincent@nextcloud.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 OCA\Provisioning_API;
use OCA\FederatedFileSharing\FederatedShareProvider;
use OCP\App\IAppManager;
use OCP\Capabilities\ICapability;
class Capabilities implements ICapability {
/** @var IAppManager */
private $appManager;
public function __construct(IAppManager $appManager) {
$this->appManager = $appManager;
}
/**
* Function an app uses to return the capabilities
*
* @return array Array containing the apps capabilities
*/
public function getCapabilities() {
$federationScopesEnabled = false;
$federatedFileSharingEnabled = $this->appManager->isEnabledForUser('federatedfilesharing');
if ($federatedFileSharingEnabled) {
/** @var FederatedShareProvider $shareProvider */
$shareProvider = \OC::$server->query(FederatedShareProvider::class);
$federationScopesEnabled = $shareProvider->isLookupServerUploadEnabled();
}
return [
'provisioning_api' => [
'version' => $this->appManager->getAppVersion('provisioning_api'),
'AccountPropertyScopesVersion' => 2,
'AccountPropertyScopesFederationEnabled' => $federationScopesEnabled,
]
];
}
}

View File

@ -51,6 +51,7 @@ use OCP\User\Backend\ISetDisplayNameBackend;
use OCP\User\Backend\ISetPasswordBackend; use OCP\User\Backend\ISetPasswordBackend;
abstract class AUserData extends OCSController { abstract class AUserData extends OCSController {
public const SCOPE_SUFFIX = 'Scope';
/** @var IUserManager */ /** @var IUserManager */
protected $userManager; protected $userManager;
@ -87,12 +88,13 @@ abstract class AUserData extends OCSController {
* creates a array with all user data * creates a array with all user data
* *
* @param string $userId * @param string $userId
* @param bool $includeScopes
* @return array * @return array
* @throws NotFoundException * @throws NotFoundException
* @throws OCSException * @throws OCSException
* @throws OCSNotFoundException * @throws OCSNotFoundException
*/ */
protected function getUserData(string $userId): array { protected function getUserData(string $userId, bool $includeScopes = false): array {
$currentLoggedInUser = $this->userSession->getUser(); $currentLoggedInUser = $this->userSession->getUser();
$data = []; $data = [];
@ -115,7 +117,7 @@ abstract class AUserData extends OCSController {
} }
// Get groups data // Get groups data
$userAccount = $this->accountManager->getUser($targetUserObject); $userAccount = $this->accountManager->getAccount($targetUserObject);
$groups = $this->groupManager->getUserGroups($targetUserObject); $groups = $this->groupManager->getUserGroups($targetUserObject);
$gids = []; $gids = [];
foreach ($groups as $group) { foreach ($groups as $group) {
@ -137,12 +139,33 @@ abstract class AUserData extends OCSController {
$data['backend'] = $targetUserObject->getBackendClassName(); $data['backend'] = $targetUserObject->getBackendClassName();
$data['subadmin'] = $this->getUserSubAdminGroupsData($targetUserObject->getUID()); $data['subadmin'] = $this->getUserSubAdminGroupsData($targetUserObject->getUID());
$data['quota'] = $this->fillStorageInfo($targetUserObject->getUID()); $data['quota'] = $this->fillStorageInfo($targetUserObject->getUID());
if ($includeScopes) {
$data[IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX] = $userAccount->getProperty(IAccountManager::PROPERTY_AVATAR)->getScope();
}
$data[IAccountManager::PROPERTY_EMAIL] = $targetUserObject->getEMailAddress(); $data[IAccountManager::PROPERTY_EMAIL] = $targetUserObject->getEMailAddress();
if ($includeScopes) {
$data[IAccountManager::PROPERTY_EMAIL . self::SCOPE_SUFFIX] = $userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getScope();
}
$data[IAccountManager::PROPERTY_DISPLAYNAME] = $targetUserObject->getDisplayName(); $data[IAccountManager::PROPERTY_DISPLAYNAME] = $targetUserObject->getDisplayName();
$data[IAccountManager::PROPERTY_PHONE] = $userAccount[IAccountManager::PROPERTY_PHONE]['value']; if ($includeScopes) {
$data[IAccountManager::PROPERTY_ADDRESS] = $userAccount[IAccountManager::PROPERTY_ADDRESS]['value']; $data[IAccountManager::PROPERTY_DISPLAYNAME . self::SCOPE_SUFFIX] = $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getScope();
$data[IAccountManager::PROPERTY_WEBSITE] = $userAccount[IAccountManager::PROPERTY_WEBSITE]['value']; }
$data[IAccountManager::PROPERTY_TWITTER] = $userAccount[IAccountManager::PROPERTY_TWITTER]['value'];
foreach ([
IAccountManager::PROPERTY_PHONE,
IAccountManager::PROPERTY_ADDRESS,
IAccountManager::PROPERTY_WEBSITE,
IAccountManager::PROPERTY_TWITTER,
] as $propertyName) {
$property = $userAccount->getProperty($propertyName);
$data[$propertyName] = $property->getValue();
if ($includeScopes) {
$data[$propertyName . self::SCOPE_SUFFIX] = $property->getScope();
}
}
$data['groups'] = $gids; $data['groups'] = $gids;
$data['language'] = $this->l10nFactory->getUserLanguage($targetUserObject); $data['language'] = $this->l10nFactory->getUserLanguage($targetUserObject);
$data['locale'] = $this->config->getUserValue($targetUserObject->getUID(), 'core', 'locale'); $data['locale'] = $this->config->getUserValue($targetUserObject->getUID(), 'core', 'locale');

View File

@ -50,7 +50,6 @@ use OC\Accounts\AccountManager;
use OC\Authentication\Token\RemoteWipe; use OC\Authentication\Token\RemoteWipe;
use OC\HintException; use OC\HintException;
use OC\KnownUser\KnownUserService; use OC\KnownUser\KnownUserService;
use OCA\Provisioning_API\FederatedShareProviderFactory;
use OCA\Settings\Mailer\NewUserMailHelper; use OCA\Settings\Mailer\NewUserMailHelper;
use OCP\Accounts\IAccountManager; use OCP\Accounts\IAccountManager;
use OCP\App\IAppManager; use OCP\App\IAppManager;
@ -85,8 +84,6 @@ class UsersController extends AUserData {
protected $l10nFactory; protected $l10nFactory;
/** @var NewUserMailHelper */ /** @var NewUserMailHelper */
private $newUserMailHelper; private $newUserMailHelper;
/** @var FederatedShareProviderFactory */
private $federatedShareProviderFactory;
/** @var ISecureRandom */ /** @var ISecureRandom */
private $secureRandom; private $secureRandom;
/** @var RemoteWipe */ /** @var RemoteWipe */
@ -108,7 +105,6 @@ class UsersController extends AUserData {
LoggerInterface $logger, LoggerInterface $logger,
IFactory $l10nFactory, IFactory $l10nFactory,
NewUserMailHelper $newUserMailHelper, NewUserMailHelper $newUserMailHelper,
FederatedShareProviderFactory $federatedShareProviderFactory,
ISecureRandom $secureRandom, ISecureRandom $secureRandom,
RemoteWipe $remoteWipe, RemoteWipe $remoteWipe,
KnownUserService $knownUserService, KnownUserService $knownUserService,
@ -127,7 +123,6 @@ class UsersController extends AUserData {
$this->logger = $logger; $this->logger = $logger;
$this->l10nFactory = $l10nFactory; $this->l10nFactory = $l10nFactory;
$this->newUserMailHelper = $newUserMailHelper; $this->newUserMailHelper = $newUserMailHelper;
$this->federatedShareProviderFactory = $federatedShareProviderFactory;
$this->secureRandom = $secureRandom; $this->secureRandom = $secureRandom;
$this->remoteWipe = $remoteWipe; $this->remoteWipe = $remoteWipe;
$this->knownUserService = $knownUserService; $this->knownUserService = $knownUserService;
@ -488,7 +483,13 @@ class UsersController extends AUserData {
* @throws OCSException * @throws OCSException
*/ */
public function getUser(string $userId): DataResponse { public function getUser(string $userId): DataResponse {
$data = $this->getUserData($userId); $includeScopes = false;
$currentUser = $this->userSession->getUser();
if ($currentUser && $currentUser->getUID() === $userId) {
$includeScopes = true;
}
$data = $this->getUserData($userId, $includeScopes);
// getUserData returns empty array if not enough permissions // getUserData returns empty array if not enough permissions
if (empty($data)) { if (empty($data)) {
throw new OCSException('', OCSController::RESPOND_UNAUTHORISED); throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
@ -508,7 +509,7 @@ class UsersController extends AUserData {
public function getCurrentUser(): DataResponse { public function getCurrentUser(): DataResponse {
$user = $this->userSession->getUser(); $user = $this->userSession->getUser();
if ($user) { if ($user) {
$data = $this->getUserData($user->getUID()); $data = $this->getUserData($user->getUID(), true);
// rename "displayname" to "display-name" only for this call to keep // rename "displayname" to "display-name" only for this call to keep
// the API stable. // the API stable.
$data['display-name'] = $data['displayname']; $data['display-name'] = $data['displayname'];
@ -532,15 +533,10 @@ class UsersController extends AUserData {
$permittedFields[] = IAccountManager::PROPERTY_EMAIL; $permittedFields[] = IAccountManager::PROPERTY_EMAIL;
} }
if ($this->appManager->isEnabledForUser('federatedfilesharing')) { $permittedFields[] = IAccountManager::PROPERTY_PHONE;
$shareProvider = $this->federatedShareProviderFactory->get(); $permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
if ($shareProvider->isLookupServerUploadEnabled()) { $permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
$permittedFields[] = IAccountManager::PROPERTY_PHONE; $permittedFields[] = IAccountManager::PROPERTY_TWITTER;
$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
$permittedFields[] = IAccountManager::PROPERTY_TWITTER;
}
}
return new DataResponse($permittedFields); return new DataResponse($permittedFields);
} }
@ -575,6 +571,9 @@ class UsersController extends AUserData {
$permittedFields[] = IAccountManager::PROPERTY_EMAIL; $permittedFields[] = IAccountManager::PROPERTY_EMAIL;
} }
$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME . self::SCOPE_SUFFIX;
$permittedFields[] = IAccountManager::PROPERTY_EMAIL . self::SCOPE_SUFFIX;
$permittedFields[] = 'password'; $permittedFields[] = 'password';
if ($this->config->getSystemValue('force_language', false) === false || if ($this->config->getSystemValue('force_language', false) === false ||
$this->groupManager->isAdmin($currentLoggedInUser->getUID())) { $this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
@ -586,15 +585,16 @@ class UsersController extends AUserData {
$permittedFields[] = 'locale'; $permittedFields[] = 'locale';
} }
if ($this->appManager->isEnabledForUser('federatedfilesharing')) { $permittedFields[] = IAccountManager::PROPERTY_PHONE;
$shareProvider = $this->federatedShareProviderFactory->get(); $permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
if ($shareProvider->isLookupServerUploadEnabled()) { $permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
$permittedFields[] = IAccountManager::PROPERTY_PHONE; $permittedFields[] = IAccountManager::PROPERTY_TWITTER;
$permittedFields[] = IAccountManager::PROPERTY_ADDRESS; $permittedFields[] = IAccountManager::PROPERTY_PHONE . self::SCOPE_SUFFIX;
$permittedFields[] = IAccountManager::PROPERTY_WEBSITE; $permittedFields[] = IAccountManager::PROPERTY_ADDRESS . self::SCOPE_SUFFIX;
$permittedFields[] = IAccountManager::PROPERTY_TWITTER; $permittedFields[] = IAccountManager::PROPERTY_WEBSITE . self::SCOPE_SUFFIX;
} $permittedFields[] = IAccountManager::PROPERTY_TWITTER . self::SCOPE_SUFFIX;
}
$permittedFields[] = IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX;
// If admin they can edit their own quota // If admin they can edit their own quota
if ($this->groupManager->isAdmin($currentLoggedInUser->getUID())) { if ($this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
@ -699,6 +699,24 @@ class UsersController extends AUserData {
} }
} }
break; break;
case IAccountManager::PROPERTY_DISPLAYNAME . self::SCOPE_SUFFIX:
case IAccountManager::PROPERTY_EMAIL . self::SCOPE_SUFFIX:
case IAccountManager::PROPERTY_PHONE . self::SCOPE_SUFFIX:
case IAccountManager::PROPERTY_ADDRESS . self::SCOPE_SUFFIX:
case IAccountManager::PROPERTY_WEBSITE . self::SCOPE_SUFFIX:
case IAccountManager::PROPERTY_TWITTER . self::SCOPE_SUFFIX:
case IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX:
$propertyName = substr($key, 0, strlen($key) - strlen(self::SCOPE_SUFFIX));
$userAccount = $this->accountManager->getUser($targetUser);
if ($userAccount[$propertyName]['scope'] !== $value) {
$userAccount[$propertyName]['scope'] = $value;
try {
$this->accountManager->updateUser($targetUser, $userAccount, true);
} catch (\InvalidArgumentException $e) {
throw new OCSException('Invalid ' . $e->getMessage(), 102);
}
}
break;
default: default:
throw new OCSException('', 103); throw new OCSException('', 103);
} }

View File

@ -0,0 +1,92 @@
<?php
/**
* @copyright Copyright (c) 2021 Vincent Petry <vincent@nextcloud.com>
*
* @author Vincent Petry <vincent@nextcloud.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 OCA\Provisioning_API\Tests\unit;
use OCA\FederatedFileSharing\FederatedShareProvider;
use OCA\Provisioning_API\Capabilities;
use OCP\App\IAppManager;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
/**
* Capabilities test for provisioning API.
*
* Note: group DB needed because of usage of overwriteService()
*
* @package OCA\Provisioning_API\Tests
* @group DB
*/
class CapabilitiesTest extends TestCase {
/** @var Capabilities */
protected $capabilities;
/** @var IAppManager|MockObject */
protected $appManager;
public function setUp(): void {
parent::setUp();
$this->appManager = $this->createMock(IAppManager::class);
$this->capabilities = new Capabilities($this->appManager);
$this->appManager->expects($this->once())
->method('getAppVersion')
->with('provisioning_api')
->willReturn('1.12');
}
public function getCapabilitiesProvider() {
return [
[false, false, false],
[true, false, false],
[true, true, true],
];
}
/**
* @dataProvider getCapabilitiesProvider
*/
public function testGetCapabilities($federationAppEnabled, $lookupServerEnabled, $expectedFederationScopesEnabled) {
$this->appManager->expects($this->once())
->method('isEnabledForUser')
->with('federatedfilesharing')
->willReturn($federationAppEnabled);
$federatedShareProvider = $this->createMock(FederatedShareProvider::class);
$this->overwriteService(FederatedShareProvider::class, $federatedShareProvider);
$federatedShareProvider->expects($this->any())
->method('isLookupServerUploadEnabled')
->willReturn($lookupServerEnabled);
$expected = [
'provisioning_api' => [
'version' => '1.12',
'AccountPropertyScopesVersion' => 2,
'AccountPropertyScopesFederationEnabled' => $expectedFederationScopesEnabled,
],
];
$this->assertSame($expected, $this->capabilities->getCapabilities());
}
}

View File

@ -46,11 +46,11 @@ use OC\Authentication\Token\RemoteWipe;
use OC\Group\Manager; use OC\Group\Manager;
use OC\KnownUser\KnownUserService; use OC\KnownUser\KnownUserService;
use OC\SubAdmin; use OC\SubAdmin;
use OCA\FederatedFileSharing\FederatedShareProvider;
use OCA\Provisioning_API\Controller\UsersController; use OCA\Provisioning_API\Controller\UsersController;
use OCA\Provisioning_API\FederatedShareProviderFactory;
use OCA\Settings\Mailer\NewUserMailHelper; use OCA\Settings\Mailer\NewUserMailHelper;
use OCP\Accounts\IAccount;
use OCP\Accounts\IAccountManager; use OCP\Accounts\IAccountManager;
use OCP\Accounts\IAccountProperty;
use OCP\App\IAppManager; use OCP\App\IAppManager;
use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\DataResponse;
use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventDispatcher;
@ -97,8 +97,6 @@ class UsersControllerTest extends TestCase {
private $l10nFactory; private $l10nFactory;
/** @var NewUserMailHelper|MockObject */ /** @var NewUserMailHelper|MockObject */
private $newUserMailHelper; private $newUserMailHelper;
/** @var FederatedShareProviderFactory|MockObject */
private $federatedShareProviderFactory;
/** @var ISecureRandom|MockObject */ /** @var ISecureRandom|MockObject */
private $secureRandom; private $secureRandom;
/** @var RemoteWipe|MockObject */ /** @var RemoteWipe|MockObject */
@ -122,7 +120,6 @@ class UsersControllerTest extends TestCase {
$this->urlGenerator = $this->createMock(IURLGenerator::class); $this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->l10nFactory = $this->createMock(IFactory::class); $this->l10nFactory = $this->createMock(IFactory::class);
$this->newUserMailHelper = $this->createMock(NewUserMailHelper::class); $this->newUserMailHelper = $this->createMock(NewUserMailHelper::class);
$this->federatedShareProviderFactory = $this->createMock(FederatedShareProviderFactory::class);
$this->secureRandom = $this->createMock(ISecureRandom::class); $this->secureRandom = $this->createMock(ISecureRandom::class);
$this->remoteWipe = $this->createMock(RemoteWipe::class); $this->remoteWipe = $this->createMock(RemoteWipe::class);
$this->knownUserService = $this->createMock(KnownUserService::class); $this->knownUserService = $this->createMock(KnownUserService::class);
@ -142,7 +139,6 @@ class UsersControllerTest extends TestCase {
$this->logger, $this->logger,
$this->l10nFactory, $this->l10nFactory,
$this->newUserMailHelper, $this->newUserMailHelper,
$this->federatedShareProviderFactory,
$this->secureRandom, $this->secureRandom,
$this->remoteWipe, $this->remoteWipe,
$this->knownUserService, $this->knownUserService,
@ -407,7 +403,6 @@ class UsersControllerTest extends TestCase {
$this->logger, $this->logger,
$this->l10nFactory, $this->l10nFactory,
$this->newUserMailHelper, $this->newUserMailHelper,
$this->federatedShareProviderFactory,
$this->secureRandom, $this->secureRandom,
$this->remoteWipe, $this->remoteWipe,
$this->knownUserService, $this->knownUserService,
@ -934,7 +929,6 @@ class UsersControllerTest extends TestCase {
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();
$this->userSession $this->userSession
->expects($this->once())
->method('getUser') ->method('getUser')
->willReturn($loggedInUser); ->willReturn($loggedInUser);
$this->userManager $this->userManager
@ -1004,16 +998,13 @@ class UsersControllerTest extends TestCase {
$group->expects($this->at(3)) $group->expects($this->at(3))
->method('getGID') ->method('getGID')
->willReturn('group3'); ->willReturn('group3');
$this->accountManager->expects($this->any())->method('getUser')
->with($targetUser) $this->mockAccount($targetUser, [
->willReturn( IAccountManager::PROPERTY_ADDRESS => ['value' => 'address'],
[ IAccountManager::PROPERTY_PHONE => ['value' => 'phone'],
IAccountManager::PROPERTY_ADDRESS => ['value' => 'address'], IAccountManager::PROPERTY_TWITTER => ['value' => 'twitter'],
IAccountManager::PROPERTY_PHONE => ['value' => 'phone'], IAccountManager::PROPERTY_WEBSITE => ['value' => 'website'],
IAccountManager::PROPERTY_TWITTER => ['value' => 'twitter'], ]);
IAccountManager::PROPERTY_WEBSITE => ['value' => 'website'],
]
);
$this->config $this->config
->expects($this->at(0)) ->expects($this->at(0))
->method('getUserValue') ->method('getUserValue')
@ -1173,16 +1164,13 @@ class UsersControllerTest extends TestCase {
$targetUser $targetUser
->method('getUID') ->method('getUID')
->willReturn('UID'); ->willReturn('UID');
$this->accountManager->expects($this->any())->method('getUser')
->with($targetUser) $this->mockAccount($targetUser, [
->willReturn( IAccountManager::PROPERTY_ADDRESS => ['value' => 'address'],
[ IAccountManager::PROPERTY_PHONE => ['value' => 'phone'],
IAccountManager::PROPERTY_ADDRESS => ['value' => 'address'], IAccountManager::PROPERTY_TWITTER => ['value' => 'twitter'],
IAccountManager::PROPERTY_PHONE => ['value' => 'phone'], IAccountManager::PROPERTY_WEBSITE => ['value' => 'website'],
IAccountManager::PROPERTY_TWITTER => ['value' => 'twitter'], ]);
IAccountManager::PROPERTY_WEBSITE => ['value' => 'website'],
]
);
$this->l10nFactory $this->l10nFactory
->expects($this->once()) ->expects($this->once())
@ -1225,14 +1213,13 @@ class UsersControllerTest extends TestCase {
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();
$loggedInUser $loggedInUser
->expects($this->exactly(2)) ->expects($this->exactly(3))
->method('getUID') ->method('getUID')
->willReturn('subadmin'); ->willReturn('subadmin');
$targetUser = $this->getMockBuilder(IUser::class) $targetUser = $this->getMockBuilder(IUser::class)
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();
$this->userSession $this->userSession
->expects($this->once())
->method('getUser') ->method('getUser')
->willReturn($loggedInUser); ->willReturn($loggedInUser);
$this->userManager $this->userManager
@ -1344,16 +1331,12 @@ class UsersControllerTest extends TestCase {
->expects($this->once()) ->expects($this->once())
->method('getBackend') ->method('getBackend')
->willReturn($backend); ->willReturn($backend);
$this->accountManager->expects($this->any())->method('getUser') $this->mockAccount($targetUser, [
->with($targetUser) IAccountManager::PROPERTY_ADDRESS => ['value' => 'address'],
->willReturn( IAccountManager::PROPERTY_PHONE => ['value' => 'phone'],
[ IAccountManager::PROPERTY_TWITTER => ['value' => 'twitter'],
IAccountManager::PROPERTY_ADDRESS => ['value' => 'address'], IAccountManager::PROPERTY_WEBSITE => ['value' => 'website'],
IAccountManager::PROPERTY_PHONE => ['value' => 'phone'], ]);
IAccountManager::PROPERTY_TWITTER => ['value' => 'twitter'],
IAccountManager::PROPERTY_WEBSITE => ['value' => 'website'],
]
);
$this->l10nFactory $this->l10nFactory
->expects($this->once()) ->expects($this->once())
@ -1538,6 +1521,91 @@ class UsersControllerTest extends TestCase {
$this->api->editUser('UserToEdit', 'email', 'demo.org'); $this->api->editUser('UserToEdit', 'email', 'demo.org');
} }
public function selfEditChangePropertyProvider() {
return [
[IAccountManager::PROPERTY_TWITTER, '@oldtwitter', '@newtwitter'],
[IAccountManager::PROPERTY_PHONE, '1234', '12345'],
[IAccountManager::PROPERTY_ADDRESS, 'Something street 2', 'Another street 3'],
[IAccountManager::PROPERTY_WEBSITE, 'https://examplesite1', 'https://examplesite2'],
];
}
/**
* @dataProvider selfEditChangePropertyProvider
*/
public function testEditUserRegularUserSelfEditChangeProperty($propertyName, $oldValue, $newValue) {
$loggedInUser = $this->getMockBuilder(IUser::class)
->disableOriginalConstructor()
->getMock();
$loggedInUser
->expects($this->any())
->method('getUID')
->willReturn('UID');
$this->userSession
->expects($this->once())
->method('getUser')
->willReturn($loggedInUser);
$this->userManager
->expects($this->once())
->method('get')
->with('UserToEdit')
->willReturn($loggedInUser);
$this->accountManager->expects($this->once())
->method('getUser')
->with($loggedInUser)
->willReturn([$propertyName => ['value' => $oldValue, 'scope' => IAccountManager::SCOPE_LOCAL]]);
$this->accountManager->expects($this->once())
->method('updateUser')
->with($loggedInUser, [$propertyName => ['value' => $newValue, 'scope' => IAccountManager::SCOPE_LOCAL]], true);
$this->assertEquals([], $this->api->editUser('UserToEdit', $propertyName, $newValue)->getData());
}
public function selfEditChangePropertyScopeProvider() {
return [
[IAccountManager::PROPERTY_AVATAR, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED],
[IAccountManager::PROPERTY_DISPLAYNAME, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED],
[IAccountManager::PROPERTY_EMAIL, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED],
[IAccountManager::PROPERTY_TWITTER, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED],
[IAccountManager::PROPERTY_PHONE, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED],
[IAccountManager::PROPERTY_ADDRESS, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED],
[IAccountManager::PROPERTY_WEBSITE, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED],
];
}
/**
* @dataProvider selfEditChangePropertyProvider
*/
public function testEditUserRegularUserSelfEditChangePropertyScope($propertyName, $oldScope, $newScope) {
$loggedInUser = $this->getMockBuilder(IUser::class)
->disableOriginalConstructor()
->getMock();
$loggedInUser
->expects($this->any())
->method('getUID')
->willReturn('UID');
$this->userSession
->expects($this->once())
->method('getUser')
->willReturn($loggedInUser);
$this->userManager
->expects($this->once())
->method('get')
->with('UserToEdit')
->willReturn($loggedInUser);
$this->accountManager->expects($this->once())
->method('getUser')
->with($loggedInUser)
->willReturn([$propertyName => ['value' => 'somevalue', 'scope' => $oldScope]]);
$this->accountManager->expects($this->once())
->method('updateUser')
->with($loggedInUser, [$propertyName => ['value' => 'somevalue', 'scope' => $newScope]], true);
$this->assertEquals([], $this->api->editUser('UserToEdit', $propertyName . 'Scope', $newScope)->getData());
}
public function testEditUserRegularUserSelfEditChangePassword() { public function testEditUserRegularUserSelfEditChangePassword() {
$loggedInUser = $this->getMockBuilder(IUser::class) $loggedInUser = $this->getMockBuilder(IUser::class)
->disableOriginalConstructor() ->disableOriginalConstructor()
@ -3247,7 +3315,6 @@ class UsersControllerTest extends TestCase {
$this->logger, $this->logger,
$this->l10nFactory, $this->l10nFactory,
$this->newUserMailHelper, $this->newUserMailHelper,
$this->federatedShareProviderFactory,
$this->secureRandom, $this->secureRandom,
$this->remoteWipe, $this->remoteWipe,
$this->knownUserService, $this->knownUserService,
@ -3256,7 +3323,7 @@ class UsersControllerTest extends TestCase {
->setMethods(['getUserData']) ->setMethods(['getUserData'])
->getMock(); ->getMock();
$api->expects($this->once())->method('getUserData')->with('UID') $api->expects($this->once())->method('getUserData')->with('UID', true)
->willReturn( ->willReturn(
[ [
'id' => 'UID', 'id' => 'UID',
@ -3297,8 +3364,15 @@ class UsersControllerTest extends TestCase {
$this->api->getCurrentUser(); $this->api->getCurrentUser();
} }
public function testGetUser() { public function testGetUser() {
$loggedInUser = $this->createMock(IUser::class);
$loggedInUser
->method('getUID')
->willReturn('currentuser');
$this->userSession
->method('getUser')
->willReturn($loggedInUser);
/** @var UsersController | MockObject $api */ /** @var UsersController | MockObject $api */
$api = $this->getMockBuilder(UsersController::class) $api = $this->getMockBuilder(UsersController::class)
->setConstructorArgs([ ->setConstructorArgs([
@ -3314,7 +3388,6 @@ class UsersControllerTest extends TestCase {
$this->logger, $this->logger,
$this->l10nFactory, $this->l10nFactory,
$this->newUserMailHelper, $this->newUserMailHelper,
$this->federatedShareProviderFactory,
$this->secureRandom, $this->secureRandom,
$this->remoteWipe, $this->remoteWipe,
$this->knownUserService, $this->knownUserService,
@ -3335,11 +3408,16 @@ class UsersControllerTest extends TestCase {
'displayname' => 'Demo User' 'displayname' => 'Demo User'
]; ];
$api->expects($this->once())->method('getUserData') $api->expects($this->at(0))->method('getUserData')
->with('uid') ->with('uid', false)
->willReturn($expected);
$api->expects($this->at(1))->method('getUserData')
->with('currentuser', true)
->willReturn($expected); ->willReturn($expected);
$this->assertSame($expected, $api->getUser('uid')->getData()); $this->assertSame($expected, $api->getUser('uid')->getData());
$this->assertSame($expected, $api->getUser('currentuser')->getData());
} }
@ -3639,18 +3717,13 @@ class UsersControllerTest extends TestCase {
public function dataGetEditableFields() { public function dataGetEditableFields() {
return [ return [
[false, false, []], [false, [
[false, true, [
IAccountManager::PROPERTY_PHONE, IAccountManager::PROPERTY_PHONE,
IAccountManager::PROPERTY_ADDRESS, IAccountManager::PROPERTY_ADDRESS,
IAccountManager::PROPERTY_WEBSITE, IAccountManager::PROPERTY_WEBSITE,
IAccountManager::PROPERTY_TWITTER, IAccountManager::PROPERTY_TWITTER,
]], ]],
[ true, false, [ [ true, [
IAccountManager::PROPERTY_DISPLAYNAME,
IAccountManager::PROPERTY_EMAIL,
]],
[ true, true ,[
IAccountManager::PROPERTY_DISPLAYNAME, IAccountManager::PROPERTY_DISPLAYNAME,
IAccountManager::PROPERTY_EMAIL, IAccountManager::PROPERTY_EMAIL,
IAccountManager::PROPERTY_PHONE, IAccountManager::PROPERTY_PHONE,
@ -3665,29 +3738,36 @@ class UsersControllerTest extends TestCase {
* @dataProvider dataGetEditableFields * @dataProvider dataGetEditableFields
* *
* @param bool $allowedToChangeDisplayName * @param bool $allowedToChangeDisplayName
* @param bool $federatedSharingEnabled
* @param array $expected * @param array $expected
*/ */
public function testGetEditableFields(bool $allowedToChangeDisplayName, bool $federatedSharingEnabled, array $expected) { public function testGetEditableFields(bool $allowedToChangeDisplayName, array $expected) {
$this->config $this->config
->method('getSystemValue') ->method('getSystemValue')
->with( ->with(
$this->equalTo('allow_user_to_change_display_name'), $this->equalTo('allow_user_to_change_display_name'),
$this->anything() $this->anything()
)->willReturn($allowedToChangeDisplayName); )->willReturn($allowedToChangeDisplayName);
$this->appManager
->method('isEnabledForUser')
->with($this->equalTo('federatedfilesharing'))
->willReturn($federatedSharingEnabled);
$shareprovider = $this->createMock(FederatedShareProvider::class);
$shareprovider->method('isLookupServerUploadEnabled')->willReturn(true);
$this->federatedShareProviderFactory
->method('get')
->willReturn($shareprovider);
$expectedResp = new DataResponse($expected); $expectedResp = new DataResponse($expected);
$this->assertEquals($expectedResp, $this->api->getEditableFields()); $this->assertEquals($expectedResp, $this->api->getEditableFields());
} }
private function mockAccount($targetUser, $accountProperties) {
$mockedProperties = [];
foreach ($accountProperties as $propertyName => $data) {
$mockedProperty = $this->createMock(IAccountProperty::class);
$mockedProperty->method('getValue')->willReturn($data['value'] ?? '');
$mockedProperty->method('getScope')->willReturn($data['scope'] ?? '');
$mockedProperties[] = [$propertyName, $mockedProperty];
}
$account = $this->createMock(IAccount::class);
$account->method('getProperty')
->will($this->returnValueMap($mockedProperties));
$this->accountManager->expects($this->any())->method('getAccount')
->with($targetUser)
->willReturn($account);
}
} }

View File

@ -15,6 +15,8 @@
* Construct a new FederationScopeMenu instance * Construct a new FederationScopeMenu instance
* @constructs FederationScopeMenu * @constructs FederationScopeMenu
* @memberof OC.Settings * @memberof OC.Settings
* @param {object} options
* @param {array.<string>} [options.excludedScopes] array of excluded scopes
*/ */
var FederationScopeMenu = OC.Backbone.View.extend({ var FederationScopeMenu = OC.Backbone.View.extend({
tagName: 'div', tagName: 'div',
@ -26,27 +28,40 @@
this.field = options.field; this.field = options.field;
this._scopes = [ this._scopes = [
{ {
name: 'private', name: 'v2-private',
displayName: t('settings', 'Private'), displayName: t('settings', 'Private'),
tooltip: t('settings', "Don't show via public link"),
iconClass: 'icon-password',
active: false
},
{
name: 'v2-local',
displayName: t('settings', 'Local'),
tooltip: t('settings', "Don't synchronize to servers"), tooltip: t('settings', "Don't synchronize to servers"),
iconClass: 'icon-password', iconClass: 'icon-password',
active: false active: false
}, },
{ {
name: 'contacts', name: 'v2-federated',
displayName: t('settings', 'Trusted'), displayName: t('settings', 'Federated'),
tooltip: t('settings', 'Only synchronize to trusted servers'), tooltip: t('settings', 'Only synchronize to trusted servers'),
iconClass: 'icon-contacts-dark', iconClass: 'icon-contacts-dark',
active: false active: false
}, },
{ {
name: 'public', name: 'v2-published',
displayName: t('settings', 'Public'), displayName: t('settings', 'Published'),
tooltip: t('settings', 'Synchronize to trusted servers and the global and public address book'), tooltip: t('settings', 'Synchronize to trusted servers and the global and public address book'),
iconClass: 'icon-link', iconClass: 'icon-link',
active: false active: false
} }
]; ];
if (options.excludedScopes && options.excludedScopes.length) {
this._scopes = this._scopes.filter(function(scopeEntry) {
return options.excludedScopes.indexOf(scopeEntry.name) === -1;
})
}
}, },
/** /**
@ -102,19 +117,11 @@
var currentlyActiveValue = $('#'+context.target.closest('form').id).find('input[type="hidden"]')[0].value; var currentlyActiveValue = $('#'+context.target.closest('form').id).find('input[type="hidden"]')[0].value;
for(var i in this._scopes) { for(var i in this._scopes) {
this._scopes[i].active = false; if (this._scopes[i].name === currentlyActiveValue) {
} this._scopes[i].active = true;
} else {
switch (currentlyActiveValue) { this._scopes[i].active = false;
case 'private': }
this._scopes[0].active = true;
break;
case 'contacts':
this._scopes[1].active = true;
break;
case 'public':
this._scopes[2].active = true;
break;
} }
this.render(); this.render();

View File

@ -10,6 +10,13 @@
(function(_, $, OC) { (function(_, $, OC) {
'use strict'; 'use strict';
/**
* Construct a new FederationScopeMenu instance
* @constructs FederationScopeMenu
* @memberof OC.Settings
* @param {object} options
* @param {bool} [options.lookupServerUploadEnabled=false] whether uploading to the lookup server is enabled
*/
var FederationSettingsView = OC.Backbone.View.extend({ var FederationSettingsView = OC.Backbone.View.extend({
_inputFields: undefined, _inputFields: undefined,
@ -24,6 +31,7 @@
} else { } else {
this._config = new OC.Settings.UserSettings(); this._config = new OC.Settings.UserSettings();
} }
this.showFederationScopes = !!options.showFederationScopes;
this._inputFields = [ this._inputFields = [
'displayname', 'displayname',
@ -61,9 +69,31 @@
render: function() { render: function() {
var self = this; var self = this;
var fieldsWithV2Private = [
'avatar',
'phone',
'twitter',
'website',
'address',
];
_.each(this._inputFields, function(field) { _.each(this._inputFields, function(field) {
var $icon = self.$('#' + field + 'form h3 > .federation-menu'); var $icon = self.$('#' + field + 'form h3 > .federation-menu');
var scopeMenu = new OC.Settings.FederationScopeMenu({field: field}); var excludedScopes = []
if (fieldsWithV2Private.indexOf(field) === -1) {
excludedScopes.push('v2-private');
}
if (!self.showFederationScopes) {
excludedScopes.push('v2-federated');
excludedScopes.push('v2-published');
}
var scopeMenu = new OC.Settings.FederationScopeMenu({
field: field,
excludedScopes: excludedScopes,
});
self.listenTo(scopeMenu, 'select:scope', function(scope) { self.listenTo(scopeMenu, 'select:scope', function(scope) {
self._onScopeChanged(field, scope); self._onScopeChanged(field, scope);
@ -207,15 +237,16 @@
$icon.addClass('hidden'); $icon.addClass('hidden');
switch (scope) { switch (scope) {
case 'private': case 'v2-private':
case 'v2-local':
$icon.addClass('icon-password'); $icon.addClass('icon-password');
$icon.removeClass('hidden'); $icon.removeClass('hidden');
break; break;
case 'contacts': case 'v2-federated':
$icon.addClass('icon-contacts-dark'); $icon.addClass('icon-contacts-dark');
$icon.removeClass('hidden'); $icon.removeClass('hidden');
break; break;
case 'public': case 'v2-published':
$icon.addClass('icon-link'); $icon.addClass('icon-link');
$icon.removeClass('hidden'); $icon.removeClass('hidden');
break; break;

View File

@ -199,10 +199,12 @@ window.addEventListener('DOMContentLoaded', function () {
}); });
var settingsEl = $('#personal-settings')
var userSettings = new OC.Settings.UserSettings(); var userSettings = new OC.Settings.UserSettings();
var federationSettingsView = new OC.Settings.FederationSettingsView({ var federationSettingsView = new OC.Settings.FederationSettingsView({
el: '#personal-settings', el: settingsEl,
config: userSettings config: userSettings,
showFederationScopes: !!settingsEl.data('lookup-server-upload-enabled'),
}); });
userSettings.on("sync", function() { userSettings.on("sync", function() {

View File

@ -46,7 +46,6 @@ use OC\KnownUser\KnownUserService;
use OC\L10N\Factory; use OC\L10N\Factory;
use OC\Security\IdentityProof\Manager; use OC\Security\IdentityProof\Manager;
use OC\User\Manager as UserManager; use OC\User\Manager as UserManager;
use OCA\FederatedFileSharing\FederatedShareProvider;
use OCA\Settings\BackgroundJobs\VerifyUserData; use OCA\Settings\BackgroundJobs\VerifyUserData;
use OCA\Settings\Events\BeforeTemplateRenderedEvent; use OCA\Settings\Events\BeforeTemplateRenderedEvent;
use OCA\User_LDAP\User_Proxy; use OCA\User_LDAP\User_Proxy;
@ -401,15 +400,11 @@ class UsersController extends Controller {
$data[IAccountManager::PROPERTY_DISPLAYNAME] = ['value' => $displayname, 'scope' => $displaynameScope]; $data[IAccountManager::PROPERTY_DISPLAYNAME] = ['value' => $displayname, 'scope' => $displaynameScope];
$data[IAccountManager::PROPERTY_EMAIL] = ['value' => $email, 'scope' => $emailScope]; $data[IAccountManager::PROPERTY_EMAIL] = ['value' => $email, 'scope' => $emailScope];
} }
if ($this->appManager->isEnabledForUser('federatedfilesharing')) { $data[IAccountManager::PROPERTY_WEBSITE] = ['value' => $website, 'scope' => $websiteScope];
$shareProvider = \OC::$server->query(FederatedShareProvider::class); $data[IAccountManager::PROPERTY_ADDRESS] = ['value' => $address, 'scope' => $addressScope];
if ($shareProvider->isLookupServerUploadEnabled()) { $data[IAccountManager::PROPERTY_PHONE] = ['value' => $phone, 'scope' => $phoneScope];
$data[IAccountManager::PROPERTY_WEBSITE] = ['value' => $website, 'scope' => $websiteScope]; $data[IAccountManager::PROPERTY_TWITTER] = ['value' => $twitter, 'scope' => $twitterScope];
$data[IAccountManager::PROPERTY_ADDRESS] = ['value' => $address, 'scope' => $addressScope];
$data[IAccountManager::PROPERTY_PHONE] = ['value' => $phone, 'scope' => $phoneScope];
$data[IAccountManager::PROPERTY_TWITTER] = ['value' => $twitter, 'scope' => $twitterScope];
}
}
try { try {
$data = $this->saveUserSettings($user, $data); $data = $this->saveUserSettings($user, $data);
if ($beforeData[IAccountManager::PROPERTY_PHONE]['value'] !== $data[IAccountManager::PROPERTY_PHONE]['value']) { if ($beforeData[IAccountManager::PROPERTY_PHONE]['value'] !== $data[IAccountManager::PROPERTY_PHONE]['value']) {

View File

@ -37,6 +37,7 @@ namespace OCA\Settings\Settings\Personal;
use OC\Accounts\AccountManager; use OC\Accounts\AccountManager;
use OCA\FederatedFileSharing\FederatedShareProvider; use OCA\FederatedFileSharing\FederatedShareProvider;
use OCP\Accounts\IAccount;
use OCP\Accounts\IAccountManager; use OCP\Accounts\IAccountManager;
use OCP\App\IAppManager; use OCP\App\IAppManager;
use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http\TemplateResponse;
@ -96,7 +97,7 @@ class PersonalInfo implements ISettings {
$uid = \OC_User::getUser(); $uid = \OC_User::getUser();
$user = $this->userManager->get($uid); $user = $this->userManager->get($uid);
$userData = $this->accountManager->getUser($user); $account = $this->accountManager->getAccount($user);
// make sure FS is setup before querying storage related stuff... // make sure FS is setup before querying storage related stuff...
\OC_Util::setupFS($user->getUID()); \OC_Util::setupFS($user->getUID());
@ -110,7 +111,7 @@ class PersonalInfo implements ISettings {
$languageParameters = $this->getLanguages($user); $languageParameters = $this->getLanguages($user);
$localeParameters = $this->getLocales($user); $localeParameters = $this->getLocales($user);
$messageParameters = $this->getMessageParameters($userData); $messageParameters = $this->getMessageParameters($account);
$parameters = [ $parameters = [
'total_space' => $totalSpace, 'total_space' => $totalSpace,
@ -119,23 +120,23 @@ class PersonalInfo implements ISettings {
'quota' => $storageInfo['quota'], 'quota' => $storageInfo['quota'],
'avatarChangeSupported' => $user->canChangeAvatar(), 'avatarChangeSupported' => $user->canChangeAvatar(),
'lookupServerUploadEnabled' => $lookupServerUploadEnabled, 'lookupServerUploadEnabled' => $lookupServerUploadEnabled,
'avatarScope' => $userData[IAccountManager::PROPERTY_AVATAR]['scope'], 'avatarScope' => $account->getProperty(IAccountManager::PROPERTY_AVATAR)->getScope(),
'displayNameChangeSupported' => $user->canChangeDisplayName(), 'displayNameChangeSupported' => $user->canChangeDisplayName(),
'displayName' => $userData[IAccountManager::PROPERTY_DISPLAYNAME]['value'], 'displayName' => $account->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue(),
'displayNameScope' => $userData[IAccountManager::PROPERTY_DISPLAYNAME]['scope'], 'displayNameScope' => $account->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getScope(),
'email' => $userData[IAccountManager::PROPERTY_EMAIL]['value'], 'email' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue(),
'emailScope' => $userData[IAccountManager::PROPERTY_EMAIL]['scope'], 'emailScope' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getScope(),
'emailVerification' => $userData[IAccountManager::PROPERTY_EMAIL]['verified'], 'emailVerification' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getVerified(),
'phone' => $userData[IAccountManager::PROPERTY_PHONE]['value'], 'phone' => $account->getProperty(IAccountManager::PROPERTY_PHONE)->getValue(),
'phoneScope' => $userData[IAccountManager::PROPERTY_PHONE]['scope'], 'phoneScope' => $account->getProperty(IAccountManager::PROPERTY_PHONE)->getScope(),
'address' => $userData[IAccountManager::PROPERTY_ADDRESS]['value'], 'address' => $account->getProperty(IAccountManager::PROPERTY_ADDRESS)->getValue(),
'addressScope' => $userData[IAccountManager::PROPERTY_ADDRESS]['scope'], 'addressScope' => $account->getProperty(IAccountManager::PROPERTY_ADDRESS)->getScope(),
'website' => $userData[IAccountManager::PROPERTY_WEBSITE]['value'], 'website' => $account->getProperty(IAccountManager::PROPERTY_WEBSITE)->getValue(),
'websiteScope' => $userData[IAccountManager::PROPERTY_WEBSITE]['scope'], 'websiteScope' => $account->getProperty(IAccountManager::PROPERTY_WEBSITE)->getScope(),
'websiteVerification' => $userData[IAccountManager::PROPERTY_WEBSITE]['verified'], 'websiteVerification' => $account->getProperty(IAccountManager::PROPERTY_WEBSITE)->getVerified(),
'twitter' => $userData[IAccountManager::PROPERTY_TWITTER]['value'], 'twitter' => $account->getProperty(IAccountManager::PROPERTY_TWITTER)->getValue(),
'twitterScope' => $userData[IAccountManager::PROPERTY_TWITTER]['scope'], 'twitterScope' => $account->getProperty(IAccountManager::PROPERTY_TWITTER)->getScope(),
'twitterVerification' => $userData[IAccountManager::PROPERTY_TWITTER]['verified'], 'twitterVerification' => $account->getProperty(IAccountManager::PROPERTY_TWITTER)->getVerified(),
'groups' => $this->getGroups($user), 'groups' => $this->getGroups($user),
] + $messageParameters + $languageParameters + $localeParameters; ] + $messageParameters + $languageParameters + $localeParameters;
@ -263,14 +264,14 @@ class PersonalInfo implements ISettings {
} }
/** /**
* @param array $userData * @param IAccount $account
* @return array * @return array
*/ */
private function getMessageParameters(array $userData): array { private function getMessageParameters(IAccount $account): array {
$needVerifyMessage = [IAccountManager::PROPERTY_EMAIL, IAccountManager::PROPERTY_WEBSITE, IAccountManager::PROPERTY_TWITTER]; $needVerifyMessage = [IAccountManager::PROPERTY_EMAIL, IAccountManager::PROPERTY_WEBSITE, IAccountManager::PROPERTY_TWITTER];
$messageParameters = []; $messageParameters = [];
foreach ($needVerifyMessage as $property) { foreach ($needVerifyMessage as $property) {
switch ($userData[$property]['verified']) { switch ($account->getProperty($property)->getVerified()) {
case AccountManager::VERIFIED: case AccountManager::VERIFIED:
$message = $this->l->t('Verifying'); $message = $this->l->t('Verifying');
break; break;

View File

@ -34,8 +34,8 @@ script('settings', [
]); ]);
?> ?>
<div id="personal-settings"> <div id="personal-settings" data-lookup-server-upload-enabled="<?php p($_['lookupServerUploadEnabled'] ? 'true' : 'false') ?>">
<div id="personal-settings-avatar-container" class="personal-settings-container"> <div id="personal-settings-avatar-container" class="personal-settings-container">
<div> <div>
<form id="avatarform" class="section" method="post" action="<?php p(\OC::$server->getURLGenerator()->linkToRoute('core.avatar.postAvatar')); ?>"> <form id="avatarform" class="section" method="post" action="<?php p(\OC::$server->getURLGenerator()->linkToRoute('core.avatar.postAvatar')); ?>">
<h3> <h3>
@ -68,9 +68,7 @@ script('settings', [
</div> </div>
<span class="icon-checkmark hidden"></span> <span class="icon-checkmark hidden"></span>
<span class="icon-error hidden" ></span> <span class="icon-error hidden" ></span>
<?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="avatarscope" value="<?php p($_['avatarScope']) ?>"> <input type="hidden" id="avatarscope" value="<?php p($_['avatarScope']) ?>">
<?php } ?>
</form> </form>
</div> </div>
<div class="personal-settings-setting-box personal-settings-group-box section"> <div class="personal-settings-setting-box personal-settings-group-box section">
@ -125,7 +123,7 @@ script('settings', [
<span class="icon-checkmark hidden"></span> <span class="icon-checkmark hidden"></span>
<span class="icon-error hidden" ></span> <span class="icon-error hidden" ></span>
<?php if ($_['lookupServerUploadEnabled']) { ?> <?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="displaynamescope" value="<?php p($_['displayNameScope']) ?>"> <input type="hidden" id="displaynamescope" value="<?php p($_['displayNameScope']) ?>">
<?php } ?> <?php } ?>
</form> </form>
</div> </div>
@ -179,7 +177,6 @@ script('settings', [
<?php } ?> <?php } ?>
</form> </form>
</div> </div>
<?php if (!empty($_['phone']) || $_['lookupServerUploadEnabled']) { ?>
<div class="personal-settings-setting-box"> <div class="personal-settings-setting-box">
<form id="phoneform" class="section"> <form id="phoneform" class="section">
<h3> <h3>
@ -190,21 +187,15 @@ script('settings', [
</span> </span>
</div> </div>
</h3> </h3>
<input type="tel" id="phone" name="phone" <?php if (!$_['lookupServerUploadEnabled']) { <input type="tel" id="phone" name="phone"
print_unescaped('disabled="1"');
} ?>
value="<?php p($_['phone']) ?>" value="<?php p($_['phone']) ?>"
placeholder="<?php p($l->t('Your phone number')); ?>" placeholder="<?php p($l->t('Your phone number')); ?>"
autocomplete="on" autocapitalize="none" autocorrect="off" /> autocomplete="on" autocapitalize="none" autocorrect="off" />
<span class="icon-checkmark hidden"></span> <span class="icon-checkmark hidden"></span>
<span class="icon-error hidden" ></span> <span class="icon-error hidden" ></span>
<?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="phonescope" value="<?php p($_['phoneScope']) ?>"> <input type="hidden" id="phonescope" value="<?php p($_['phoneScope']) ?>">
<?php } ?>
</form> </form>
</div> </div>
<?php } ?>
<?php if (!empty($_['address']) || $_['lookupServerUploadEnabled']) { ?>
<div class="personal-settings-setting-box"> <div class="personal-settings-setting-box">
<form id="addressform" class="section"> <form id="addressform" class="section">
<h3> <h3>
@ -215,21 +206,15 @@ script('settings', [
</span> </span>
</div> </div>
</h3> </h3>
<input type="text" id="address" name="address" <?php if (!$_['lookupServerUploadEnabled']) { <input type="text" id="address" name="address"
print_unescaped('disabled="1"');
} ?>
placeholder="<?php p($l->t('Your postal address')); ?>" placeholder="<?php p($l->t('Your postal address')); ?>"
value="<?php p($_['address']) ?>" value="<?php p($_['address']) ?>"
autocomplete="on" autocapitalize="none" autocorrect="off" /> autocomplete="on" autocapitalize="none" autocorrect="off" />
<span class="icon-checkmark hidden"></span> <span class="icon-checkmark hidden"></span>
<span class="icon-error hidden" ></span> <span class="icon-error hidden" ></span>
<?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="addressscope" value="<?php p($_['addressScope']) ?>"> <input type="hidden" id="addressscope" value="<?php p($_['addressScope']) ?>">
<?php } ?>
</form> </form>
</div> </div>
<?php } ?>
<?php if (!empty($_['website']) || $_['lookupServerUploadEnabled']) { ?>
<div class="personal-settings-setting-box"> <div class="personal-settings-setting-box">
<form id="websiteform" class="section"> <form id="websiteform" class="section">
<h3> <h3>
@ -273,19 +258,12 @@ script('settings', [
<input type="url" name="website" id="website" value="<?php p($_['website']); ?>" <input type="url" name="website" id="website" value="<?php p($_['website']); ?>"
placeholder="<?php p($l->t('Link https://…')); ?>" placeholder="<?php p($l->t('Link https://…')); ?>"
autocomplete="on" autocapitalize="none" autocorrect="off" autocomplete="on" autocapitalize="none" autocorrect="off"
<?php if (!$_['lookupServerUploadEnabled']) {
print_unescaped('disabled="1"');
} ?>
/> />
<span class="icon-checkmark hidden"></span> <span class="icon-checkmark hidden"></span>
<span class="icon-error hidden" ></span> <span class="icon-error hidden" ></span>
<?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="websitescope" value="<?php p($_['websiteScope']) ?>"> <input type="hidden" id="websitescope" value="<?php p($_['websiteScope']) ?>">
<?php } ?>
</form> </form>
</div> </div>
<?php } ?>
<?php if (!empty($_['twitter']) || $_['lookupServerUploadEnabled']) { ?>
<div class="personal-settings-setting-box"> <div class="personal-settings-setting-box">
<form id="twitterform" class="section"> <form id="twitterform" class="section">
<h3> <h3>
@ -329,18 +307,12 @@ script('settings', [
<input type="text" name="twitter" id="twitter" value="<?php p($_['twitter']); ?>" <input type="text" name="twitter" id="twitter" value="<?php p($_['twitter']); ?>"
placeholder="<?php p($l->t('Twitter handle @…')); ?>" placeholder="<?php p($l->t('Twitter handle @…')); ?>"
autocomplete="on" autocapitalize="none" autocorrect="off" autocomplete="on" autocapitalize="none" autocorrect="off"
<?php if (!$_['lookupServerUploadEnabled']) {
print_unescaped('disabled="1"');
} ?>
/> />
<span class="icon-checkmark hidden"></span> <span class="icon-checkmark hidden"></span>
<span class="icon-error hidden" ></span> <span class="icon-error hidden" ></span>
<?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="twitterscope" value="<?php p($_['twitterScope']) ?>"> <input type="hidden" id="twitterscope" value="<?php p($_['twitterScope']) ?>">
<?php } ?>
</form> </form>
</div> </div>
<?php } ?>
</div> </div>
<div class="profile-settings-container"> <div class="profile-settings-container">

View File

@ -190,6 +190,7 @@ class UsersControllerTest extends \Test\TestCase {
public function testSetUserSettings($email, $validEmail, $expectedStatus) { public function testSetUserSettings($email, $validEmail, $expectedStatus) {
$controller = $this->getController(false, ['saveUserSettings']); $controller = $this->getController(false, ['saveUserSettings']);
$user = $this->createMock(IUser::class); $user = $this->createMock(IUser::class);
$user->method('getUID')->willReturn('johndoe');
$this->userSession->method('getUser')->willReturn($user); $this->userSession->method('getUser')->willReturn($user);
@ -208,41 +209,41 @@ class UsersControllerTest extends \Test\TestCase {
IAccountManager::PROPERTY_DISPLAYNAME => IAccountManager::PROPERTY_DISPLAYNAME =>
[ [
'value' => 'Display name', 'value' => 'Display name',
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY, 'scope' => AccountManager::SCOPE_FEDERATED,
'verified' => AccountManager::NOT_VERIFIED, 'verified' => AccountManager::NOT_VERIFIED,
], ],
IAccountManager::PROPERTY_ADDRESS => IAccountManager::PROPERTY_ADDRESS =>
[ [
'value' => '', 'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE, 'scope' => AccountManager::SCOPE_LOCAL,
'verified' => AccountManager::NOT_VERIFIED, 'verified' => AccountManager::NOT_VERIFIED,
], ],
IAccountManager::PROPERTY_WEBSITE => IAccountManager::PROPERTY_WEBSITE =>
[ [
'value' => '', 'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE, 'scope' => AccountManager::SCOPE_LOCAL,
'verified' => AccountManager::NOT_VERIFIED, 'verified' => AccountManager::NOT_VERIFIED,
], ],
IAccountManager::PROPERTY_EMAIL => IAccountManager::PROPERTY_EMAIL =>
[ [
'value' => '', 'value' => '',
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY, 'scope' => AccountManager::SCOPE_FEDERATED,
'verified' => AccountManager::NOT_VERIFIED, 'verified' => AccountManager::NOT_VERIFIED,
], ],
IAccountManager::PROPERTY_AVATAR => IAccountManager::PROPERTY_AVATAR =>
[ [
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY 'scope' => AccountManager::SCOPE_FEDERATED
], ],
IAccountManager::PROPERTY_PHONE => IAccountManager::PROPERTY_PHONE =>
[ [
'value' => '', 'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE, 'scope' => AccountManager::SCOPE_LOCAL,
'verified' => AccountManager::NOT_VERIFIED, 'verified' => AccountManager::NOT_VERIFIED,
], ],
IAccountManager::PROPERTY_TWITTER => IAccountManager::PROPERTY_TWITTER =>
[ [
'value' => '', 'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE, 'scope' => AccountManager::SCOPE_LOCAL,
'verified' => AccountManager::NOT_VERIFIED, 'verified' => AccountManager::NOT_VERIFIED,
], ],
]); ]);
@ -255,19 +256,19 @@ class UsersControllerTest extends \Test\TestCase {
} }
$result = $controller->setUserSettings(// $result = $controller->setUserSettings(//
AccountManager::VISIBILITY_CONTACTS_ONLY, AccountManager::SCOPE_FEDERATED,
'displayName', 'displayName',
AccountManager::VISIBILITY_CONTACTS_ONLY, AccountManager::SCOPE_FEDERATED,
'47658468', '47658468',
AccountManager::VISIBILITY_CONTACTS_ONLY, AccountManager::SCOPE_FEDERATED,
$email, $email,
AccountManager::VISIBILITY_CONTACTS_ONLY, AccountManager::SCOPE_FEDERATED,
'nextcloud.com', 'nextcloud.com',
AccountManager::VISIBILITY_CONTACTS_ONLY, AccountManager::SCOPE_FEDERATED,
'street and city', 'street and city',
AccountManager::VISIBILITY_CONTACTS_ONLY, AccountManager::SCOPE_FEDERATED,
'@nextclouders', '@nextclouders',
AccountManager::VISIBILITY_CONTACTS_ONLY AccountManager::SCOPE_FEDERATED
); );
$this->assertSame($expectedStatus, $result->getStatus()); $this->assertSame($expectedStatus, $result->getStatus());

View File

@ -157,7 +157,11 @@ trait Provisioning {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/users/$user"; $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/users/$user";
$client = new Client(); $client = new Client();
$options = []; $options = [];
$options['auth'] = $this->adminUser; if ($this->currentUser === 'admin') {
$options['auth'] = $this->adminUser;
} else {
$options['auth'] = [$this->currentUser, $this->regularUser];
}
$options['headers'] = [ $options['headers'] = [
'OCS-APIREQUEST' => 'true', 'OCS-APIREQUEST' => 'true',
]; ];

View File

@ -103,6 +103,82 @@ Feature: provisioning
| website | https://nextcloud.com | | website | https://nextcloud.com |
| twitter | Nextcloud | | twitter | Nextcloud |
Scenario: Edit a user account properties scopes
Given user "brand-new-user" exists
And As an "brand-new-user"
When sending "PUT" to "/cloud/users/brand-new-user" with
| key | phoneScope |
| value | v2-private |
Then the OCS status code should be "100"
And the HTTP status code should be "200"
When sending "PUT" to "/cloud/users/brand-new-user" with
| key | twitterScope |
| value | v2-local |
Then the OCS status code should be "100"
And the HTTP status code should be "200"
When sending "PUT" to "/cloud/users/brand-new-user" with
| key | addressScope |
| value | v2-federated |
Then the OCS status code should be "100"
And the HTTP status code should be "200"
When sending "PUT" to "/cloud/users/brand-new-user" with
| key | emailScope |
| value | v2-published |
Then the OCS status code should be "100"
And the HTTP status code should be "200"
When sending "PUT" to "/cloud/users/brand-new-user" with
| key | websiteScope |
| value | public |
Then the OCS status code should be "100"
And the HTTP status code should be "200"
When sending "PUT" to "/cloud/users/brand-new-user" with
| key | displaynameScope |
| value | contacts |
Then the OCS status code should be "100"
And the HTTP status code should be "200"
When sending "PUT" to "/cloud/users/brand-new-user" with
| key | avatarScope |
| value | private |
Then the OCS status code should be "100"
And the HTTP status code should be "200"
Then user "brand-new-user" has
| id | brand-new-user |
| phoneScope | v2-private |
| twitterScope | v2-local |
| addressScope | v2-federated |
| emailScope | v2-published |
| websiteScope | v2-published |
| displaynameScope | v2-federated |
| avatarScope | v2-local |
Scenario: Edit a user account properties scopes with invalid or unsupported value
Given user "brand-new-user" exists
And As an "brand-new-user"
When sending "PUT" to "/cloud/users/brand-new-user" with
| key | phoneScope |
| value | invalid |
Then the OCS status code should be "102"
And the HTTP status code should be "200"
When sending "PUT" to "/cloud/users/brand-new-user" with
| key | displaynameScope |
| value | v2-private |
Then the OCS status code should be "102"
And the HTTP status code should be "200"
When sending "PUT" to "/cloud/users/brand-new-user" with
| key | emailScope |
| value | v2-private |
Then the OCS status code should be "102"
And the HTTP status code should be "200"
Scenario: An admin cannot edit user account property scopes
Given As an "admin"
And user "brand-new-user" exists
When sending "PUT" to "/cloud/users/brand-new-user" with
| key | phoneScope |
| value | v2-private |
Then the OCS status code should be "997"
And the HTTP status code should be "401"
Scenario: Search by phone number Scenario: Search by phone number
Given As an "admin" Given As an "admin"
And user "phone-user" exists And user "phone-user" exists
@ -612,4 +688,3 @@ Feature: provisioning
And As an "user0" And As an "user0"
When sending "GET" with exact url to "/index.php/apps/files" When sending "GET" with exact url to "/index.php/apps/files"
And the HTTP status code should be "403" And the HTTP status code should be "403"

View File

@ -2981,6 +2981,14 @@
<code>InMemoryFile</code> <code>InMemoryFile</code>
</ImplementedReturnTypeMismatch> </ImplementedReturnTypeMismatch>
</file> </file>
<file src="lib/private/Avatar/PlaceholderAvatar.php">
<ImplementedReturnTypeMismatch occurrences="1">
<code>ISimpleFile</code>
</ImplementedReturnTypeMismatch>
<InvalidScalarArgument occurrences="1">
<code>$data</code>
</InvalidScalarArgument>
</file>
<file src="lib/private/Avatar/UserAvatar.php"> <file src="lib/private/Avatar/UserAvatar.php">
<ImplementedReturnTypeMismatch occurrences="1"> <ImplementedReturnTypeMismatch occurrences="1">
<code>ISimpleFile</code> <code>ISimpleFile</code>

View File

@ -734,6 +734,7 @@ return array(
'OC\\Avatar\\Avatar' => $baseDir . '/lib/private/Avatar/Avatar.php', 'OC\\Avatar\\Avatar' => $baseDir . '/lib/private/Avatar/Avatar.php',
'OC\\Avatar\\AvatarManager' => $baseDir . '/lib/private/Avatar/AvatarManager.php', 'OC\\Avatar\\AvatarManager' => $baseDir . '/lib/private/Avatar/AvatarManager.php',
'OC\\Avatar\\GuestAvatar' => $baseDir . '/lib/private/Avatar/GuestAvatar.php', 'OC\\Avatar\\GuestAvatar' => $baseDir . '/lib/private/Avatar/GuestAvatar.php',
'OC\\Avatar\\PlaceholderAvatar' => $baseDir . '/lib/private/Avatar/PlaceholderAvatar.php',
'OC\\Avatar\\UserAvatar' => $baseDir . '/lib/private/Avatar/UserAvatar.php', 'OC\\Avatar\\UserAvatar' => $baseDir . '/lib/private/Avatar/UserAvatar.php',
'OC\\BackgroundJob\\Job' => $baseDir . '/lib/private/BackgroundJob/Job.php', 'OC\\BackgroundJob\\Job' => $baseDir . '/lib/private/BackgroundJob/Job.php',
'OC\\BackgroundJob\\JobList' => $baseDir . '/lib/private/BackgroundJob/JobList.php', 'OC\\BackgroundJob\\JobList' => $baseDir . '/lib/private/BackgroundJob/JobList.php',

View File

@ -763,6 +763,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Avatar\\Avatar' => __DIR__ . '/../../..' . '/lib/private/Avatar/Avatar.php', 'OC\\Avatar\\Avatar' => __DIR__ . '/../../..' . '/lib/private/Avatar/Avatar.php',
'OC\\Avatar\\AvatarManager' => __DIR__ . '/../../..' . '/lib/private/Avatar/AvatarManager.php', 'OC\\Avatar\\AvatarManager' => __DIR__ . '/../../..' . '/lib/private/Avatar/AvatarManager.php',
'OC\\Avatar\\GuestAvatar' => __DIR__ . '/../../..' . '/lib/private/Avatar/GuestAvatar.php', 'OC\\Avatar\\GuestAvatar' => __DIR__ . '/../../..' . '/lib/private/Avatar/GuestAvatar.php',
'OC\\Avatar\\PlaceholderAvatar' => __DIR__ . '/../../..' . '/lib/private/Avatar/PlaceholderAvatar.php',
'OC\\Avatar\\UserAvatar' => __DIR__ . '/../../..' . '/lib/private/Avatar/UserAvatar.php', 'OC\\Avatar\\UserAvatar' => __DIR__ . '/../../..' . '/lib/private/Avatar/UserAvatar.php',
'OC\\BackgroundJob\\Job' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/Job.php', 'OC\\BackgroundJob\\Job' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/Job.php',
'OC\\BackgroundJob\\JobList' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/JobList.php', 'OC\\BackgroundJob\\JobList' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/JobList.php',

View File

@ -144,6 +144,42 @@ class AccountManager implements IAccountManager {
} }
} }
$allowedScopes = [
self::SCOPE_PRIVATE,
self::SCOPE_LOCAL,
self::SCOPE_FEDERATED,
self::SCOPE_PUBLISHED,
self::VISIBILITY_PRIVATE,
self::VISIBILITY_CONTACTS_ONLY,
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']);
}
}
}
if (empty($userData)) { if (empty($userData)) {
$this->insertNewUser($user, $data); $this->insertNewUser($user, $data);
} elseif ($userData !== $data) { } elseif ($userData !== $data) {
@ -198,6 +234,8 @@ class AccountManager implements IAccountManager {
* *
* @param IUser $user * @param IUser $user
* @return array * @return array
*
* @deprecated use getAccount instead to make sure migrated properties work correctly
*/ */
public function getUser(IUser $user) { public function getUser(IUser $user) {
$uid = $user->getUID(); $uid = $user->getUID();
@ -405,7 +443,7 @@ class AccountManager implements IAccountManager {
} }
$query->setParameter('name', $propertyName) $query->setParameter('name', $propertyName)
->setParameter('value', $property['value']); ->setParameter('value', $property['value'] ?? '');
$query->execute(); $query->execute();
} }
} }
@ -421,41 +459,41 @@ class AccountManager implements IAccountManager {
self::PROPERTY_DISPLAYNAME => self::PROPERTY_DISPLAYNAME =>
[ [
'value' => $user->getDisplayName(), 'value' => $user->getDisplayName(),
'scope' => self::VISIBILITY_CONTACTS_ONLY, 'scope' => self::SCOPE_FEDERATED,
'verified' => self::NOT_VERIFIED, 'verified' => self::NOT_VERIFIED,
], ],
self::PROPERTY_ADDRESS => self::PROPERTY_ADDRESS =>
[ [
'value' => '', 'value' => '',
'scope' => self::VISIBILITY_PRIVATE, 'scope' => self::SCOPE_LOCAL,
'verified' => self::NOT_VERIFIED, 'verified' => self::NOT_VERIFIED,
], ],
self::PROPERTY_WEBSITE => self::PROPERTY_WEBSITE =>
[ [
'value' => '', 'value' => '',
'scope' => self::VISIBILITY_PRIVATE, 'scope' => self::SCOPE_LOCAL,
'verified' => self::NOT_VERIFIED, 'verified' => self::NOT_VERIFIED,
], ],
self::PROPERTY_EMAIL => self::PROPERTY_EMAIL =>
[ [
'value' => $user->getEMailAddress(), 'value' => $user->getEMailAddress(),
'scope' => self::VISIBILITY_CONTACTS_ONLY, 'scope' => self::SCOPE_FEDERATED,
'verified' => self::NOT_VERIFIED, 'verified' => self::NOT_VERIFIED,
], ],
self::PROPERTY_AVATAR => self::PROPERTY_AVATAR =>
[ [
'scope' => self::VISIBILITY_CONTACTS_ONLY 'scope' => self::SCOPE_FEDERATED
], ],
self::PROPERTY_PHONE => self::PROPERTY_PHONE =>
[ [
'value' => '', 'value' => '',
'scope' => self::VISIBILITY_PRIVATE, 'scope' => self::SCOPE_LOCAL,
'verified' => self::NOT_VERIFIED, 'verified' => self::NOT_VERIFIED,
], ],
self::PROPERTY_TWITTER => self::PROPERTY_TWITTER =>
[ [
'value' => '', 'value' => '',
'scope' => self::VISIBILITY_PRIVATE, 'scope' => self::SCOPE_LOCAL,
'verified' => self::NOT_VERIFIED, 'verified' => self::NOT_VERIFIED,
], ],
]; ];
@ -464,7 +502,7 @@ class AccountManager implements IAccountManager {
private function parseAccountData(IUser $user, $data): Account { private function parseAccountData(IUser $user, $data): Account {
$account = new Account($user); $account = new Account($user);
foreach ($data as $property => $accountData) { foreach ($data as $property => $accountData) {
$account->setProperty($property, $accountData['value'] ?? '', $accountData['scope'] ?? self::VISIBILITY_PRIVATE, $accountData['verified'] ?? self::NOT_VERIFIED); $account->setProperty($property, $accountData['value'] ?? '', $accountData['scope'] ?? self::SCOPE_LOCAL, $accountData['verified'] ?? self::NOT_VERIFIED);
} }
return $account; return $account;
} }

View File

@ -26,6 +26,7 @@ declare(strict_types=1);
namespace OC\Accounts; namespace OC\Accounts;
use OCP\Accounts\IAccountManager;
use OCP\Accounts\IAccountProperty; use OCP\Accounts\IAccountProperty;
class AccountProperty implements IAccountProperty { class AccountProperty implements IAccountProperty {
@ -42,7 +43,7 @@ class AccountProperty implements IAccountProperty {
public function __construct(string $name, string $value, string $scope, string $verified) { public function __construct(string $name, string $value, string $scope, string $verified) {
$this->name = $name; $this->name = $name;
$this->value = $value; $this->value = $value;
$this->scope = $scope; $this->scope = $this->mapScopeToV2($scope);
$this->verified = $verified; $this->verified = $verified;
} }
@ -77,7 +78,7 @@ class AccountProperty implements IAccountProperty {
* @return IAccountProperty * @return IAccountProperty
*/ */
public function setScope(string $scope): IAccountProperty { public function setScope(string $scope): IAccountProperty {
$this->scope = $scope; $this->scope = $this->mapScopeToV2($scope);
return $this; return $this;
} }
@ -127,6 +128,23 @@ class AccountProperty implements IAccountProperty {
return $this->scope; return $this->scope;
} }
public static function mapScopeToV2($scope) {
if (strpos($scope, 'v2-') === 0) {
return $scope;
}
switch ($scope) {
case IAccountManager::VISIBILITY_PRIVATE:
return IAccountManager::SCOPE_LOCAL;
case IAccountManager::VISIBILITY_CONTACTS_ONLY:
return IAccountManager::SCOPE_FEDERATED;
case IAccountManager::VISIBILITY_PUBLIC:
return IAccountManager::SCOPE_PUBLISHED;
}
return IAccountManager::SCOPE_LOCAL;
}
/** /**
* Get the verification status of a property * Get the verification status of a property
* *

View File

@ -34,8 +34,10 @@ declare(strict_types=1);
namespace OC\Avatar; namespace OC\Avatar;
use OC\KnownUser\KnownUserService;
use OC\User\Manager; use OC\User\Manager;
use OC\User\NoUserException; use OC\User\NoUserException;
use OCP\Accounts\IAccountManager;
use OCP\Files\IAppData; use OCP\Files\IAppData;
use OCP\Files\NotFoundException; use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException; use OCP\Files\NotPermittedException;
@ -44,12 +46,16 @@ use OCP\IAvatarManager;
use OCP\IConfig; use OCP\IConfig;
use OCP\IL10N; use OCP\IL10N;
use OCP\ILogger; use OCP\ILogger;
use OCP\IUserSession;
/** /**
* This class implements methods to access Avatar functionality * This class implements methods to access Avatar functionality
*/ */
class AvatarManager implements IAvatarManager { class AvatarManager implements IAvatarManager {
/** @var IUserSession */
private $userSession;
/** @var Manager */ /** @var Manager */
private $userManager; private $userManager;
@ -65,6 +71,12 @@ class AvatarManager implements IAvatarManager {
/** @var IConfig */ /** @var IConfig */
private $config; private $config;
/** @var IAccountManager */
private $accountManager;
/** @var KnownUserService */
private $knownUserService;
/** /**
* AvatarManager constructor. * AvatarManager constructor.
* *
@ -73,18 +85,26 @@ class AvatarManager implements IAvatarManager {
* @param IL10N $l * @param IL10N $l
* @param ILogger $logger * @param ILogger $logger
* @param IConfig $config * @param IConfig $config
* @param IUserSession $userSession
*/ */
public function __construct( public function __construct(
IUserSession $userSession,
Manager $userManager, Manager $userManager,
IAppData $appData, IAppData $appData,
IL10N $l, IL10N $l,
ILogger $logger, ILogger $logger,
IConfig $config) { IConfig $config,
IAccountManager $accountManager,
KnownUserService $knownUserService
) {
$this->userSession = $userSession;
$this->userManager = $userManager; $this->userManager = $userManager;
$this->appData = $appData; $this->appData = $appData;
$this->l = $l; $this->l = $l;
$this->logger = $logger; $this->logger = $logger;
$this->config = $config; $this->config = $config;
$this->accountManager = $accountManager;
$this->knownUserService = $knownUserService;
} }
/** /**
@ -104,12 +124,34 @@ class AvatarManager implements IAvatarManager {
// sanitize userID - fixes casing issue (needed for the filesystem stuff that is done below) // sanitize userID - fixes casing issue (needed for the filesystem stuff that is done below)
$userId = $user->getUID(); $userId = $user->getUID();
$requestingUser = null;
if ($this->userSession !== null) {
$requestingUser = $this->userSession->getUser();
}
try { try {
$folder = $this->appData->getFolder($userId); $folder = $this->appData->getFolder($userId);
} catch (NotFoundException $e) { } catch (NotFoundException $e) {
$folder = $this->appData->newFolder($userId); $folder = $this->appData->newFolder($userId);
} }
$account = $this->accountManager->getAccount($user);
$avatarProperties = $account->getProperty(IAccountManager::PROPERTY_AVATAR);
$avatarScope = $avatarProperties->getScope();
if (
// v2-private scope hides the avatar from public access and from unknown users
$avatarScope === IAccountManager::SCOPE_PRIVATE
&& (
// accessing from public link
$requestingUser === null
// logged in, but unknown to user
|| !$this->knownUserService->isKnownToUser($requestingUser->getUID(), $userId)
)) {
// use a placeholder avatar which caches the generated images
return new PlaceholderAvatar($folder, $user, $this->logger);
}
return new UserAvatar($folder, $this->l, $user, $this->logger, $this->config); return new UserAvatar($folder, $this->l, $user, $this->logger, $this->config);
} }

View File

@ -0,0 +1,183 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018, Michael Weimann <mail@michael-weimann.eu>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Michael Weimann <mail@michael-weimann.eu>
*
* @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\Avatar;
use OC\NotSquareException;
use OC\User\User;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\Files\SimpleFS\ISimpleFolder;
use OCP\IConfig;
use OCP\IImage;
use OCP\IL10N;
use OCP\ILogger;
/**
* This class represents a registered user's placeholder avatar.
*
* It generates an image based on the user's initials and caches it on storage
* for faster retrieval, unlike the GuestAvatar.
*/
class PlaceholderAvatar extends Avatar {
/** @var ISimpleFolder */
private $folder;
/** @var User */
private $user;
/**
* UserAvatar constructor.
*
* @param IConfig $config The configuration
* @param ISimpleFolder $folder The avatar files folder
* @param IL10N $l The localization helper
* @param User $user The user this class manages the avatar for
* @param ILogger $logger The logger
*/
public function __construct(
ISimpleFolder $folder,
$user,
ILogger $logger) {
parent::__construct($logger);
$this->folder = $folder;
$this->user = $user;
}
/**
* Check if an avatar exists for the user
*
* @return bool
*/
public function exists() {
return true;
}
/**
* Sets the users avatar.
*
* @param IImage|resource|string $data An image object, imagedata or path to set a new avatar
* @throws \Exception if the provided file is not a jpg or png image
* @throws \Exception if the provided image is not valid
* @throws NotSquareException if the image is not square
* @return void
*/
public function set($data) {
// unimplemented for placeholder avatars
}
/**
* Removes the users avatar.
*/
public function remove(bool $silent = false) {
$avatars = $this->folder->getDirectoryListing();
foreach ($avatars as $avatar) {
$avatar->delete();
}
}
/**
* Returns the avatar for an user.
*
* If there is no avatar file yet, one is generated.
*
* @param int $size
* @return ISimpleFile
* @throws NotFoundException
* @throws \OCP\Files\NotPermittedException
* @throws \OCP\PreConditionNotMetException
*/
public function getFile($size) {
$size = (int) $size;
$ext = 'png';
if ($size === -1) {
$path = 'avatar-placeholder.' . $ext;
} else {
$path = 'avatar-placeholder.' . $size . '.' . $ext;
}
try {
$file = $this->folder->getFile($path);
} catch (NotFoundException $e) {
if ($size <= 0) {
throw new NotFoundException;
}
if (!$data = $this->generateAvatarFromSvg($size)) {
$data = $this->generateAvatar($this->getDisplayName(), $size);
}
try {
$file = $this->folder->newFile($path);
$file->putContent($data);
} catch (NotPermittedException $e) {
$this->logger->error('Failed to save avatar placeholder for ' . $this->user->getUID());
throw new NotFoundException();
}
}
return $file;
}
/**
* Returns the user display name.
*
* @return string
*/
public function getDisplayName(): string {
return $this->user->getDisplayName();
}
/**
* Handles user changes.
*
* @param string $feature The changed feature
* @param mixed $oldValue The previous value
* @param mixed $newValue The new value
* @throws NotPermittedException
* @throws \OCP\PreConditionNotMetException
*/
public function userChanged($feature, $oldValue, $newValue) {
$this->remove();
}
/**
* Check if the avatar of a user is a custom uploaded one
*
* @return bool
*/
public function isCustomAvatar(): bool {
return false;
}
}

View File

@ -270,6 +270,7 @@ class UserAvatar extends Avatar {
throw new NotFoundException; throw new NotFoundException;
} }
// TODO: rework to integrate with the PlaceholderAvatar in a compatible way
if ($this->folder->fileExists('generated')) { if ($this->folder->fileExists('generated')) {
if (!$data = $this->generateAvatarFromSvg($size)) { if (!$data = $this->generateAvatarFromSvg($size)) {
$data = $this->generateAvatar($this->getDisplayName(), $size); $data = $this->generateAvatar($this->getDisplayName(), $size);

View File

@ -74,6 +74,10 @@ class KnownUserService {
* @return bool * @return bool
*/ */
public function isKnownToUser(string $knownTo, string $contactUserId): bool { public function isKnownToUser(string $knownTo, string $contactUserId): bool {
if ($knownTo === $contactUserId) {
return true;
}
if (!isset($this->knownUsers[$knownTo])) { if (!isset($this->knownUsers[$knownTo])) {
$entities = $this->mapper->getKnownUsers($knownTo); $entities = $this->mapper->getKnownUsers($knownTo);
$this->knownUsers[$knownTo] = []; $this->knownUsers[$knownTo] = [];

View File

@ -104,6 +104,7 @@ use OC\IntegrityCheck\Checker;
use OC\IntegrityCheck\Helpers\AppLocator; use OC\IntegrityCheck\Helpers\AppLocator;
use OC\IntegrityCheck\Helpers\EnvironmentHelper; use OC\IntegrityCheck\Helpers\EnvironmentHelper;
use OC\IntegrityCheck\Helpers\FileAccessHelper; use OC\IntegrityCheck\Helpers\FileAccessHelper;
use OC\KnownUser\KnownUserService;
use OC\Lock\DBLockingProvider; use OC\Lock\DBLockingProvider;
use OC\Lock\MemcacheLockingProvider; use OC\Lock\MemcacheLockingProvider;
use OC\Lock\NoopLockingProvider; use OC\Lock\NoopLockingProvider;
@ -720,11 +721,14 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerService(AvatarManager::class, function (Server $c) { $this->registerService(AvatarManager::class, function (Server $c) {
return new AvatarManager( return new AvatarManager(
$c->get(IUserSession::class),
$c->get(\OC\User\Manager::class), $c->get(\OC\User\Manager::class),
$c->getAppDataDir('avatar'), $c->getAppDataDir('avatar'),
$c->getL10N('lib'), $c->getL10N('lib'),
$c->get(ILogger::class), $c->get(ILogger::class),
$c->get(\OCP\IConfig::class) $c->get(\OCP\IConfig::class),
$c->get(IAccountManager::class),
$c->get(KnownUserService::class)
); );
}); });
$this->registerAlias(IAvatarManager::class, AvatarManager::class); $this->registerAlias(IAvatarManager::class, AvatarManager::class);

View File

@ -38,11 +38,54 @@ use OCP\IUser;
*/ */
interface IAccountManager { interface IAccountManager {
/** nobody can see my account details */ /**
* Contact details visible locally only
*
* @since 21.0.1
*/
public const SCOPE_PRIVATE = 'v2-private';
/**
* Contact details visible locally and through public link access on local instance
*
* @since 21.0.1
*/
public const SCOPE_LOCAL = 'v2-local';
/**
* Contact details visible locally, through public link access and on trusted federated servers.
*
* @since 21.0.1
*/
public const SCOPE_FEDERATED = 'v2-federated';
/**
* Contact details visible locally, through public link access, on trusted federated servers
* and published to the public lookup server.
*
* @since 21.0.1
*/
public const SCOPE_PUBLISHED = 'v2-published';
/**
* Contact details only visible locally
*
* @deprecated 21.0.1
*/
public const VISIBILITY_PRIVATE = 'private'; public const VISIBILITY_PRIVATE = 'private';
/** only contacts, especially trusted servers can see my contact details */
/**
* Contact details visible on trusted federated servers.
*
* @deprecated 21.0.1
*/
public const VISIBILITY_CONTACTS_ONLY = 'contacts'; public const VISIBILITY_CONTACTS_ONLY = 'contacts';
/** every body ca see my contact detail, will be published to the lookup server */
/**
* Contact details visible on trusted federated servers and in the public lookup server.
*
* @deprecated 21.0.1
*/
public const VISIBILITY_PUBLIC = 'public'; public const VISIBILITY_PUBLIC = 'public';
public const PROPERTY_AVATAR = 'avatar'; public const PROPERTY_AVATAR = 'avatar';

View File

@ -153,6 +153,129 @@ class AccountManagerTest extends TestCase {
]; ];
} }
public function updateUserSetScopeProvider() {
return [
// regular scope switching
[
[
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'Display Name', 'scope' => IAccountManager::SCOPE_PUBLISHED],
IAccountManager::PROPERTY_EMAIL => ['value' => 'test@example.org', 'scope' => IAccountManager::SCOPE_PUBLISHED],
IAccountManager::PROPERTY_AVATAR => ['value' => '@sometwitter', 'scope' => IAccountManager::SCOPE_PUBLISHED],
IAccountManager::PROPERTY_TWITTER => ['value' => '@sometwitter', 'scope' => IAccountManager::SCOPE_PUBLISHED],
IAccountManager::PROPERTY_PHONE => ['value' => '+491601231212', 'scope' => IAccountManager::SCOPE_FEDERATED],
IAccountManager::PROPERTY_ADDRESS => ['value' => 'some street', 'scope' => IAccountManager::SCOPE_LOCAL],
IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://example.org', 'scope' => IAccountManager::SCOPE_PRIVATE],
],
[
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'Display Name', 'scope' => IAccountManager::SCOPE_LOCAL],
IAccountManager::PROPERTY_EMAIL => ['value' => 'test@example.org', 'scope' => IAccountManager::SCOPE_FEDERATED],
IAccountManager::PROPERTY_TWITTER => ['value' => '@sometwitter', 'scope' => IAccountManager::SCOPE_PRIVATE],
IAccountManager::PROPERTY_PHONE => ['value' => '+491601231212', 'scope' => IAccountManager::SCOPE_LOCAL],
IAccountManager::PROPERTY_ADDRESS => ['value' => 'some street', 'scope' => IAccountManager::SCOPE_FEDERATED],
IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://example.org', 'scope' => IAccountManager::SCOPE_PUBLISHED],
],
[
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'Display Name', 'scope' => IAccountManager::SCOPE_LOCAL],
IAccountManager::PROPERTY_EMAIL => ['value' => 'test@example.org', 'scope' => IAccountManager::SCOPE_FEDERATED],
IAccountManager::PROPERTY_TWITTER => ['value' => '@sometwitter', 'scope' => IAccountManager::SCOPE_PRIVATE],
IAccountManager::PROPERTY_PHONE => ['value' => '+491601231212', 'scope' => IAccountManager::SCOPE_LOCAL],
IAccountManager::PROPERTY_ADDRESS => ['value' => 'some street', 'scope' => IAccountManager::SCOPE_FEDERATED],
IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://example.org', 'scope' => IAccountManager::SCOPE_PUBLISHED],
],
],
// legacy scope mapping, the given visibility values get converted to scopes
[
[
IAccountManager::PROPERTY_TWITTER => ['value' => '@sometwitter', 'scope' => IAccountManager::SCOPE_PUBLISHED],
IAccountManager::PROPERTY_PHONE => ['value' => '+491601231212', 'scope' => IAccountManager::SCOPE_FEDERATED],
IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://example.org', 'scope' => IAccountManager::SCOPE_PRIVATE],
],
[
IAccountManager::PROPERTY_TWITTER => ['value' => '@sometwitter', 'scope' => IAccountManager::VISIBILITY_PUBLIC],
IAccountManager::PROPERTY_PHONE => ['value' => '+491601231212', 'scope' => IAccountManager::VISIBILITY_CONTACTS_ONLY],
IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://example.org', 'scope' => IAccountManager::VISIBILITY_PRIVATE],
],
[
IAccountManager::PROPERTY_TWITTER => ['value' => '@sometwitter', 'scope' => IAccountManager::SCOPE_PUBLISHED],
IAccountManager::PROPERTY_PHONE => ['value' => '+491601231212', 'scope' => IAccountManager::SCOPE_FEDERATED],
IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://example.org', 'scope' => IAccountManager::SCOPE_LOCAL],
],
],
// invalid or unsupported scope values get converted to SCOPE_LOCAL
[
[
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'Display Name', 'scope' => IAccountManager::SCOPE_PUBLISHED],
IAccountManager::PROPERTY_EMAIL => ['value' => 'test@example.org', 'scope' => IAccountManager::SCOPE_PUBLISHED],
IAccountManager::PROPERTY_TWITTER => ['value' => '@sometwitter', 'scope' => IAccountManager::SCOPE_PUBLISHED],
IAccountManager::PROPERTY_PHONE => ['value' => '+491601231212', 'scope' => IAccountManager::SCOPE_FEDERATED],
],
[
// SCOPE_PRIVATE is not allowed for display name and email
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'Display Name', 'scope' => IAccountManager::SCOPE_PRIVATE],
IAccountManager::PROPERTY_EMAIL => ['value' => 'test@example.org', 'scope' => IAccountManager::SCOPE_PRIVATE],
IAccountManager::PROPERTY_TWITTER => ['value' => '@sometwitter', 'scope' => 'invalid'],
IAccountManager::PROPERTY_PHONE => ['value' => '+491601231212', 'scope' => ''],
],
[
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'Display Name', 'scope' => IAccountManager::SCOPE_LOCAL],
IAccountManager::PROPERTY_EMAIL => ['value' => 'test@example.org', 'scope' => IAccountManager::SCOPE_LOCAL],
IAccountManager::PROPERTY_TWITTER => ['value' => '@sometwitter', 'scope' => IAccountManager::SCOPE_LOCAL],
IAccountManager::PROPERTY_PHONE => ['value' => '+491601231212', 'scope' => IAccountManager::SCOPE_LOCAL],
],
// don't throw but fall back
false, false,
],
// invalid or unsupported scope values throw an exception when passing $throwOnData=true
[
[IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'Display Name', 'scope' => IAccountManager::SCOPE_PUBLISHED]],
[IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'Display Name', 'scope' => IAccountManager::SCOPE_PRIVATE]],
null,
// throw exception
true, true,
],
[
[IAccountManager::PROPERTY_EMAIL => ['value' => 'test@example.org', 'scope' => IAccountManager::SCOPE_PUBLISHED]],
[IAccountManager::PROPERTY_EMAIL => ['value' => 'test@example.org', 'scope' => IAccountManager::SCOPE_PRIVATE]],
null,
// throw exception
true, true,
],
[
[IAccountManager::PROPERTY_TWITTER => ['value' => '@sometwitter', 'scope' => IAccountManager::SCOPE_PUBLISHED]],
[IAccountManager::PROPERTY_TWITTER => ['value' => '@sometwitter', 'scope' => 'invalid']],
null,
// throw exception
true, true,
],
];
}
/**
* @dataProvider updateUserSetScopeProvider
*/
public function testUpdateUserSetScope($oldData, $newData, $savedData, $throwOnData = true, $expectedThrow = false) {
$accountManager = $this->getInstance(['getUser', 'insertNewUser', 'updateExistingUser', 'updateVerifyStatus', 'checkEmailVerification']);
/** @var IUser $user */
$user = $this->createMock(IUser::class);
$accountManager->expects($this->once())->method('getUser')->with($user)->willReturn($oldData);
if ($expectedThrow) {
$accountManager->expects($this->never())->method('updateExistingUser');
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('scope');
} else {
$accountManager->expects($this->once())->method('checkEmailVerification')
->with($oldData, $savedData, $user)->willReturn($savedData);
$accountManager->expects($this->once())->method('updateVerifyStatus')
->with($oldData, $savedData)->willReturn($savedData);
$accountManager->expects($this->once())->method('updateExistingUser')
->with($user, $savedData);
$accountManager->expects($this->never())->method('insertNewUser');
}
$accountManager->updateUser($user, $newData, $throwOnData);
}
/** /**
* @dataProvider dataTestGetUser * @dataProvider dataTestGetUser
@ -278,26 +401,26 @@ class AccountManagerTest extends TestCase {
IAccountManager::PROPERTY_TWITTER => IAccountManager::PROPERTY_TWITTER =>
[ [
'value' => '@twitterhandle', 'value' => '@twitterhandle',
'scope' => IAccountManager::VISIBILITY_PRIVATE, 'scope' => IAccountManager::SCOPE_LOCAL,
'verified' => IAccountManager::NOT_VERIFIED, 'verified' => IAccountManager::NOT_VERIFIED,
], ],
IAccountManager::PROPERTY_EMAIL => IAccountManager::PROPERTY_EMAIL =>
[ [
'value' => 'test@example.com', 'value' => 'test@example.com',
'scope' => IAccountManager::VISIBILITY_PUBLIC, 'scope' => IAccountManager::SCOPE_PUBLISHED,
'verified' => IAccountManager::VERIFICATION_IN_PROGRESS, 'verified' => IAccountManager::VERIFICATION_IN_PROGRESS,
], ],
IAccountManager::PROPERTY_WEBSITE => IAccountManager::PROPERTY_WEBSITE =>
[ [
'value' => 'https://example.com', 'value' => 'https://example.com',
'scope' => IAccountManager::VISIBILITY_CONTACTS_ONLY, 'scope' => IAccountManager::SCOPE_FEDERATED,
'verified' => IAccountManager::VERIFIED, 'verified' => IAccountManager::VERIFIED,
], ],
]; ];
$expected = new Account($user); $expected = new Account($user);
$expected->setProperty(IAccountManager::PROPERTY_TWITTER, '@twitterhandle', IAccountManager::VISIBILITY_PRIVATE, IAccountManager::NOT_VERIFIED); $expected->setProperty(IAccountManager::PROPERTY_TWITTER, '@twitterhandle', IAccountManager::SCOPE_LOCAL, IAccountManager::NOT_VERIFIED);
$expected->setProperty(IAccountManager::PROPERTY_EMAIL, 'test@example.com', IAccountManager::VISIBILITY_PUBLIC, IAccountManager::VERIFICATION_IN_PROGRESS); $expected->setProperty(IAccountManager::PROPERTY_EMAIL, 'test@example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::VERIFICATION_IN_PROGRESS);
$expected->setProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::VISIBILITY_CONTACTS_ONLY, IAccountManager::VERIFIED); $expected->setProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_FEDERATED, IAccountManager::VERIFIED);
$accountManager->expects($this->once()) $accountManager->expects($this->once())
->method('getUser') ->method('getUser')

View File

@ -37,12 +37,12 @@ class AccountPropertyTest extends TestCase {
$accountProperty = new AccountProperty( $accountProperty = new AccountProperty(
IAccountManager::PROPERTY_WEBSITE, IAccountManager::PROPERTY_WEBSITE,
'https://example.com', 'https://example.com',
IAccountManager::VISIBILITY_PUBLIC, IAccountManager::SCOPE_PUBLISHED,
IAccountManager::VERIFIED IAccountManager::VERIFIED
); );
$this->assertEquals(IAccountManager::PROPERTY_WEBSITE, $accountProperty->getName()); $this->assertEquals(IAccountManager::PROPERTY_WEBSITE, $accountProperty->getName());
$this->assertEquals('https://example.com', $accountProperty->getValue()); $this->assertEquals('https://example.com', $accountProperty->getValue());
$this->assertEquals(IAccountManager::VISIBILITY_PUBLIC, $accountProperty->getScope()); $this->assertEquals(IAccountManager::SCOPE_PUBLISHED, $accountProperty->getScope());
$this->assertEquals(IAccountManager::VERIFIED, $accountProperty->getVerified()); $this->assertEquals(IAccountManager::VERIFIED, $accountProperty->getVerified());
} }
@ -50,7 +50,7 @@ class AccountPropertyTest extends TestCase {
$accountProperty = new AccountProperty( $accountProperty = new AccountProperty(
IAccountManager::PROPERTY_WEBSITE, IAccountManager::PROPERTY_WEBSITE,
'https://example.com', 'https://example.com',
IAccountManager::VISIBILITY_PUBLIC, IAccountManager::SCOPE_PUBLISHED,
IAccountManager::VERIFIED IAccountManager::VERIFIED
); );
$actualReturn = $accountProperty->setValue('https://example.org'); $actualReturn = $accountProperty->setValue('https://example.org');
@ -62,19 +62,48 @@ class AccountPropertyTest extends TestCase {
$accountProperty = new AccountProperty( $accountProperty = new AccountProperty(
IAccountManager::PROPERTY_WEBSITE, IAccountManager::PROPERTY_WEBSITE,
'https://example.com', 'https://example.com',
IAccountManager::VISIBILITY_PUBLIC, IAccountManager::SCOPE_PUBLISHED,
IAccountManager::VERIFIED IAccountManager::VERIFIED
); );
$actualReturn = $accountProperty->setScope(IAccountManager::VISIBILITY_PRIVATE); $actualReturn = $accountProperty->setScope(IAccountManager::SCOPE_LOCAL);
$this->assertEquals(IAccountManager::VISIBILITY_PRIVATE, $accountProperty->getScope()); $this->assertEquals(IAccountManager::SCOPE_LOCAL, $accountProperty->getScope());
$this->assertEquals(IAccountManager::VISIBILITY_PRIVATE, $actualReturn->getScope()); $this->assertEquals(IAccountManager::SCOPE_LOCAL, $actualReturn->getScope());
}
public function scopesProvider() {
return [
// current values
[IAccountManager::SCOPE_PRIVATE, IAccountManager::SCOPE_PRIVATE],
[IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_LOCAL],
[IAccountManager::SCOPE_PUBLISHED, IAccountManager::SCOPE_PUBLISHED],
// legacy values
[IAccountManager::VISIBILITY_PRIVATE, IAccountManager::SCOPE_LOCAL],
[IAccountManager::VISIBILITY_CONTACTS_ONLY, IAccountManager::SCOPE_FEDERATED],
[IAccountManager::VISIBILITY_PUBLIC, IAccountManager::SCOPE_PUBLISHED],
// fallback
['', IAccountManager::SCOPE_LOCAL],
['unknown', IAccountManager::SCOPE_LOCAL],
];
}
/**
* @dataProvider scopesProvider
*/
public function testSetScopeMapping($storedScope, $returnedScope) {
$accountProperty = new AccountProperty(
IAccountManager::PROPERTY_WEBSITE,
'https://example.com',
$storedScope,
IAccountManager::VERIFIED
);
$this->assertEquals($returnedScope, $accountProperty->getScope());
} }
public function testSetVerified() { public function testSetVerified() {
$accountProperty = new AccountProperty( $accountProperty = new AccountProperty(
IAccountManager::PROPERTY_WEBSITE, IAccountManager::PROPERTY_WEBSITE,
'https://example.com', 'https://example.com',
IAccountManager::VISIBILITY_PUBLIC, IAccountManager::SCOPE_PUBLISHED,
IAccountManager::VERIFIED IAccountManager::VERIFIED
); );
$actualReturn = $accountProperty->setVerified(IAccountManager::NOT_VERIFIED); $actualReturn = $accountProperty->setVerified(IAccountManager::NOT_VERIFIED);
@ -86,13 +115,13 @@ class AccountPropertyTest extends TestCase {
$accountProperty = new AccountProperty( $accountProperty = new AccountProperty(
IAccountManager::PROPERTY_WEBSITE, IAccountManager::PROPERTY_WEBSITE,
'https://example.com', 'https://example.com',
IAccountManager::VISIBILITY_PUBLIC, IAccountManager::SCOPE_PUBLISHED,
IAccountManager::VERIFIED IAccountManager::VERIFIED
); );
$this->assertEquals([ $this->assertEquals([
'name' => IAccountManager::PROPERTY_WEBSITE, 'name' => IAccountManager::PROPERTY_WEBSITE,
'value' => 'https://example.com', 'value' => 'https://example.com',
'scope' => IAccountManager::VISIBILITY_PUBLIC, 'scope' => IAccountManager::SCOPE_PUBLISHED,
'verified' => IAccountManager::VERIFIED 'verified' => IAccountManager::VERIFIED
], $accountProperty->jsonSerialize()); ], $accountProperty->jsonSerialize());
} }

View File

@ -43,21 +43,21 @@ class AccountTest extends TestCase {
public function testSetProperty() { public function testSetProperty() {
$user = $this->createMock(IUser::class); $user = $this->createMock(IUser::class);
$property = new AccountProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::VISIBILITY_PUBLIC, IAccountManager::NOT_VERIFIED); $property = new AccountProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED);
$account = new Account($user); $account = new Account($user);
$account->setProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::VISIBILITY_PUBLIC, IAccountManager::NOT_VERIFIED); $account->setProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED);
$this->assertEquals($property, $account->getProperty(IAccountManager::PROPERTY_WEBSITE)); $this->assertEquals($property, $account->getProperty(IAccountManager::PROPERTY_WEBSITE));
} }
public function testGetProperties() { public function testGetProperties() {
$user = $this->createMock(IUser::class); $user = $this->createMock(IUser::class);
$properties = [ $properties = [
IAccountManager::PROPERTY_WEBSITE => new AccountProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::VISIBILITY_PUBLIC, IAccountManager::NOT_VERIFIED), IAccountManager::PROPERTY_WEBSITE => new AccountProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED),
IAccountManager::PROPERTY_EMAIL => new AccountProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::VISIBILITY_PRIVATE, IAccountManager::VERIFIED) IAccountManager::PROPERTY_EMAIL => new AccountProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::SCOPE_LOCAL, IAccountManager::VERIFIED)
]; ];
$account = new Account($user); $account = new Account($user);
$account->setProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::VISIBILITY_PUBLIC, IAccountManager::NOT_VERIFIED); $account->setProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED);
$account->setProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::VISIBILITY_PRIVATE, IAccountManager::VERIFIED); $account->setProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::SCOPE_LOCAL, IAccountManager::VERIFIED);
$this->assertEquals($properties, $account->getProperties()); $this->assertEquals($properties, $account->getProperties());
} }
@ -65,14 +65,14 @@ class AccountTest extends TestCase {
public function testGetFilteredProperties() { public function testGetFilteredProperties() {
$user = $this->createMock(IUser::class); $user = $this->createMock(IUser::class);
$properties = [ $properties = [
IAccountManager::PROPERTY_WEBSITE => new AccountProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::VISIBILITY_PUBLIC, IAccountManager::NOT_VERIFIED), IAccountManager::PROPERTY_WEBSITE => new AccountProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED),
IAccountManager::PROPERTY_EMAIL => new AccountProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::VISIBILITY_PRIVATE, IAccountManager::VERIFIED), IAccountManager::PROPERTY_EMAIL => new AccountProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::SCOPE_LOCAL, IAccountManager::VERIFIED),
IAccountManager::PROPERTY_PHONE => new AccountProperty(IAccountManager::PROPERTY_PHONE, '123456', IAccountManager::VISIBILITY_PUBLIC, IAccountManager::VERIFIED), IAccountManager::PROPERTY_PHONE => new AccountProperty(IAccountManager::PROPERTY_PHONE, '123456', IAccountManager::SCOPE_PUBLISHED, IAccountManager::VERIFIED),
]; ];
$account = new Account($user); $account = new Account($user);
$account->setProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::VISIBILITY_PUBLIC, IAccountManager::NOT_VERIFIED); $account->setProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED);
$account->setProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::VISIBILITY_PRIVATE, IAccountManager::VERIFIED); $account->setProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::SCOPE_LOCAL, IAccountManager::VERIFIED);
$account->setProperty(IAccountManager::PROPERTY_PHONE, '123456', IAccountManager::VISIBILITY_PUBLIC, IAccountManager::VERIFIED); $account->setProperty(IAccountManager::PROPERTY_PHONE, '123456', IAccountManager::SCOPE_PUBLISHED, IAccountManager::VERIFIED);
$this->assertEquals( $this->assertEquals(
@ -80,7 +80,7 @@ class AccountTest extends TestCase {
IAccountManager::PROPERTY_WEBSITE => $properties[IAccountManager::PROPERTY_WEBSITE], IAccountManager::PROPERTY_WEBSITE => $properties[IAccountManager::PROPERTY_WEBSITE],
IAccountManager::PROPERTY_PHONE => $properties[IAccountManager::PROPERTY_PHONE], IAccountManager::PROPERTY_PHONE => $properties[IAccountManager::PROPERTY_PHONE],
], ],
$account->getFilteredProperties(IAccountManager::VISIBILITY_PUBLIC) $account->getFilteredProperties(IAccountManager::SCOPE_PUBLISHED)
); );
$this->assertEquals( $this->assertEquals(
[ [
@ -91,19 +91,19 @@ class AccountTest extends TestCase {
); );
$this->assertEquals( $this->assertEquals(
[IAccountManager::PROPERTY_PHONE => $properties[IAccountManager::PROPERTY_PHONE]], [IAccountManager::PROPERTY_PHONE => $properties[IAccountManager::PROPERTY_PHONE]],
$account->getFilteredProperties(IAccountManager::VISIBILITY_PUBLIC, IAccountManager::VERIFIED) $account->getFilteredProperties(IAccountManager::SCOPE_PUBLISHED, IAccountManager::VERIFIED)
); );
} }
public function testJsonSerialize() { public function testJsonSerialize() {
$user = $this->createMock(IUser::class); $user = $this->createMock(IUser::class);
$properties = [ $properties = [
IAccountManager::PROPERTY_WEBSITE => new AccountProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::VISIBILITY_PUBLIC, IAccountManager::NOT_VERIFIED), IAccountManager::PROPERTY_WEBSITE => new AccountProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED),
IAccountManager::PROPERTY_EMAIL => new AccountProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::VISIBILITY_PRIVATE, IAccountManager::VERIFIED) IAccountManager::PROPERTY_EMAIL => new AccountProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::SCOPE_LOCAL, IAccountManager::VERIFIED)
]; ];
$account = new Account($user); $account = new Account($user);
$account->setProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::VISIBILITY_PUBLIC, IAccountManager::NOT_VERIFIED); $account->setProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED);
$account->setProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::VISIBILITY_PRIVATE, IAccountManager::VERIFIED); $account->setProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::SCOPE_LOCAL, IAccountManager::VERIFIED);
$this->assertEquals($properties, $account->jsonSerialize()); $this->assertEquals($properties, $account->jsonSerialize());
} }

View File

@ -25,19 +25,27 @@
namespace Test\Avatar; namespace Test\Avatar;
use OC\Avatar\AvatarManager; use OC\Avatar\AvatarManager;
use OC\Avatar\PlaceholderAvatar;
use OC\Avatar\UserAvatar; use OC\Avatar\UserAvatar;
use OC\KnownUser\KnownUserService;
use OC\User\Manager; use OC\User\Manager;
use OCP\Accounts\IAccount;
use OCP\Accounts\IAccountManager;
use OCP\Accounts\IAccountProperty;
use OCP\Files\IAppData; use OCP\Files\IAppData;
use OCP\Files\SimpleFS\ISimpleFolder; use OCP\Files\SimpleFS\ISimpleFolder;
use OCP\IConfig; use OCP\IConfig;
use OCP\IL10N; use OCP\IL10N;
use OCP\ILogger; use OCP\ILogger;
use OCP\IUser; use OCP\IUser;
use OCP\IUserSession;
/** /**
* Class AvatarManagerTest * Class AvatarManagerTest
*/ */
class AvatarManagerTest extends \Test\TestCase { class AvatarManagerTest extends \Test\TestCase {
/** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */
private $userSession;
/** @var Manager|\PHPUnit\Framework\MockObject\MockObject */ /** @var Manager|\PHPUnit\Framework\MockObject\MockObject */
private $userManager; private $userManager;
/** @var IAppData|\PHPUnit\Framework\MockObject\MockObject */ /** @var IAppData|\PHPUnit\Framework\MockObject\MockObject */
@ -48,28 +56,37 @@ class AvatarManagerTest extends \Test\TestCase {
private $logger; private $logger;
/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
private $config; private $config;
/** @var IAccountManager|\PHPUnit\Framework\MockObject\MockObject */
private $accountManager;
/** @var AvatarManager | \PHPUnit\Framework\MockObject\MockObject */ /** @var AvatarManager | \PHPUnit\Framework\MockObject\MockObject */
private $avatarManager; private $avatarManager;
/** @var KnownUserService | \PHPUnit\Framework\MockObject\MockObject */
private $knownUserService;
protected function setUp(): void { protected function setUp(): void {
parent::setUp(); parent::setUp();
$this->userSession = $this->createMock(IUserSession::class);
$this->userManager = $this->createMock(Manager::class); $this->userManager = $this->createMock(Manager::class);
$this->appData = $this->createMock(IAppData::class); $this->appData = $this->createMock(IAppData::class);
$this->l10n = $this->createMock(IL10N::class); $this->l10n = $this->createMock(IL10N::class);
$this->logger = $this->createMock(ILogger::class); $this->logger = $this->createMock(ILogger::class);
$this->config = $this->createMock(IConfig::class); $this->config = $this->createMock(IConfig::class);
$this->accountManager = $this->createMock(IAccountManager::class);
$this->knownUserService = $this->createMock(KnownUserService::class);
$this->avatarManager = new AvatarManager( $this->avatarManager = new AvatarManager(
$this->userSession,
$this->userManager, $this->userManager,
$this->appData, $this->appData,
$this->l10n, $this->l10n,
$this->logger, $this->logger,
$this->config $this->config,
$this->accountManager,
$this->knownUserService
); );
} }
public function testGetAvatarInvalidUser() { public function testGetAvatarInvalidUser() {
$this->expectException(\Exception::class); $this->expectException(\Exception::class);
$this->expectExceptionMessage('user does not exist'); $this->expectExceptionMessage('user does not exist');
@ -83,17 +100,45 @@ class AvatarManagerTest extends \Test\TestCase {
$this->avatarManager->getAvatar('invalidUser'); $this->avatarManager->getAvatar('invalidUser');
} }
public function testGetAvatarValidUser() { public function testGetAvatarForSelf() {
$user = $this->createMock(IUser::class); $user = $this->createMock(IUser::class);
$user $user
->expects($this->once()) ->expects($this->any())
->method('getUID') ->method('getUID')
->willReturn('valid-user'); ->willReturn('valid-user');
// requesting user
$this->userSession->expects($this->once())
->method('getUser')
->willReturn($user);
$this->userManager $this->userManager
->expects($this->once()) ->expects($this->once())
->method('get') ->method('get')
->with('valid-user') ->with('valid-user')
->willReturn($user); ->willReturn($user);
$account = $this->createMock(IAccount::class);
$this->accountManager->expects($this->once())
->method('getAccount')
->with($user)
->willReturn($account);
$property = $this->createMock(IAccountProperty::class);
$account->expects($this->once())
->method('getProperty')
->with(IAccountManager::PROPERTY_AVATAR)
->willReturn($property);
$property->expects($this->once())
->method('getScope')
->willReturn(IAccountManager::SCOPE_PRIVATE);
$this->knownUserService->expects($this->any())
->method('isKnownToUser')
->with('valid-user', 'valid-user')
->willReturn(true);
$folder = $this->createMock(ISimpleFolder::class); $folder = $this->createMock(ISimpleFolder::class);
$this->appData $this->appData
->expects($this->once()) ->expects($this->once())
@ -126,4 +171,86 @@ class AvatarManagerTest extends \Test\TestCase {
$expected = new UserAvatar($folder, $this->l10n, $user, $this->logger, $this->config); $expected = new UserAvatar($folder, $this->l10n, $user, $this->logger, $this->config);
$this->assertEquals($expected, $this->avatarManager->getAvatar('vaLid-USER')); $this->assertEquals($expected, $this->avatarManager->getAvatar('vaLid-USER'));
} }
public function knownUnknownProvider() {
return [
[IAccountManager::SCOPE_LOCAL, false, false, false],
[IAccountManager::SCOPE_LOCAL, true, false, false],
// public access cannot see real avatar
[IAccountManager::SCOPE_PRIVATE, true, false, true],
// unknown users cannot see real avatar
[IAccountManager::SCOPE_PRIVATE, false, false, true],
// known users can see real avatar
[IAccountManager::SCOPE_PRIVATE, false, true, false],
];
}
/**
* @dataProvider knownUnknownProvider
*/
public function testGetAvatarScopes($avatarScope, $isPublicCall, $isKnownUser, $expectedPlaceholder) {
if ($isPublicCall) {
$requestingUser = null;
} else {
$requestingUser = $this->createMock(IUser::class);
$requestingUser->method('getUID')->willReturn('requesting-user');
}
// requesting user
$this->userSession->expects($this->once())
->method('getUser')
->willReturn($requestingUser);
$user = $this->createMock(IUser::class);
$user
->expects($this->once())
->method('getUID')
->willReturn('valid-user');
$this->userManager
->expects($this->once())
->method('get')
->with('valid-user')
->willReturn($user);
$account = $this->createMock(IAccount::class);
$this->accountManager->expects($this->once())
->method('getAccount')
->with($user)
->willReturn($account);
$property = $this->createMock(IAccountProperty::class);
$account->expects($this->once())
->method('getProperty')
->with(IAccountManager::PROPERTY_AVATAR)
->willReturn($property);
$property->expects($this->once())
->method('getScope')
->willReturn($avatarScope);
$folder = $this->createMock(ISimpleFolder::class);
$this->appData
->expects($this->once())
->method('getFolder')
->with('valid-user')
->willReturn($folder);
if (!$isPublicCall) {
$this->knownUserService->expects($this->any())
->method('isKnownToUser')
->with('requesting-user', 'valid-user')
->willReturn($isKnownUser);
} else {
$this->knownUserService->expects($this->never())
->method('isKnownToUser');
}
if ($expectedPlaceholder) {
$expected = new PlaceholderAvatar($folder, $user, $this->createMock(ILogger::class));
} else {
$expected = new UserAvatar($folder, $this->l10n, $user, $this->logger, $this->config);
}
$this->assertEquals($expected, $this->avatarManager->getAvatar('valid-user'));
}
} }