diff options
author | John Molakvoæ <skjnldsv@users.noreply.github.com> | 2021-09-10 08:34:20 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-10 08:34:20 +0200 |
commit | 78d62063ff0617ca594bd1a45c5be3ff592b206c (patch) | |
tree | f862af71ee9c4becc4fdc8124ea59c2124b2186b /apps | |
parent | 347b59f2fd43c3299c1cddbb17ec5d49a12ede4a (diff) | |
parent | 763136ab48cc54fda27fa137392020fda900e114 (diff) | |
download | nextcloud-server-78d62063ff0617ca594bd1a45c5be3ff592b206c.tar.gz nextcloud-server-78d62063ff0617ca594bd1a45c5be3ff592b206c.zip |
Merge pull request #28422 from nextcloud/enh/27465/notification-email
Diffstat (limited to 'apps')
16 files changed, 262 insertions, 76 deletions
diff --git a/apps/dav/lib/Connector/Sabre/Principal.php b/apps/dav/lib/Connector/Sabre/Principal.php index 4a422fa9628..8002f963798 100644 --- a/apps/dav/lib/Connector/Sabre/Principal.php +++ b/apps/dav/lib/Connector/Sabre/Principal.php @@ -300,16 +300,13 @@ class Principal implements BackendInterface { if (!$allowEnumeration) { if ($allowEnumerationFullMatch) { $users = $this->userManager->getByEmail($value); - $users = \array_filter($users, static function (IUser $user) use ($value) { - return $user->getEMailAddress() === $value; - }); } else { $users = []; } } else { $users = $this->userManager->getByEmail($value); $users = \array_filter($users, function (IUser $user) use ($currentUser, $value, $limitEnumerationPhone, $limitEnumerationGroup, $allowEnumerationFullMatch, $currentUserGroups) { - if ($allowEnumerationFullMatch && $user->getEMailAddress() === $value) { + if ($allowEnumerationFullMatch && $user->getSystemEMailAddress() === $value) { return true; } @@ -516,7 +513,7 @@ class Principal implements BackendInterface { '{http://nextcloud.com/ns}language' => $this->languageFactory->getUserLanguage($user), ]; - $email = $user->getEMailAddress(); + $email = $user->getSystemEMailAddress(); if (!empty($email)) { $principal['{http://sabredav.org/ns}email-address'] = $email; } diff --git a/apps/dav/lib/DAV/GroupPrincipalBackend.php b/apps/dav/lib/DAV/GroupPrincipalBackend.php index 34ac26d7d97..6317fc59cc2 100644 --- a/apps/dav/lib/DAV/GroupPrincipalBackend.php +++ b/apps/dav/lib/DAV/GroupPrincipalBackend.php @@ -331,7 +331,7 @@ class GroupPrincipalBackend implements BackendInterface { '{urn:ietf:params:xml:ns:caldav}calendar-user-type' => 'INDIVIDUAL', ]; - $email = $user->getEMailAddress(); + $email = $user->getSystemEMailAddress(); if (!empty($email)) { $principal['{http://sabredav.org/ns}email-address'] = $email; } diff --git a/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php index ba65edf2dd1..d7c074c9e3b 100644 --- a/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php @@ -120,7 +120,7 @@ class PrincipalTest extends TestCase { ->willReturn('Dr. Foo-Bar'); $fooUser ->expects($this->once()) - ->method('getEMailAddress') + ->method('getSystemEMailAddress') ->willReturn(''); $barUser = $this->createMock(User::class); $barUser @@ -129,7 +129,7 @@ class PrincipalTest extends TestCase { ->willReturn('bar'); $barUser ->expects($this->once()) - ->method('getEMailAddress') + ->method('getSystemEMailAddress') ->willReturn('bar@nextcloud.com'); $this->userManager ->expects($this->once()) @@ -205,7 +205,7 @@ class PrincipalTest extends TestCase { $fooUser = $this->createMock(User::class); $fooUser ->expects($this->once()) - ->method('getEMailAddress') + ->method('getSystemEMailAddress') ->willReturn('foo@nextcloud.com'); $fooUser ->expects($this->once()) @@ -605,15 +605,15 @@ class PrincipalTest extends TestCase { $user2 = $this->createMock(IUser::class); $user2->method('getUID')->willReturn('user2'); $user2->method('getDisplayName')->willReturn('User 2'); - $user2->method('getEMailAddress')->willReturn('user2@foo.bar'); + $user2->method('getSystemEMailAddress')->willReturn('user2@foo.bar'); $user3 = $this->createMock(IUser::class); $user3->method('getUID')->willReturn('user3'); $user2->method('getDisplayName')->willReturn('User 22'); - $user2->method('getEMailAddress')->willReturn('user2@foo.bar123'); + $user2->method('getSystemEMailAddress')->willReturn('user2@foo.bar123'); $user4 = $this->createMock(IUser::class); $user4->method('getUID')->willReturn('user4'); $user2->method('getDisplayName')->willReturn('User 222'); - $user2->method('getEMailAddress')->willReturn('user2@foo.bar456'); + $user2->method('getSystemEMailAddress')->willReturn('user2@foo.bar456'); $this->userManager->expects($this->at(0)) ->method('searchDisplayName') @@ -665,20 +665,20 @@ class PrincipalTest extends TestCase { $user2 = $this->createMock(IUser::class); $user2->method('getUID')->willReturn('user2'); $user2->method('getDisplayName')->willReturn('User 2'); - $user2->method('getEMailAddress')->willReturn('user2@foo.bar'); + $user2->method('getSystemEMailAddress')->willReturn('user2@foo.bar'); $user3 = $this->createMock(IUser::class); $user3->method('getUID')->willReturn('user3'); $user2->method('getDisplayName')->willReturn('User 22'); - $user2->method('getEMailAddress')->willReturn('user2@foo.bar123'); + $user2->method('getSystemEMailAddress')->willReturn('user2@foo.bar123'); $user4 = $this->createMock(IUser::class); $user4->method('getUID')->willReturn('user4'); $user2->method('getDisplayName')->willReturn('User 222'); - $user2->method('getEMailAddress')->willReturn('user2@foo.bar456'); + $user2->method('getSystemEMailAddress')->willReturn('user2@foo.bar456'); - $this->userManager->expects($this->at(0)) + $this->userManager->expects($this->once()) ->method('getByEmail') ->with('user2@foo.bar') - ->willReturn([$user2, $user3, $user4]); + ->willReturn([$user2]); $this->assertEquals(['principals/users/user2'], $this->connector->searchPrincipals('principals/users', ['{http://sabredav.org/ns}email-address' => 'user2@foo.bar'])); @@ -726,15 +726,15 @@ class PrincipalTest extends TestCase { $user2 = $this->createMock(IUser::class); $user2->method('getUID')->willReturn('user2'); $user2->method('getDisplayName')->willReturn('User 2'); - $user2->method('getEMailAddress')->willReturn('user2@foo.bar'); + $user2->method('getSystemEMailAddress')->willReturn('user2@foo.bar'); $user3 = $this->createMock(IUser::class); $user3->method('getUID')->willReturn('user3'); $user3->method('getDisplayName')->willReturn('User 22'); - $user3->method('getEMailAddress')->willReturn('user2@foo.bar123'); + $user3->method('getSystemEMailAddress')->willReturn('user2@foo.bar123'); $user4 = $this->createMock(IUser::class); $user4->method('getUID')->willReturn('user4'); $user4->method('getDisplayName')->willReturn('User 222'); - $user4->method('getEMailAddress')->willReturn('user2@foo.bar456'); + $user4->method('getSystemEMailAddress')->willReturn('user2@foo.bar456'); $this->userSession->expects($this->at(0)) @@ -787,15 +787,15 @@ class PrincipalTest extends TestCase { $user2 = $this->createMock(IUser::class); $user2->method('getUID')->willReturn('user2'); $user2->method('getDisplayName')->willReturn('User 2'); - $user2->method('getEMailAddress')->willReturn('user2@foo.bar'); + $user2->method('getSystemEMailAddress')->willReturn('user2@foo.bar'); $user3 = $this->createMock(IUser::class); $user3->method('getUID')->willReturn('user3'); $user3->method('getDisplayName')->willReturn('User 22'); - $user3->method('getEMailAddress')->willReturn('user2@foo.bar123'); + $user3->method('getSystemEMailAddress')->willReturn('user2@foo.bar123'); $user4 = $this->createMock(IUser::class); $user4->method('getUID')->willReturn('user4'); $user4->method('getDisplayName')->willReturn('User 222'); - $user4->method('getEMailAddress')->willReturn('user2@foo.bar456'); + $user4->method('getSystemEMailAddress')->willReturn('user2@foo.bar456'); $this->userSession->expects($this->at(0)) diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php index 668bbb7cca8..c9853f1e12c 100644 --- a/apps/files_sharing/lib/Controller/ShareAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareAPIController.php @@ -238,7 +238,7 @@ class ShareAPIController extends OCSController { $result['share_with'] = $share->getSharedWith(); $result['share_with_displayname'] = $sharedWith !== null ? $sharedWith->getDisplayName() : $share->getSharedWith(); $result['share_with_displayname_unique'] = $sharedWith !== null ? ( - $sharedWith->getEMailAddress() !== '' ? $sharedWith->getEMailAddress() : $sharedWith->getUID() + !empty($sharedWith->getSystemEMailAddress()) ? $sharedWith->getSystemEMailAddress() : $sharedWith->getUID() ) : $share->getSharedWith(); $result['status'] = []; diff --git a/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php b/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php index 411496f7a27..86a7d479899 100644 --- a/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php +++ b/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php @@ -47,6 +47,7 @@ use OCP\Files\Mount\IMountPoint; use OCP\Files\NotFoundException; use OCP\Files\Storage; use OCP\IConfig; +use OCP\IGroup; use OCP\IGroupManager; use OCP\IL10N; use OCP\IPreview; @@ -785,7 +786,7 @@ class ShareAPIControllerTest extends TestCase { $user = $this->getMockBuilder(IUser::class)->getMock(); $user->method('getUID')->willReturn('userId'); $user->method('getDisplayName')->willReturn('userDisplay'); - $user->method('getEMailAddress')->willReturn('userId@example.com'); + $user->method('getSystemEMailAddress')->willReturn('userId@example.com'); $group = $this->getMockBuilder('OCP\IGroup')->getMock(); $group->method('getGID')->willReturn('groupId'); @@ -3586,7 +3587,7 @@ class ShareAPIControllerTest extends TestCase { $initiator->method('getDisplayName')->willReturn('initiatorDN'); $recipient = $this->getMockBuilder(IUser::class)->getMock(); $recipient->method('getDisplayName')->willReturn('recipientDN'); - $recipient->method('getEmailAddress')->willReturn('recipient'); + $recipient->method('getSystemEMailAddress')->willReturn('recipient'); $result = []; @@ -4387,7 +4388,7 @@ class ShareAPIControllerTest extends TestCase { public function testFormatShare(array $expects, \OCP\Share\IShare $share, array $users, $exception) { $this->userManager->method('get')->willReturnMap($users); - $recipientGroup = $this->createMock('\OCP\IGroup'); + $recipientGroup = $this->createMock(IGroup::class); $recipientGroup->method('getDisplayName')->willReturn('recipientGroupDisplayName'); $this->groupManager->method('get')->willReturnMap([ ['recipientGroup', $recipientGroup], @@ -4397,7 +4398,6 @@ class ShareAPIControllerTest extends TestCase { ->with('files_sharing.sharecontroller.showShare', ['token' => 'myToken']) ->willReturn('myLink'); - $this->rootFolder->method('getUserFolder') ->with($this->currentUser) ->willReturnSelf(); diff --git a/apps/provisioning_api/appinfo/routes.php b/apps/provisioning_api/appinfo/routes.php index 2f981e0c924..54d550260b8 100644 --- a/apps/provisioning_api/appinfo/routes.php +++ b/apps/provisioning_api/appinfo/routes.php @@ -74,4 +74,9 @@ return [ ['name' => 'AppConfig#setValue', 'url' => '/api/v1/config/apps/{app}/{key}', 'verb' => 'POST'], ['name' => 'AppConfig#deleteKey', 'url' => '/api/v1/config/apps/{app}/{key}', 'verb' => 'DELETE'], ], + 'routes' => [ + // Verification + ['name' => 'Verification#showVerifyMail', 'url' => '/mailVerification/{key}/{token}/{userId}', 'verb' => 'GET'], + ['name' => 'Verification#verifyMail', 'url' => '/mailVerification/{key}/{token}/{userId}', 'verb' => 'POST'], + ] ]; diff --git a/apps/provisioning_api/composer/composer/autoload_classmap.php b/apps/provisioning_api/composer/composer/autoload_classmap.php index 22927806e65..447f92afc8d 100644 --- a/apps/provisioning_api/composer/composer/autoload_classmap.php +++ b/apps/provisioning_api/composer/composer/autoload_classmap.php @@ -14,6 +14,7 @@ return array( 'OCA\\Provisioning_API\\Controller\\AppsController' => $baseDir . '/../lib/Controller/AppsController.php', 'OCA\\Provisioning_API\\Controller\\GroupsController' => $baseDir . '/../lib/Controller/GroupsController.php', 'OCA\\Provisioning_API\\Controller\\UsersController' => $baseDir . '/../lib/Controller/UsersController.php', + 'OCA\\Provisioning_API\\Controller\\VerificationController' => $baseDir . '/../lib/Controller/VerificationController.php', 'OCA\\Provisioning_API\\FederatedShareProviderFactory' => $baseDir . '/../lib/FederatedShareProviderFactory.php', 'OCA\\Provisioning_API\\Listener\\UserDeletedListener' => $baseDir . '/../lib/Listener/UserDeletedListener.php', 'OCA\\Provisioning_API\\Middleware\\Exceptions\\NotSubAdminException' => $baseDir . '/../lib/Middleware/Exceptions/NotSubAdminException.php', diff --git a/apps/provisioning_api/composer/composer/autoload_static.php b/apps/provisioning_api/composer/composer/autoload_static.php index f5a4b73f4f8..6dbf6b45c79 100644 --- a/apps/provisioning_api/composer/composer/autoload_static.php +++ b/apps/provisioning_api/composer/composer/autoload_static.php @@ -29,6 +29,7 @@ class ComposerStaticInitProvisioning_API 'OCA\\Provisioning_API\\Controller\\AppsController' => __DIR__ . '/..' . '/../lib/Controller/AppsController.php', 'OCA\\Provisioning_API\\Controller\\GroupsController' => __DIR__ . '/..' . '/../lib/Controller/GroupsController.php', 'OCA\\Provisioning_API\\Controller\\UsersController' => __DIR__ . '/..' . '/../lib/Controller/UsersController.php', + 'OCA\\Provisioning_API\\Controller\\VerificationController' => __DIR__ . '/..' . '/../lib/Controller/VerificationController.php', 'OCA\\Provisioning_API\\FederatedShareProviderFactory' => __DIR__ . '/..' . '/../lib/FederatedShareProviderFactory.php', 'OCA\\Provisioning_API\\Listener\\UserDeletedListener' => __DIR__ . '/..' . '/../lib/Listener/UserDeletedListener.php', 'OCA\\Provisioning_API\\Middleware\\Exceptions\\NotSubAdminException' => __DIR__ . '/..' . '/../lib/Middleware/Exceptions/NotSubAdminException.php', diff --git a/apps/provisioning_api/composer/composer/installed.php b/apps/provisioning_api/composer/composer/installed.php index b99ca67ef3a..561b3105cde 100644 --- a/apps/provisioning_api/composer/composer/installed.php +++ b/apps/provisioning_api/composer/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), - 'reference' => 'fa56c13484afa1baf908b93ed5b6990c6a0e9ad6', + 'reference' => '2e49000abb5acb09de041369a2239db23fa63ec7', 'name' => '__root__', 'dev' => false, ), @@ -16,7 +16,7 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), - 'reference' => 'fa56c13484afa1baf908b93ed5b6990c6a0e9ad6', + 'reference' => '2e49000abb5acb09de041369a2239db23fa63ec7', 'dev_requirement' => false, ), ), diff --git a/apps/provisioning_api/lib/Controller/AUserData.php b/apps/provisioning_api/lib/Controller/AUserData.php index e358d282061..5bb62f2b7dc 100644 --- a/apps/provisioning_api/lib/Controller/AUserData.php +++ b/apps/provisioning_api/lib/Controller/AUserData.php @@ -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,14 +146,14 @@ 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) { $data[IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX] = $userAccount->getProperty(IAccountManager::PROPERTY_AVATAR)->getScope(); } - $data[IAccountManager::PROPERTY_EMAIL] = $targetUserObject->getEMailAddress(); + $data[IAccountManager::PROPERTY_EMAIL] = $targetUserObject->getSystemEMailAddress(); if ($includeScopes) { $data[IAccountManager::PROPERTY_EMAIL . self::SCOPE_SUFFIX] = $userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getScope(); } @@ -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 ]; } diff --git a/apps/provisioning_api/lib/Controller/UsersController.php b/apps/provisioning_api/lib/Controller/UsersController.php index a0eda5848ec..dd8397a8a89 100644 --- a/apps/provisioning_api/lib/Controller/UsersController.php +++ b/apps/provisioning_api/lib/Controller/UsersController.php @@ -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', @@ -621,6 +622,10 @@ class UsersController extends AUserData { throw new OCSException('', OCSController::RESPOND_NOT_FOUND); } + $subAdminManager = $this->groupManager->getSubAdmin(); + $isAdminOrSubadmin = $this->groupManager->isAdmin($currentLoggedInUser->getUID()) + || $subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser); + $permittedFields = []; if ($targetUser->getUID() === $currentLoggedInUser->getUID()) { // Editing self (display, email) @@ -628,11 +633,8 @@ class UsersController extends AUserData { $permittedFields[] = IAccountManager::COLLECTION_EMAIL . self::SCOPE_SUFFIX; } else { // Check if admin / subadmin - $subAdminManager = $this->groupManager->getSubAdmin(); - if ($this->groupManager->isAdmin($currentLoggedInUser->getUID()) - || $subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) { + if ($isAdminOrSubadmin) { // They have permissions over the user - $permittedFields[] = IAccountManager::COLLECTION_EMAIL; } else { // No rights @@ -652,6 +654,11 @@ class UsersController extends AUserData { $mailCollection->removePropertyByValue($key); if ($value !== '') { $mailCollection->addPropertyWithDefaults($value); + $property = $mailCollection->getPropertyByValue($key); + if ($isAdminOrSubadmin && $property) { + // admin set mails are auto-verified + $property->setLocallyVerified(IAccountManager::VERIFIED); + } } $this->accountManager->updateAccount($userAccount); break; @@ -670,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 { @@ -711,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; @@ -722,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; @@ -746,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 @@ -756,19 +764,20 @@ 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; + $permittedFields[] = self::USER_FIELD_NOTIFICATION_EMAIL; } else { // No rights throw new OCSException('', OCSController::RESPOND_NOT_FOUND); @@ -780,11 +789,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)) { @@ -814,7 +823,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); @@ -824,19 +833,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); @@ -845,7 +874,7 @@ class UsersController extends AUserData { } break; case IAccountManager::COLLECTION_EMAIL: - if (filter_var($value, FILTER_VALIDATE_EMAIL) && $value !== $targetUser->getEMailAddress()) { + if (filter_var($value, FILTER_VALIDATE_EMAIL) && $value !== $targetUser->getSystemEMailAddress()) { $userAccount = $this->accountManager->getAccount($targetUser); $mailCollection = $userAccount->getPropertyCollection(IAccountManager::COLLECTION_EMAIL); foreach ($mailCollection->getProperties() as $property) { @@ -872,7 +901,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); } } @@ -895,7 +924,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); } } diff --git a/apps/provisioning_api/lib/Controller/VerificationController.php b/apps/provisioning_api/lib/Controller/VerificationController.php new file mode 100644 index 00000000000..c4ddd1e644d --- /dev/null +++ b/apps/provisioning_api/lib/Controller/VerificationController.php @@ -0,0 +1,143 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2021 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @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 <https://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Provisioning_API\Controller; + +use InvalidArgumentException; +use OC\Security\Crypto; +use OCP\Accounts\IAccountManager; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\IL10N; +use OCP\IRequest; +use OCP\IUserManager; +use OCP\IUserSession; +use OCP\Security\VerificationToken\InvalidTokenException; +use OCP\Security\VerificationToken\IVerificationToken; + +class VerificationController extends Controller { + + /** @var IVerificationToken */ + private $verificationToken; + /** @var IUserManager */ + private $userManager; + /** @var IL10N */ + private $l10n; + /** @var IUserSession */ + private $userSession; + /** @var IAccountManager */ + private $accountManager; + /** @var Crypto */ + private $crypto; + + public function __construct( + string $appName, + IRequest $request, + IVerificationToken $verificationToken, + IUserManager $userManager, + IL10N $l10n, + IUserSession $userSession, + IAccountManager $accountManager, + Crypto $crypto + ) { + parent::__construct($appName, $request); + $this->verificationToken = $verificationToken; + $this->userManager = $userManager; + $this->l10n = $l10n; + $this->userSession = $userSession; + $this->accountManager = $accountManager; + $this->crypto = $crypto; + } + + /** + * @NoCSRFRequired + * @NoAdminRequired + * @NoSubAdminRequired + */ + public function showVerifyMail(string $token, string $userId, string $key) { + if ($this->userSession->getUser()->getUID() !== $userId) { + // not a public page, hence getUser() must return an IUser + throw new InvalidArgumentException('Logged in user is not mail address owner'); + } + $email = $this->crypto->decrypt($key); + + return new TemplateResponse( + 'core', 'confirmation', [ + 'title' => $this->l10n->t('Email confirmation'), + 'message' => $this->l10n->t('To enable the email address %s please click the button below.', [$email]), + 'action' => $this->l10n->t('Confirm'), + ], TemplateResponse::RENDER_AS_GUEST); + } + + /** + * @NoAdminRequired + * @NoSubAdminRequired + */ + public function verifyMail(string $token, string $userId, string $key) { + try { + if ($this->userSession->getUser()->getUID() !== $userId) { + throw new InvalidArgumentException('Logged in user is not mail address owner'); + } + $email = $this->crypto->decrypt($key); + $ref = \substr(hash('sha256', $email), 0, 8); + + $user = $this->userManager->get($userId); + $this->verificationToken->check($token, $user, 'verifyMail' . $ref, $email); + + $userAccount = $this->accountManager->getAccount($user); + $emailProperty = $userAccount->getPropertyCollection(IAccountManager::COLLECTION_EMAIL) + ->getPropertyByValue($email); + + if ($emailProperty === null) { + throw new InvalidArgumentException($this->l10n->t('Email was already removed from account and cannot be confirmed anymore.')); + } + $emailProperty->setLocallyVerified(IAccountManager::VERIFIED); + $this->accountManager->updateAccount($userAccount); + $this->verificationToken->delete($token, $user, 'verifyMail' . $ref); + } catch (InvalidTokenException $e) { + $error = $e->getCode() === InvalidTokenException::TOKEN_EXPIRED + ? $this->l10n->t('Could not verify mail because the token is expired.') + : $this->l10n->t('Could not verify mail because the token is invalid.'); + } catch (InvalidArgumentException $e) { + $error = $e->getMessage(); + } catch (\Exception $e) { + $error = $this->l10n->t('An unexpected error occurred. Please consult your sysadmin.'); + } + + if (isset($error)) { + return new TemplateResponse( + 'core', 'error', [ + 'errors' => [['error' => $error]] + ], TemplateResponse::RENDER_AS_GUEST); + } + + return new TemplateResponse( + 'core', 'success', [ + 'title' => $this->l10n->t('Email confirmation successful'), + 'message' => $this->l10n->t('Email confirmation successful'), + ], TemplateResponse::RENDER_AS_GUEST); + } +} diff --git a/apps/provisioning_api/tests/Controller/UsersControllerTest.php b/apps/provisioning_api/tests/Controller/UsersControllerTest.php index cc638c89a63..7ae5d0c245f 100644 --- a/apps/provisioning_api/tests/Controller/UsersControllerTest.php +++ b/apps/provisioning_api/tests/Controller/UsersControllerTest.php @@ -952,7 +952,7 @@ class UsersControllerTest extends TestCase { ->disableOriginalConstructor() ->getMock(); $targetUser->expects($this->once()) - ->method('getEMailAddress') + ->method('getSystemEMailAddress') ->willReturn('demo@nextcloud.com'); $this->userSession ->expects($this->once()) @@ -1067,6 +1067,7 @@ class UsersControllerTest extends TestCase { 'setPassword' => true, ], 'additional_mail' => [], + 'notify_email' => null, ]; $this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UID'])); } @@ -1083,9 +1084,9 @@ class UsersControllerTest extends TestCase { ->disableOriginalConstructor() ->getMock(); $targetUser - ->expects($this->once()) - ->method('getEMailAddress') - ->willReturn('demo@nextcloud.com'); + ->expects($this->once()) + ->method('getSystemEMailAddress') + ->willReturn('demo@nextcloud.com'); $this->userSession ->expects($this->once()) ->method('getUser') @@ -1195,6 +1196,7 @@ class UsersControllerTest extends TestCase { 'setPassword' => true, ], 'additional_mail' => [], + 'notify_email' => null, ]; $this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UID'])); } @@ -1306,7 +1308,7 @@ class UsersControllerTest extends TestCase { ->willReturn('Subadmin User'); $targetUser ->expects($this->once()) - ->method('getEMailAddress') + ->method('getSystemEMailAddress') ->willReturn('subadmin@nextcloud.com'); $targetUser ->method('getUID') @@ -1361,6 +1363,7 @@ class UsersControllerTest extends TestCase { 'setPassword' => false, ], 'additional_mail' => [], + 'notify_email' => null, ]; $this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UID'])); } diff --git a/apps/settings/lib/Controller/UsersController.php b/apps/settings/lib/Controller/UsersController.php index f78fa7dd9b8..6be93d6a3a0 100644 --- a/apps/settings/lib/Controller/UsersController.php +++ b/apps/settings/lib/Controller/UsersController.php @@ -482,7 +482,7 @@ class UsersController extends Controller { } } - $oldEmailAddress = $userAccount->getUser()->getEMailAddress(); + $oldEmailAddress = $userAccount->getUser()->getSystemEMailAddress(); $oldEmailAddress = strtolower((string)$oldEmailAddress); if ($oldEmailAddress !== $userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue()) { // this is the only permission a backend provides and is also used @@ -490,7 +490,7 @@ class UsersController extends Controller { if (!$userAccount->getUser()->canChangeDisplayName()) { throw new ForbiddenException($this->l10n->t('Unable to change email address')); } - $userAccount->getUser()->setEMailAddress($userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue()); + $userAccount->getUser()->setSystemEMailAddress($userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue()); } try { diff --git a/apps/settings/tests/Controller/UsersControllerTest.php b/apps/settings/tests/Controller/UsersControllerTest.php index 7faca378cdf..797fa1621fa 100644 --- a/apps/settings/tests/Controller/UsersControllerTest.php +++ b/apps/settings/tests/Controller/UsersControllerTest.php @@ -621,16 +621,15 @@ class UsersControllerTest extends \Test\TestCase { $user = $this->createMock(IUser::class); $user->method('getDisplayName')->willReturn($oldDisplayName); - $user->method('getEMailAddress')->willReturn($oldEmailAddress); + $user->method('getSystemEMailAddress')->willReturn($oldEmailAddress); $user->method('canChangeDisplayName')->willReturn(true); if ($data[IAccountManager::PROPERTY_EMAIL]['value'] === $oldEmailAddress || ($oldEmailAddress === null && $data[IAccountManager::PROPERTY_EMAIL]['value'] === '')) { - $user->expects($this->never())->method('setEMailAddress'); + $user->expects($this->never())->method('setSystemEMailAddress'); } else { - $user->expects($this->once())->method('setEMailAddress') - ->with($data[IAccountManager::PROPERTY_EMAIL]['value']) - ->willReturn(true); + $user->expects($this->once())->method('setSystemEMailAddress') + ->with($data[IAccountManager::PROPERTY_EMAIL]['value']); } if ($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'] === $oldDisplayName || diff --git a/apps/user_ldap/lib/User/User.php b/apps/user_ldap/lib/User/User.php index 7d57fcbb275..b09fbd18327 100644 --- a/apps/user_ldap/lib/User/User.php +++ b/apps/user_ldap/lib/User/User.php @@ -448,7 +448,7 @@ class User { if ($email !== '') { $user = $this->userManager->get($this->uid); if (!is_null($user)) { - $currentEmail = (string)$user->getEMailAddress(); + $currentEmail = (string)$user->getSystemEMailAddress(); if ($currentEmail !== $email) { $user->setEMailAddress($email); } |