OCS allow reading and writing account property scopes

Extends the provisioning API to allow a user to get and set their own
account property scopes.

Signed-off-by: Vincent Petry <vincent@nextcloud.com>
This commit is contained in:
Vincent Petry 2021-03-24 12:32:06 +01:00 committed by backportbot[bot]
parent 5c854ba132
commit b9d59e2994
4 changed files with 88 additions and 10 deletions

View File

@ -50,6 +50,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;
@ -86,12 +87,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 = [];
@ -114,7 +116,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,11 +139,26 @@ abstract class AUserData extends OCSController {
$data['subadmin'] = $this->getUserSubAdminGroupsData($targetUserObject->getUID()); $data['subadmin'] = $this->getUserSubAdminGroupsData($targetUserObject->getUID());
$data['quota'] = $this->fillStorageInfo($targetUserObject->getUID()); $data['quota'] = $this->fillStorageInfo($targetUserObject->getUID());
$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

@ -470,7 +470,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('', \OCP\API::RESPOND_UNAUTHORISED); throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
@ -490,7 +496,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'];
@ -552,6 +558,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())) {
@ -567,6 +576,10 @@ class UsersController extends AUserData {
$permittedFields[] = IAccountManager::PROPERTY_ADDRESS; $permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
$permittedFields[] = IAccountManager::PROPERTY_WEBSITE; $permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
$permittedFields[] = IAccountManager::PROPERTY_TWITTER; $permittedFields[] = IAccountManager::PROPERTY_TWITTER;
$permittedFields[] = IAccountManager::PROPERTY_PHONE . self::SCOPE_SUFFIX;
$permittedFields[] = IAccountManager::PROPERTY_ADDRESS . self::SCOPE_SUFFIX;
$permittedFields[] = IAccountManager::PROPERTY_WEBSITE . self::SCOPE_SUFFIX;
$permittedFields[] = IAccountManager::PROPERTY_TWITTER . 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())) {
@ -671,6 +684,23 @@ 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:
$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

@ -144,6 +144,37 @@ 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)
) {
// v2-private is not available for these fields
throw new \InvalidArgumentException('scope');
}
// 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) {
@ -405,7 +436,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();
} }
} }

View File

@ -128,7 +128,7 @@ class AccountProperty implements IAccountProperty {
return $this->scope; return $this->scope;
} }
private function mapScopeToV2($scope) { public static function mapScopeToV2($scope) {
if (strpos($scope, 'v2-') === 0) { if (strpos($scope, 'v2-') === 0) {
return $scope; return $scope;
} }