enable the user to set a primary (notification) email address (backend)

- specific getters and setters on IUser and implementation
- new notify_email field in provisioning API

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
pull/28422/head
Arthur Schiwon 3 years ago
parent 0dee717c94
commit 4461b9e870
No known key found for this signature in database
GPG Key ID: 7424F1874854DF23

@ -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);

Loading…
Cancel
Save