- specific getters and setters on IUser and implementation - new notify_email field in provisioning API Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>tags/v23.0.0beta1
@@ -54,6 +54,13 @@ use OCP\User\Backend\ISetPasswordBackend; | |||
abstract class AUserData extends OCSController { | |||
public const SCOPE_SUFFIX = 'Scope'; | |||
public const USER_FIELD_DISPLAYNAME = 'display'; | |||
public const USER_FIELD_LANGUAGE = 'language'; | |||
public const USER_FIELD_LOCALE = 'locale'; | |||
public const USER_FIELD_PASSWORD = 'password'; | |||
public const USER_FIELD_QUOTA = 'quota'; | |||
public const USER_FIELD_NOTIFICATION_EMAIL = 'notify_email'; | |||
/** @var IUserManager */ | |||
protected $userManager; | |||
/** @var IConfig */ | |||
@@ -139,7 +146,7 @@ abstract class AUserData extends OCSController { | |||
$data['lastLogin'] = $targetUserObject->getLastLogin() * 1000; | |||
$data['backend'] = $targetUserObject->getBackendClassName(); | |||
$data['subadmin'] = $this->getUserSubAdminGroupsData($targetUserObject->getUID()); | |||
$data['quota'] = $this->fillStorageInfo($targetUserObject->getUID()); | |||
$data[self::USER_FIELD_QUOTA] = $this->fillStorageInfo($targetUserObject->getUID()); | |||
try { | |||
if ($includeScopes) { | |||
@@ -187,8 +194,9 @@ abstract class AUserData extends OCSController { | |||
} | |||
$data['groups'] = $gids; | |||
$data['language'] = $this->l10nFactory->getUserLanguage($targetUserObject); | |||
$data['locale'] = $this->config->getUserValue($targetUserObject->getUID(), 'core', 'locale'); | |||
$data[self::USER_FIELD_LANGUAGE] = $this->l10nFactory->getUserLanguage($targetUserObject); | |||
$data[self::USER_FIELD_LOCALE] = $this->config->getUserValue($targetUserObject->getUID(), 'core', 'locale'); | |||
$data[self::USER_FIELD_NOTIFICATION_EMAIL] = $targetUserObject->getPrimaryEMailAddress(); | |||
$backend = $targetUserObject->getBackend(); | |||
$data['backendCapabilities'] = [ | |||
@@ -238,7 +246,7 @@ abstract class AUserData extends OCSController { | |||
'used' => $storage['used'], | |||
'total' => $storage['total'], | |||
'relative' => $storage['relative'], | |||
'quota' => $storage['quota'], | |||
self::USER_FIELD_QUOTA => $storage['quota'], | |||
]; | |||
} catch (NotFoundException $ex) { | |||
// User fs is not setup yet | |||
@@ -251,7 +259,7 @@ abstract class AUserData extends OCSController { | |||
$quota = OC_Helper::computerFileSize($quota); | |||
} | |||
$data = [ | |||
'quota' => $quota !== false ? $quota : 'none', | |||
self::USER_FIELD_QUOTA => $quota !== false ? $quota : 'none', | |||
'used' => 0 | |||
]; | |||
} |
@@ -42,6 +42,7 @@ declare(strict_types=1); | |||
*/ | |||
namespace OCA\Provisioning_API\Controller; | |||
use InvalidArgumentException; | |||
use libphonenumber\NumberParseException; | |||
use libphonenumber\PhoneNumber; | |||
use libphonenumber\PhoneNumberFormat; | |||
@@ -418,15 +419,15 @@ class UsersController extends AUserData { | |||
} | |||
if ($displayName !== '') { | |||
$this->editUser($userid, 'display', $displayName); | |||
$this->editUser($userid, self::USER_FIELD_DISPLAYNAME, $displayName); | |||
} | |||
if ($quota !== '') { | |||
$this->editUser($userid, 'quota', $quota); | |||
$this->editUser($userid, self::USER_FIELD_QUOTA, $quota); | |||
} | |||
if ($language !== '') { | |||
$this->editUser($userid, 'language', $language); | |||
$this->editUser($userid, self::USER_FIELD_LANGUAGE, $language); | |||
} | |||
// Send new user mail only if a mail is set | |||
@@ -466,7 +467,7 @@ class UsersController extends AUserData { | |||
] | |||
); | |||
throw $e; | |||
} catch (\InvalidArgumentException $e) { | |||
} catch (InvalidArgumentException $e) { | |||
$this->logger->error('Failed addUser attempt with invalid argument exeption.', | |||
[ | |||
'app' => 'ocs_api', | |||
@@ -676,7 +677,7 @@ class UsersController extends AUserData { | |||
try { | |||
$targetProperty->setScope($value); | |||
$this->accountManager->updateAccount($userAccount); | |||
} catch (\InvalidArgumentException $e) { | |||
} catch (InvalidArgumentException $e) { | |||
throw new OCSException('', 102); | |||
} | |||
} else { | |||
@@ -717,7 +718,7 @@ class UsersController extends AUserData { | |||
if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) { | |||
if ($targetUser->getBackend() instanceof ISetDisplayNameBackend | |||
|| $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)) { | |||
$permittedFields[] = 'display'; | |||
$permittedFields[] = self::USER_FIELD_DISPLAYNAME; | |||
$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME; | |||
} | |||
$permittedFields[] = IAccountManager::PROPERTY_EMAIL; | |||
@@ -728,15 +729,16 @@ class UsersController extends AUserData { | |||
$permittedFields[] = IAccountManager::COLLECTION_EMAIL; | |||
$permittedFields[] = 'password'; | |||
$permittedFields[] = self::USER_FIELD_PASSWORD; | |||
$permittedFields[] = self::USER_FIELD_NOTIFICATION_EMAIL; | |||
if ($this->config->getSystemValue('force_language', false) === false || | |||
$this->groupManager->isAdmin($currentLoggedInUser->getUID())) { | |||
$permittedFields[] = 'language'; | |||
$permittedFields[] = self::USER_FIELD_LANGUAGE; | |||
} | |||
if ($this->config->getSystemValue('force_locale', false) === false || | |||
$this->groupManager->isAdmin($currentLoggedInUser->getUID())) { | |||
$permittedFields[] = 'locale'; | |||
$permittedFields[] = self::USER_FIELD_LOCALE; | |||
} | |||
$permittedFields[] = IAccountManager::PROPERTY_PHONE; | |||
@@ -752,7 +754,7 @@ class UsersController extends AUserData { | |||
// If admin they can edit their own quota | |||
if ($this->groupManager->isAdmin($currentLoggedInUser->getUID())) { | |||
$permittedFields[] = 'quota'; | |||
$permittedFields[] = self::USER_FIELD_QUOTA; | |||
} | |||
} else { | |||
// Check if admin / subadmin | |||
@@ -762,19 +764,19 @@ class UsersController extends AUserData { | |||
// They have permissions over the user | |||
if ($targetUser->getBackend() instanceof ISetDisplayNameBackend | |||
|| $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)) { | |||
$permittedFields[] = 'display'; | |||
$permittedFields[] = self::USER_FIELD_DISPLAYNAME; | |||
$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME; | |||
} | |||
$permittedFields[] = IAccountManager::PROPERTY_EMAIL; | |||
$permittedFields[] = IAccountManager::COLLECTION_EMAIL; | |||
$permittedFields[] = 'password'; | |||
$permittedFields[] = 'language'; | |||
$permittedFields[] = 'locale'; | |||
$permittedFields[] = self::USER_FIELD_PASSWORD; | |||
$permittedFields[] = self::USER_FIELD_LANGUAGE; | |||
$permittedFields[] = self::USER_FIELD_LOCALE; | |||
$permittedFields[] = IAccountManager::PROPERTY_PHONE; | |||
$permittedFields[] = IAccountManager::PROPERTY_ADDRESS; | |||
$permittedFields[] = IAccountManager::PROPERTY_WEBSITE; | |||
$permittedFields[] = IAccountManager::PROPERTY_TWITTER; | |||
$permittedFields[] = 'quota'; | |||
$permittedFields[] = self::USER_FIELD_QUOTA; | |||
} else { | |||
// No rights | |||
throw new OCSException('', OCSController::RESPOND_NOT_FOUND); | |||
@@ -786,11 +788,11 @@ class UsersController extends AUserData { | |||
} | |||
// Process the edit | |||
switch ($key) { | |||
case 'display': | |||
case self::USER_FIELD_DISPLAYNAME: | |||
case IAccountManager::PROPERTY_DISPLAYNAME: | |||
$targetUser->setDisplayName($value); | |||
break; | |||
case 'quota': | |||
case self::USER_FIELD_QUOTA: | |||
$quota = $value; | |||
if ($quota !== 'none' && $quota !== 'default') { | |||
if (is_numeric($quota)) { | |||
@@ -820,7 +822,7 @@ class UsersController extends AUserData { | |||
} | |||
$targetUser->setQuota($quota); | |||
break; | |||
case 'password': | |||
case self::USER_FIELD_PASSWORD: | |||
try { | |||
if (!$targetUser->canChangePassword()) { | |||
throw new OCSException('Setting the password is not supported by the users backend', 103); | |||
@@ -830,19 +832,39 @@ class UsersController extends AUserData { | |||
throw new OCSException($e->getMessage(), 103); | |||
} | |||
break; | |||
case 'language': | |||
case self::USER_FIELD_LANGUAGE: | |||
$languagesCodes = $this->l10nFactory->findAvailableLanguages(); | |||
if (!in_array($value, $languagesCodes, true) && $value !== 'en') { | |||
throw new OCSException('Invalid language', 102); | |||
} | |||
$this->config->setUserValue($targetUser->getUID(), 'core', 'lang', $value); | |||
break; | |||
case 'locale': | |||
case self::USER_FIELD_LOCALE: | |||
if (!$this->l10nFactory->localeExists($value)) { | |||
throw new OCSException('Invalid locale', 102); | |||
} | |||
$this->config->setUserValue($targetUser->getUID(), 'core', 'locale', $value); | |||
break; | |||
case self::USER_FIELD_NOTIFICATION_EMAIL: | |||
$success = false; | |||
if ($value === '' || filter_var($value, FILTER_VALIDATE_EMAIL)) { | |||
try { | |||
$targetUser->setPrimaryEMailAddress($value); | |||
$success = true; | |||
} catch (InvalidArgumentException $e) { | |||
$this->logger->info( | |||
'Cannot set primary email, because provided address is not verified', | |||
[ | |||
'app' => 'provisioning_api', | |||
'exception' => $e, | |||
] | |||
); | |||
} | |||
} | |||
if (!$success) { | |||
throw new OCSException('', 102); | |||
} | |||
break; | |||
case IAccountManager::PROPERTY_EMAIL: | |||
if (filter_var($value, FILTER_VALIDATE_EMAIL) || $value === '') { | |||
$targetUser->setEMailAddress($value); | |||
@@ -878,7 +900,7 @@ class UsersController extends AUserData { | |||
if ($userProperty->getName() === IAccountManager::PROPERTY_PHONE) { | |||
$this->knownUserService->deleteByContactUserId($targetUser->getUID()); | |||
} | |||
} catch (\InvalidArgumentException $e) { | |||
} catch (InvalidArgumentException $e) { | |||
throw new OCSException('Invalid ' . $e->getMessage(), 102); | |||
} | |||
} | |||
@@ -901,7 +923,7 @@ class UsersController extends AUserData { | |||
try { | |||
$userProperty->setScope($value); | |||
$this->accountManager->updateAccount($userAccount); | |||
} catch (\InvalidArgumentException $e) { | |||
} catch (InvalidArgumentException $e) { | |||
throw new OCSException('Invalid ' . $e->getMessage(), 102); | |||
} | |||
} |
@@ -439,7 +439,7 @@ class Setup { | |||
// Set email for admin | |||
if (!empty($options['adminemail'])) { | |||
$config->setUserValue($user->getUID(), 'settings', 'email', $options['adminemail']); | |||
$user->setSystemEMailAddress($options['adminemail']); | |||
} | |||
} | |||
@@ -700,6 +700,7 @@ class Manager extends PublicEmitter implements IUserManager { | |||
* @since 9.1.0 | |||
*/ | |||
public function getByEmail($email) { | |||
// looking for 'email' only (and not primary_mail) is intentional | |||
$userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email); | |||
$users = array_map(function ($uid) { |
@@ -34,10 +34,12 @@ | |||
*/ | |||
namespace OC\User; | |||
use InvalidArgumentException; | |||
use OC\Accounts\AccountManager; | |||
use OC\Avatar\AvatarManager; | |||
use OC\Hooks\Emitter; | |||
use OC_Helper; | |||
use OCP\Accounts\IAccountManager; | |||
use OCP\EventDispatcher\IEventDispatcher; | |||
use OCP\Group\Events\BeforeUserRemovedEvent; | |||
use OCP\Group\Events\UserRemovedEvent; | |||
@@ -55,6 +57,8 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; | |||
use Symfony\Component\EventDispatcher\GenericEvent; | |||
class User implements IUser { | |||
/** @var IAccountManager */ | |||
protected $accountManager; | |||
/** @var string */ | |||
private $uid; | |||
@@ -165,24 +169,61 @@ class User implements IUser { | |||
} | |||
/** | |||
* set the email address of the user | |||
* | |||
* @param string|null $mailAddress | |||
* @return void | |||
* @since 9.0.0 | |||
* @inheritDoc | |||
*/ | |||
public function setEMailAddress($mailAddress) { | |||
$oldMailAddress = $this->getEMailAddress(); | |||
$this->setSystemEMailAddress($mailAddress); | |||
} | |||
/** | |||
* @inheritDoc | |||
*/ | |||
public function setSystemEMailAddress(string $mailAddress): void { | |||
$oldMailAddress = $this->getSystemEMailAddress(); | |||
if ($mailAddress === '') { | |||
$this->config->deleteUserValue($this->uid, 'settings', 'email'); | |||
} else { | |||
$this->config->setUserValue($this->uid, 'settings', 'email', $mailAddress); | |||
} | |||
$primaryAddress = $this->getPrimaryEMailAddress(); | |||
if ($primaryAddress === $mailAddress) { | |||
// on match no dedicated primary settings is necessary | |||
$this->setPrimaryEMailAddress(''); | |||
} | |||
if ($oldMailAddress !== $mailAddress) { | |||
if ($mailAddress === '') { | |||
$this->config->deleteUserValue($this->uid, 'settings', 'email'); | |||
} else { | |||
$this->config->setUserValue($this->uid, 'settings', 'email', $mailAddress); | |||
} | |||
$this->triggerChange('eMailAddress', $mailAddress, $oldMailAddress); | |||
} | |||
} | |||
/** | |||
* @inheritDoc | |||
*/ | |||
public function setPrimaryEMailAddress(string $mailAddress): void { | |||
if ($mailAddress === '') { | |||
$this->config->deleteUserValue($this->uid, 'settings', 'primary_email'); | |||
return; | |||
} | |||
$this->ensureAccountManager(); | |||
$account = $this->accountManager->getAccount($this); | |||
$property = $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL) | |||
->getPropertyByValue($mailAddress); | |||
if ($property === null || $property->getLocallyVerified() !== IAccountManager::VERIFIED) { | |||
throw new InvalidArgumentException('Only verified emails can be set as primary'); | |||
} | |||
$this->config->setUserValue($this->uid, 'settings', 'primary_email', $mailAddress); | |||
} | |||
private function ensureAccountManager() { | |||
if (!$this->accountManager instanceof IAccountManager) { | |||
$this->accountManager = \OC::$server->get(IAccountManager::class); | |||
} | |||
} | |||
/** | |||
* returns the timestamp of the user's last login or 0 if the user did never | |||
* login | |||
@@ -390,9 +431,23 @@ class User implements IUser { | |||
* @since 9.0.0 | |||
*/ | |||
public function getEMailAddress() { | |||
return $this->getPrimaryEMailAddress() ?? $this->getSystemEMailAddress(); | |||
} | |||
/** | |||
* @inheritDoc | |||
*/ | |||
public function getSystemEMailAddress(): ?string { | |||
return $this->config->getUserValue($this->uid, 'settings', 'email', null); | |||
} | |||
/** | |||
* @inheritDoc | |||
*/ | |||
public function getPrimaryEMailAddress(): ?string { | |||
return $this->config->getUserValue($this->uid, 'settings', 'primary_email', null); | |||
} | |||
/** | |||
* get the users' quota | |||
* |
@@ -27,6 +27,8 @@ | |||
*/ | |||
namespace OCP; | |||
use InvalidArgumentException; | |||
/** | |||
* Interface IUser | |||
* | |||
@@ -157,13 +159,42 @@ interface IUser { | |||
public function setEnabled(bool $enabled = true); | |||
/** | |||
* get the users email address | |||
* get the user's email address | |||
* | |||
* @return string|null | |||
* @since 9.0.0 | |||
*/ | |||
public function getEMailAddress(); | |||
/** | |||
* get the user's system email address | |||
* | |||
* The system mail address may be read only and may be set from different | |||
* sources like LDAP, SAML or simply the admin. | |||
* | |||
* Use this getter only when the system address is needed. For picking the | |||
* proper address to e.g. send a mail to, use getEMailAddress(). | |||
* | |||
* @return string|null | |||
* @since 23.0.0 | |||
*/ | |||
public function getSystemEMailAddress(): ?string; | |||
/** | |||
* get the user's preferred email address | |||
* | |||
* The primary mail address may be set be the user to specify a different | |||
* email address where mails by Nextcloud are sent to. It is not necessarily | |||
* set. | |||
* | |||
* Use this getter only when the primary address is needed. For picking the | |||
* proper address to e.g. send a mail to, use getEMailAddress(). | |||
* | |||
* @return string|null | |||
* @since 23.0.0 | |||
*/ | |||
public function getPrimaryEMailAddress(): ?string; | |||
/** | |||
* get the avatar image if it exists | |||
* | |||
@@ -184,12 +215,42 @@ interface IUser { | |||
/** | |||
* set the email address of the user | |||
* | |||
* It is an alias to setSystemEMailAddress() | |||
* | |||
* @param string|null $mailAddress | |||
* @return void | |||
* @since 9.0.0 | |||
* @deprecated 23.0.0 use setSystemEMailAddress() or setPrimaryEMailAddress() | |||
*/ | |||
public function setEMailAddress($mailAddress); | |||
/** | |||
* Set the system email address of the user | |||
* | |||
* This is supposed to be used when the email is set from different sources | |||
* (i.e. other user backends, admin). | |||
* | |||
* @since 23.0.0 | |||
*/ | |||
public function setSystemEMailAddress(string $mailAddress): void; | |||
/** | |||
* Set the primary email address of the user. | |||
* | |||
* This method should be typically called when the user is changing their | |||
* own primary address and is not allowed to change their system email. | |||
* | |||
* The mail address provided here must be already registered as an | |||
* additional mail in the user account and also be verified locally. Also | |||
* an empty string is allowed to delete this preference. | |||
* | |||
* @throws InvalidArgumentException when the provided email address does not | |||
* satisfy constraints. | |||
* | |||
* @since 23.0.0 | |||
*/ | |||
public function setPrimaryEMailAddress(string $mailAddress): void; | |||
/** | |||
* get the users' quota in human readable form. If a specific quota is not | |||
* set for the user, the default value is returned. If a default setting |
@@ -196,6 +196,8 @@ interface IUserManager { | |||
public function callForSeenUsers(\Closure $callback); | |||
/** | |||
* returns all users having the provided email set as system email address | |||
* | |||
* @param string $email | |||
* @return IUser[] | |||
* @since 9.1.0 |
@@ -676,11 +676,14 @@ class UserTest extends TestCase { | |||
$emitter->expects($this->never()) | |||
->method('emit'); | |||
$this->dispatcher->expects($this->never()) | |||
->method('dispatch'); | |||
$config = $this->createMock(IConfig::class); | |||
$config->expects($this->any()) | |||
->method('getUserValue') | |||
->willReturn('foo@bar.com'); | |||
$config->expects($this->never()) | |||
$config->expects($this->any()) | |||
->method('setUserValue'); | |||
$user = new User('foo', $backend, $this->dispatcher, $emitter, $config); |