mirror of
https://github.com/nextcloud/server.git
synced 2026-02-03 20:41:22 -05:00
feat: Allow user backends to manage property permissions
Not yet reflected in the UI apart from displayname/email/avatar Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
This commit is contained in:
parent
34c2125217
commit
de011ee668
9 changed files with 122 additions and 113 deletions
|
|
@ -770,33 +770,20 @@ class UsersController extends AUserDataOCSController {
|
|||
$targetUser = $currentLoggedInUser;
|
||||
}
|
||||
|
||||
$allowDisplayNameChange = $this->config->getSystemValue('allow_user_to_change_display_name', true);
|
||||
if ($allowDisplayNameChange === true && (
|
||||
$targetUser->getBackend() instanceof ISetDisplayNameBackend
|
||||
|| $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)
|
||||
)) {
|
||||
$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
|
||||
foreach (IAccountManager::ALLOWED_PROPERTIES as $property) {
|
||||
if ($property === IAccountManager::PROPERTY_AVATAR) {
|
||||
continue;
|
||||
}
|
||||
if (!$targetUser->canEditProperty($property)) {
|
||||
continue;
|
||||
}
|
||||
$permittedFields[] = $property;
|
||||
}
|
||||
|
||||
// Fallback to display name value to avoid changing behavior with the new option.
|
||||
if ($this->config->getSystemValue('allow_user_to_change_email', $allowDisplayNameChange)) {
|
||||
$permittedFields[] = IAccountManager::PROPERTY_EMAIL;
|
||||
if ($targetUser->canEditProperty(IAccountManager::COLLECTION_EMAIL)) {
|
||||
$permittedFields[] = IAccountManager::COLLECTION_EMAIL;
|
||||
}
|
||||
|
||||
$permittedFields[] = IAccountManager::COLLECTION_EMAIL;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_PHONE;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_TWITTER;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_BLUESKY;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_ORGANISATION;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_ROLE;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_HEADLINE;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_PRONOUNS;
|
||||
|
||||
return new DataResponse($permittedFields);
|
||||
}
|
||||
|
||||
|
|
@ -841,7 +828,9 @@ class UsersController extends AUserDataOCSController {
|
|||
$permittedFields = [];
|
||||
if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
|
||||
// Editing self (display, email)
|
||||
$permittedFields[] = IAccountManager::COLLECTION_EMAIL;
|
||||
if ($targetUser->canEditProperty(IAccountManager::COLLECTION_EMAIL)) {
|
||||
$permittedFields[] = IAccountManager::COLLECTION_EMAIL;
|
||||
}
|
||||
$permittedFields[] = IAccountManager::COLLECTION_EMAIL . self::SCOPE_SUFFIX;
|
||||
} else {
|
||||
// Check if admin / subadmin
|
||||
|
|
@ -933,23 +922,10 @@ class UsersController extends AUserDataOCSController {
|
|||
|
||||
$permittedFields = [];
|
||||
if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
|
||||
$allowDisplayNameChange = $this->config->getSystemValue('allow_user_to_change_display_name', true);
|
||||
if ($allowDisplayNameChange !== false && (
|
||||
$targetUser->getBackend() instanceof ISetDisplayNameBackend
|
||||
|| $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)
|
||||
)) {
|
||||
if ($targetUser->canChangeDisplayName()) {
|
||||
$permittedFields[] = self::USER_FIELD_DISPLAYNAME;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
|
||||
}
|
||||
|
||||
// Fallback to display name value to avoid changing behavior with the new option.
|
||||
if ($this->config->getSystemValue('allow_user_to_change_email', $allowDisplayNameChange)) {
|
||||
$permittedFields[] = IAccountManager::PROPERTY_EMAIL;
|
||||
}
|
||||
|
||||
$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME . self::SCOPE_SUFFIX;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_EMAIL . self::SCOPE_SUFFIX;
|
||||
|
||||
$permittedFields[] = IAccountManager::COLLECTION_EMAIL;
|
||||
|
||||
$permittedFields[] = self::USER_FIELD_PASSWORD;
|
||||
|
|
@ -972,34 +948,16 @@ class UsersController extends AUserDataOCSController {
|
|||
$permittedFields[] = self::USER_FIELD_FIRST_DAY_OF_WEEK;
|
||||
}
|
||||
|
||||
$permittedFields[] = IAccountManager::PROPERTY_PHONE;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_TWITTER;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_BLUESKY;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_ORGANISATION;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_ROLE;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_HEADLINE;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_BIRTHDATE;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_PRONOUNS;
|
||||
|
||||
$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;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_BLUESKY . self::SCOPE_SUFFIX;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE . self::SCOPE_SUFFIX;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_ORGANISATION . self::SCOPE_SUFFIX;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_ROLE . self::SCOPE_SUFFIX;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_HEADLINE . self::SCOPE_SUFFIX;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY . self::SCOPE_SUFFIX;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED . self::SCOPE_SUFFIX;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_BIRTHDATE . self::SCOPE_SUFFIX;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX;
|
||||
$permittedFields[] = IAccountManager::PROPERTY_PRONOUNS . self::SCOPE_SUFFIX;
|
||||
foreach (IAccountManager::ALLOWED_PROPERTIES as $property) {
|
||||
$permittedFields[] = $property . self::SCOPE_SUFFIX;
|
||||
if ($property === IAccountManager::PROPERTY_AVATAR) {
|
||||
continue;
|
||||
}
|
||||
if (!$targetUser->canEditProperty($property)) {
|
||||
continue;
|
||||
}
|
||||
$permittedFields[] = $property;
|
||||
}
|
||||
|
||||
// If admin they can edit their own quota and manager
|
||||
$isAdmin = $this->groupManager->isAdmin($currentLoggedInUser->getUID());
|
||||
|
|
|
|||
|
|
@ -422,10 +422,8 @@ class UsersController extends Controller {
|
|||
IAccountManager::PROPERTY_BIRTHDATE => ['value' => $birthdate, 'scope' => $birthdateScope],
|
||||
IAccountManager::PROPERTY_PRONOUNS => ['value' => $pronouns, 'scope' => $pronounsScope],
|
||||
];
|
||||
$allowUserToChangeDisplayName = $this->config->getSystemValueBool('allow_user_to_change_display_name', true);
|
||||
foreach ($updatable as $property => $data) {
|
||||
if ($allowUserToChangeDisplayName === false
|
||||
&& in_array($property, [IAccountManager::PROPERTY_DISPLAYNAME, IAccountManager::PROPERTY_EMAIL], true)) {
|
||||
if (!$user->canEditProperty($property)) {
|
||||
continue;
|
||||
}
|
||||
$property = $userAccount->getProperty($property);
|
||||
|
|
|
|||
|
|
@ -337,6 +337,16 @@ class UsersControllerTest extends \Test\TestCase {
|
|||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('johndoe');
|
||||
|
||||
$user->expects($this->atLeastOnce())
|
||||
->method('canEditProperty')
|
||||
->willReturnCallback(
|
||||
fn (string $property): bool => match($property) {
|
||||
IAccountManager::PROPERTY_EMAIL,
|
||||
IAccountManager::PROPERTY_DISPLAYNAME => false,
|
||||
default => true,
|
||||
}
|
||||
);
|
||||
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
|
||||
/** @var MockObject|IAccount $userAccount */
|
||||
|
|
@ -378,11 +388,6 @@ class UsersControllerTest extends \Test\TestCase {
|
|||
$emailProperty->expects($this->never())
|
||||
->method('setScope');
|
||||
|
||||
$this->config->expects($this->once())
|
||||
->method('getSystemValueBool')
|
||||
->with('allow_user_to_change_display_name')
|
||||
->willReturn(false);
|
||||
|
||||
$this->appManager->expects($this->any())
|
||||
->method('isEnabledForUser')
|
||||
->with('federatedfilesharing')
|
||||
|
|
|
|||
|
|
@ -1013,6 +1013,7 @@ return array(
|
|||
'OCP\\User\\Backend\\ILimitAwareCountUsersBackend' => $baseDir . '/lib/public/User/Backend/ILimitAwareCountUsersBackend.php',
|
||||
'OCP\\User\\Backend\\IPasswordConfirmationBackend' => $baseDir . '/lib/public/User/Backend/IPasswordConfirmationBackend.php',
|
||||
'OCP\\User\\Backend\\IPasswordHashBackend' => $baseDir . '/lib/public/User/Backend/IPasswordHashBackend.php',
|
||||
'OCP\\User\\Backend\\IPropertyPermissionBackend' => $baseDir . '/lib/public/User/Backend/IPropertyPermissionBackend.php',
|
||||
'OCP\\User\\Backend\\IProvideAvatarBackend' => $baseDir . '/lib/public/User/Backend/IProvideAvatarBackend.php',
|
||||
'OCP\\User\\Backend\\IProvideEnabledStateBackend' => $baseDir . '/lib/public/User/Backend/IProvideEnabledStateBackend.php',
|
||||
'OCP\\User\\Backend\\ISearchKnownUsersBackend' => $baseDir . '/lib/public/User/Backend/ISearchKnownUsersBackend.php',
|
||||
|
|
|
|||
|
|
@ -1054,6 +1054,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OCP\\User\\Backend\\ILimitAwareCountUsersBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ILimitAwareCountUsersBackend.php',
|
||||
'OCP\\User\\Backend\\IPasswordConfirmationBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IPasswordConfirmationBackend.php',
|
||||
'OCP\\User\\Backend\\IPasswordHashBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IPasswordHashBackend.php',
|
||||
'OCP\\User\\Backend\\IPropertyPermissionBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IPropertyPermissionBackend.php',
|
||||
'OCP\\User\\Backend\\IProvideAvatarBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IProvideAvatarBackend.php',
|
||||
'OCP\\User\\Backend\\IProvideEnabledStateBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IProvideEnabledStateBackend.php',
|
||||
'OCP\\User\\Backend\\ISearchKnownUsersBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ISearchKnownUsersBackend.php',
|
||||
|
|
|
|||
|
|
@ -100,15 +100,15 @@ class LazyUser implements IUser {
|
|||
return $this->getUser()->getBackend();
|
||||
}
|
||||
|
||||
public function canChangeAvatar() {
|
||||
public function canChangeAvatar(): bool {
|
||||
return $this->getUser()->canChangeAvatar();
|
||||
}
|
||||
|
||||
public function canChangePassword() {
|
||||
public function canChangePassword(): bool {
|
||||
return $this->getUser()->canChangePassword();
|
||||
}
|
||||
|
||||
public function canChangeDisplayName() {
|
||||
public function canChangeDisplayName(): bool {
|
||||
return $this->getUser()->canChangeDisplayName();
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +116,10 @@ class LazyUser implements IUser {
|
|||
return $this->getUser()->canChangeEmail();
|
||||
}
|
||||
|
||||
public function canEditProperty(string $property): bool {
|
||||
return $this->getUser()->canEditProperty($property);
|
||||
}
|
||||
|
||||
public function isEnabled() {
|
||||
return $this->getUser()->isEnabled();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ use OCP\IUserBackend;
|
|||
use OCP\Notification\IManager as INotificationManager;
|
||||
use OCP\User\Backend\IGetHomeBackend;
|
||||
use OCP\User\Backend\IPasswordHashBackend;
|
||||
use OCP\User\Backend\IPropertyPermissionBackend;
|
||||
use OCP\User\Backend\IProvideAvatarBackend;
|
||||
use OCP\User\Backend\IProvideEnabledStateBackend;
|
||||
use OCP\User\Backend\ISetDisplayNameBackend;
|
||||
|
|
@ -413,44 +414,50 @@ class User implements IUser {
|
|||
return $this->backend;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the backend allows the user to change their avatar on Personal page
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canChangeAvatar() {
|
||||
if ($this->backend instanceof IProvideAvatarBackend || $this->backend->implementsActions(Backend::PROVIDE_AVATAR)) {
|
||||
/** @var IProvideAvatarBackend $backend */
|
||||
$backend = $this->backend;
|
||||
return $backend->canChangeAvatar($this->uid);
|
||||
}
|
||||
return true;
|
||||
public function canChangeAvatar(): bool {
|
||||
return $this->canEditProperty(IAccountManager::PROPERTY_AVATAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the backend supports changing passwords
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canChangePassword() {
|
||||
public function canChangePassword(): bool {
|
||||
return $this->backend->implementsActions(Backend::SET_PASSWORD);
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the backend supports changing display names
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canChangeDisplayName() {
|
||||
if (!$this->config->getSystemValueBool('allow_user_to_change_display_name', true)) {
|
||||
return false;
|
||||
}
|
||||
return $this->backend->implementsActions(Backend::SET_DISPLAYNAME);
|
||||
public function canChangeDisplayName(): bool {
|
||||
return $this->canEditProperty(IAccountManager::PROPERTY_DISPLAYNAME);
|
||||
}
|
||||
|
||||
public function canChangeEmail(): bool {
|
||||
// Fallback to display name value to avoid changing behavior with the new option.
|
||||
return $this->config->getSystemValueBool('allow_user_to_change_email', $this->config->getSystemValueBool('allow_user_to_change_display_name', true));
|
||||
return $this->canEditProperty(IAccountManager::PROPERTY_EMAIL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IAccountManager::PROPERTY_*|IAccountManager::COLLECTION_* $property
|
||||
*/
|
||||
public function canEditProperty(string $property): bool {
|
||||
if ($this->backend instanceof IPropertyPermissionBackend) {
|
||||
$permission = $this->backend->canEditProperty($this->uid, $property);
|
||||
if (!$permission) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
switch ($property) {
|
||||
case IAccountManager::PROPERTY_DISPLAYNAME:
|
||||
if (!$this->config->getSystemValueBool('allow_user_to_change_display_name', true)) {
|
||||
return false;
|
||||
}
|
||||
return $this->backend->implementsActions(Backend::SET_DISPLAYNAME);
|
||||
case IAccountManager::PROPERTY_AVATAR:
|
||||
if ($this->backend instanceof IProvideAvatarBackend || $this->backend->implementsActions(Backend::PROVIDE_AVATAR)) {
|
||||
/** @var IProvideAvatarBackend $backend */
|
||||
$backend = $this->backend;
|
||||
return $backend->canChangeAvatar($this->uid);
|
||||
}
|
||||
return true;
|
||||
case IAccountManager::PROPERTY_EMAIL:
|
||||
return $this->config->getSystemValueBool('allow_user_to_change_email', $this->config->getSystemValueBool('allow_user_to_change_display_name', true));
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -8,12 +8,15 @@
|
|||
namespace OCP;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use OCP\Accounts\IAccountManager;
|
||||
use OCP\AppFramework\Attribute\Consumable;
|
||||
|
||||
/**
|
||||
* Interface IUser
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
#[Consumable(since: '8.0.0')]
|
||||
interface IUser {
|
||||
/**
|
||||
* @since 32.0.0
|
||||
|
|
@ -133,26 +136,23 @@ interface IUser {
|
|||
/**
|
||||
* check if the backend allows the user to change their avatar on Personal page
|
||||
*
|
||||
* @return bool
|
||||
* @since 8.0.0
|
||||
*/
|
||||
public function canChangeAvatar();
|
||||
public function canChangeAvatar(): bool;
|
||||
|
||||
/**
|
||||
* check if the backend supports changing passwords
|
||||
*
|
||||
* @return bool
|
||||
* @since 8.0.0
|
||||
*/
|
||||
public function canChangePassword();
|
||||
public function canChangePassword(): bool;
|
||||
|
||||
/**
|
||||
* check if the backend supports changing display names
|
||||
*
|
||||
* @return bool
|
||||
* @since 8.0.0
|
||||
*/
|
||||
public function canChangeDisplayName();
|
||||
public function canChangeDisplayName(): bool;
|
||||
|
||||
/**
|
||||
* Check if the backend supports changing email
|
||||
|
|
@ -161,6 +161,12 @@ interface IUser {
|
|||
*/
|
||||
public function canChangeEmail(): bool;
|
||||
|
||||
/**
|
||||
* @param IAccountManager::PROPERTY_*|IAccountManager::COLLECTION_* $property
|
||||
* @since 34.0.0
|
||||
*/
|
||||
public function canEditProperty(string $property): bool;
|
||||
|
||||
/**
|
||||
* check if the user is enabled
|
||||
*
|
||||
|
|
|
|||
29
lib/public/User/Backend/IPropertyPermissionBackend.php
Normal file
29
lib/public/User/Backend/IPropertyPermissionBackend.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCP\User\Backend;
|
||||
|
||||
use OCP\Accounts\IAccountManager;
|
||||
use OCP\AppFramework\Attribute\Consumable;
|
||||
use OCP\AppFramework\Attribute\Implementable;
|
||||
|
||||
/**
|
||||
* @since 34.0.0
|
||||
*/
|
||||
#[Implementable(since: '34.0.0')]
|
||||
#[Consumable(since: '34.0.0')]
|
||||
interface IPropertyPermissionBackend {
|
||||
/**
|
||||
* @since 34.0.0
|
||||
*
|
||||
* @param IAccountManager::PROPERTY_*|IAccountManager::COLLECTION_* $property
|
||||
* @return bool Whether the user is allowed to edit its own property
|
||||
*/
|
||||
public function canEditProperty(string $uid, string $property): bool;
|
||||
}
|
||||
Loading…
Reference in a new issue