mirror of
https://github.com/nextcloud/server.git
synced 2026-02-27 03:50:37 -05:00
Merge pull request #28780 from nextcloud/backport/28422/stable22
This commit is contained in:
commit
317e04dd26
46 changed files with 1422 additions and 448 deletions
|
|
@ -295,16 +295,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;
|
||||
}
|
||||
|
||||
|
|
@ -510,7 +507,7 @@ class Principal 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ class PrincipalTest extends TestCase {
|
|||
->willReturn('Dr. Foo-Bar');
|
||||
$fooUser
|
||||
->expects($this->exactly(1))
|
||||
->method('getEMailAddress')
|
||||
->method('getSystemEMailAddress')
|
||||
->willReturn('');
|
||||
$barUser = $this->createMock(User::class);
|
||||
$barUser
|
||||
|
|
@ -122,7 +122,7 @@ class PrincipalTest extends TestCase {
|
|||
->willReturn('bar');
|
||||
$barUser
|
||||
->expects($this->exactly(1))
|
||||
->method('getEMailAddress')
|
||||
->method('getSystemEMailAddress')
|
||||
->willReturn('bar@nextcloud.com');
|
||||
$this->userManager
|
||||
->expects($this->once())
|
||||
|
|
@ -183,7 +183,7 @@ class PrincipalTest extends TestCase {
|
|||
$fooUser = $this->createMock(User::class);
|
||||
$fooUser
|
||||
->expects($this->exactly(1))
|
||||
->method('getEMailAddress')
|
||||
->method('getSystemEMailAddress')
|
||||
->willReturn('foo@nextcloud.com');
|
||||
$fooUser
|
||||
->expects($this->exactly(1))
|
||||
|
|
@ -576,15 +576,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')
|
||||
|
|
@ -636,20 +636,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']));
|
||||
|
|
@ -697,15 +697,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))
|
||||
|
|
@ -758,15 +758,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))
|
||||
|
|
|
|||
|
|
@ -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'] = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../',
|
||||
'aliases' => array(),
|
||||
'reference' => 'fb5ee6087bfd1f4cc2f37cda7a7cab7072aaae86',
|
||||
'reference' => '13a9cd28a5a5d92e285df040d084d5d608e2f768',
|
||||
'name' => '__root__',
|
||||
'dev' => false,
|
||||
),
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../',
|
||||
'aliases' => array(),
|
||||
'reference' => 'fb5ee6087bfd1f4cc2f37cda7a7cab7072aaae86',
|
||||
'reference' => '13a9cd28a5a5d92e285df040d084d5d608e2f768',
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
143
apps/provisioning_api/lib/Controller/VerificationController.php
Normal file
143
apps/provisioning_api/lib/Controller/VerificationController.php
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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']));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 ||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class Info extends Base {
|
|||
$data = [
|
||||
'user_id' => $user->getUID(),
|
||||
'display_name' => $user->getDisplayName(),
|
||||
'email' => $user->getEMailAddress() ? $user->getEMailAddress() : '',
|
||||
'email' => (string)$user->getSystemEMailAddress(),
|
||||
'cloud_id' => $user->getCloudId(),
|
||||
'enabled' => $user->isEnabled(),
|
||||
'groups' => $groups,
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class ListCommand extends Base {
|
|||
return [
|
||||
'user_id' => $user->getUID(),
|
||||
'display_name' => $user->getDisplayName(),
|
||||
'email' => $user->getEMailAddress() ? $user->getEMailAddress() : '',
|
||||
'email' => (string)$user->getSystemEMailAddress(),
|
||||
'cloud_id' => $user->getCloudId(),
|
||||
'enabled' => $user->isEnabled(),
|
||||
'groups' => $groups,
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ use OC\HintException;
|
|||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Defaults;
|
||||
use OCP\Encryption\IEncryptionModule;
|
||||
use OCP\Encryption\IManager;
|
||||
|
|
@ -56,8 +55,8 @@ use OCP\IURLGenerator;
|
|||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Mail\IMailer;
|
||||
use OCP\Security\ICrypto;
|
||||
use OCP\Security\ISecureRandom;
|
||||
use OCP\Security\VerificationToken\InvalidTokenException;
|
||||
use OCP\Security\VerificationToken\IVerificationToken;
|
||||
use function reset;
|
||||
|
||||
/**
|
||||
|
|
@ -82,67 +81,46 @@ class LostController extends Controller {
|
|||
protected $encryptionManager;
|
||||
/** @var IConfig */
|
||||
protected $config;
|
||||
/** @var ISecureRandom */
|
||||
protected $secureRandom;
|
||||
/** @var IMailer */
|
||||
protected $mailer;
|
||||
/** @var ITimeFactory */
|
||||
protected $timeFactory;
|
||||
/** @var ICrypto */
|
||||
protected $crypto;
|
||||
/** @var ILogger */
|
||||
private $logger;
|
||||
/** @var Manager */
|
||||
private $twoFactorManager;
|
||||
/** @var IInitialStateService */
|
||||
private $initialStateService;
|
||||
/** @var IVerificationToken */
|
||||
private $verificationToken;
|
||||
|
||||
/**
|
||||
* @param string $appName
|
||||
* @param IRequest $request
|
||||
* @param IURLGenerator $urlGenerator
|
||||
* @param IUserManager $userManager
|
||||
* @param Defaults $defaults
|
||||
* @param IL10N $l10n
|
||||
* @param IConfig $config
|
||||
* @param ISecureRandom $secureRandom
|
||||
* @param string $defaultMailAddress
|
||||
* @param IManager $encryptionManager
|
||||
* @param IMailer $mailer
|
||||
* @param ITimeFactory $timeFactory
|
||||
* @param ICrypto $crypto
|
||||
*/
|
||||
public function __construct($appName,
|
||||
IRequest $request,
|
||||
IURLGenerator $urlGenerator,
|
||||
IUserManager $userManager,
|
||||
Defaults $defaults,
|
||||
IL10N $l10n,
|
||||
IConfig $config,
|
||||
ISecureRandom $secureRandom,
|
||||
$defaultMailAddress,
|
||||
IManager $encryptionManager,
|
||||
IMailer $mailer,
|
||||
ITimeFactory $timeFactory,
|
||||
ICrypto $crypto,
|
||||
ILogger $logger,
|
||||
Manager $twoFactorManager,
|
||||
IInitialStateService $initialStateService) {
|
||||
public function __construct(
|
||||
$appName,
|
||||
IRequest $request,
|
||||
IURLGenerator $urlGenerator,
|
||||
IUserManager $userManager,
|
||||
Defaults $defaults,
|
||||
IL10N $l10n,
|
||||
IConfig $config,
|
||||
$defaultMailAddress,
|
||||
IManager $encryptionManager,
|
||||
IMailer $mailer,
|
||||
ILogger $logger,
|
||||
Manager $twoFactorManager,
|
||||
IInitialStateService $initialStateService,
|
||||
IVerificationToken $verificationToken
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->userManager = $userManager;
|
||||
$this->defaults = $defaults;
|
||||
$this->l10n = $l10n;
|
||||
$this->secureRandom = $secureRandom;
|
||||
$this->from = $defaultMailAddress;
|
||||
$this->encryptionManager = $encryptionManager;
|
||||
$this->config = $config;
|
||||
$this->mailer = $mailer;
|
||||
$this->timeFactory = $timeFactory;
|
||||
$this->crypto = $crypto;
|
||||
$this->logger = $logger;
|
||||
$this->twoFactorManager = $twoFactorManager;
|
||||
$this->initialStateService = $initialStateService;
|
||||
$this->verificationToken = $verificationToken;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -192,36 +170,14 @@ class LostController extends Controller {
|
|||
* @param string $userId
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function checkPasswordResetToken($token, $userId) {
|
||||
$user = $this->userManager->get($userId);
|
||||
if ($user === null || !$user->isEnabled()) {
|
||||
throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
|
||||
}
|
||||
|
||||
$encryptedToken = $this->config->getUserValue($userId, 'core', 'lostpassword', null);
|
||||
if ($encryptedToken === null) {
|
||||
throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
|
||||
}
|
||||
|
||||
protected function checkPasswordResetToken(string $token, string $userId): void {
|
||||
try {
|
||||
$mailAddress = !is_null($user->getEMailAddress()) ? $user->getEMailAddress() : '';
|
||||
$decryptedToken = $this->crypto->decrypt($encryptedToken, $mailAddress.$this->config->getSystemValue('secret'));
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
|
||||
}
|
||||
|
||||
$splittedToken = explode(':', $decryptedToken);
|
||||
if (count($splittedToken) !== 2) {
|
||||
throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
|
||||
}
|
||||
|
||||
if ($splittedToken[0] < ($this->timeFactory->getTime() - 60 * 60 * 24 * 7) ||
|
||||
$user->getLastLogin() > $splittedToken[0]) {
|
||||
throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is expired'));
|
||||
}
|
||||
|
||||
if (!hash_equals($splittedToken[1], $token)) {
|
||||
throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
|
||||
$this->verificationToken->check($token, $this->userManager->get($userId), 'lostpassword', '', true);
|
||||
} catch (InvalidTokenException $e) {
|
||||
$error = $e->getCode() === InvalidTokenException::TOKEN_EXPIRED
|
||||
? $this->l10n->t('Could not reset password because the token is expired')
|
||||
: $this->l10n->t('Could not reset password because the token is invalid');
|
||||
throw new \Exception($error, (int)$e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -343,15 +299,7 @@ class LostController extends Controller {
|
|||
// secret being the users' email address appended with the system secret.
|
||||
// This makes the token automatically invalidate once the user changes
|
||||
// their email address.
|
||||
$token = $this->secureRandom->generate(
|
||||
21,
|
||||
ISecureRandom::CHAR_DIGITS.
|
||||
ISecureRandom::CHAR_LOWER.
|
||||
ISecureRandom::CHAR_UPPER
|
||||
);
|
||||
$tokenValue = $this->timeFactory->getTime() .':'. $token;
|
||||
$encryptedValue = $this->crypto->encrypt($tokenValue, $email . $this->config->getSystemValue('secret'));
|
||||
$this->config->setUserValue($user->getUID(), 'core', 'lostpassword', $encryptedValue);
|
||||
$token = $this->verificationToken->create($user, 'lostpassword', $email);
|
||||
|
||||
$link = $this->urlGenerator->linkToRouteAbsolute('core.lost.resetform', ['userId' => $user->getUID(), 'token' => $token]);
|
||||
|
||||
|
|
|
|||
20
core/templates/confirmation.php
Normal file
20
core/templates/confirmation.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
/** @var array $_ */
|
||||
/** @var \OCP\IL10N $l */
|
||||
/** @var \OCP\Defaults $theme */
|
||||
|
||||
?>
|
||||
|
||||
<div class="update">
|
||||
<form method="POST" action="<?php print_unescaped($_['targetUrl']);?>">
|
||||
<h2><?php p($_['title']) ?></h2>
|
||||
<p><?php p($_['message']) ?></p>
|
||||
<div class="buttons">
|
||||
<input type="submit" class="primary" value="<?php p($_['action']); ?>">
|
||||
</div>
|
||||
<?php foreach ($_['parameters'] as $name => $value) {?>
|
||||
<input type="hidden" name="<?php p($name); ?>" value="<?php p($value); ?>">
|
||||
<?php } ?>
|
||||
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']) ?>">
|
||||
</form>
|
||||
</div>
|
||||
13
core/templates/success.php
Normal file
13
core/templates/success.php
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
/** @var array $_ */
|
||||
/** @var \OCP\IL10N $l */
|
||||
/** @var \OCP\Defaults $theme */
|
||||
?>
|
||||
|
||||
<div class="update">
|
||||
<h2><?php p($_['title']) ?></h2>
|
||||
<p><?php p($_['message']) ?></p>
|
||||
<p><a class="button primary" href="<?php p(\OC::$server->get(\OCP\IURLGenerator::class)->linkTo('', 'index.php')) ?>">
|
||||
<?php p($l->t('Go to %s', [$theme->getName()])); ?>
|
||||
</a></p>
|
||||
</div>
|
||||
|
|
@ -487,6 +487,8 @@ return array(
|
|||
'OCP\\Security\\ICrypto' => $baseDir . '/lib/public/Security/ICrypto.php',
|
||||
'OCP\\Security\\IHasher' => $baseDir . '/lib/public/Security/IHasher.php',
|
||||
'OCP\\Security\\ISecureRandom' => $baseDir . '/lib/public/Security/ISecureRandom.php',
|
||||
'OCP\\Security\\VerificationToken\\IVerificationToken' => $baseDir . '/lib/public/Security/VerificationToken/IVerificationToken.php',
|
||||
'OCP\\Security\\VerificationToken\\InvalidTokenException' => $baseDir . '/lib/public/Security/VerificationToken/InvalidTokenException.php',
|
||||
'OCP\\Session\\Exceptions\\SessionNotAvailableException' => $baseDir . '/lib/public/Session/Exceptions/SessionNotAvailableException.php',
|
||||
'OCP\\Settings\\IIconSection' => $baseDir . '/lib/public/Settings/IIconSection.php',
|
||||
'OCP\\Settings\\IManager' => $baseDir . '/lib/public/Settings/IManager.php',
|
||||
|
|
@ -1371,6 +1373,8 @@ return array(
|
|||
'OC\\Security\\RateLimiting\\Limiter' => $baseDir . '/lib/private/Security/RateLimiting/Limiter.php',
|
||||
'OC\\Security\\SecureRandom' => $baseDir . '/lib/private/Security/SecureRandom.php',
|
||||
'OC\\Security\\TrustedDomainHelper' => $baseDir . '/lib/private/Security/TrustedDomainHelper.php',
|
||||
'OC\\Security\\VerificationToken\\CleanUpJob' => $baseDir . '/lib/private/Security/VerificationToken/CleanUpJob.php',
|
||||
'OC\\Security\\VerificationToken\\VerificationToken' => $baseDir . '/lib/private/Security/VerificationToken/VerificationToken.php',
|
||||
'OC\\Server' => $baseDir . '/lib/private/Server.php',
|
||||
'OC\\ServerContainer' => $baseDir . '/lib/private/ServerContainer.php',
|
||||
'OC\\ServerNotAvailableException' => $baseDir . '/lib/private/ServerNotAvailableException.php',
|
||||
|
|
|
|||
|
|
@ -516,6 +516,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OCP\\Security\\ICrypto' => __DIR__ . '/../../..' . '/lib/public/Security/ICrypto.php',
|
||||
'OCP\\Security\\IHasher' => __DIR__ . '/../../..' . '/lib/public/Security/IHasher.php',
|
||||
'OCP\\Security\\ISecureRandom' => __DIR__ . '/../../..' . '/lib/public/Security/ISecureRandom.php',
|
||||
'OCP\\Security\\VerificationToken\\IVerificationToken' => __DIR__ . '/../../..' . '/lib/public/Security/VerificationToken/IVerificationToken.php',
|
||||
'OCP\\Security\\VerificationToken\\InvalidTokenException' => __DIR__ . '/../../..' . '/lib/public/Security/VerificationToken/InvalidTokenException.php',
|
||||
'OCP\\Session\\Exceptions\\SessionNotAvailableException' => __DIR__ . '/../../..' . '/lib/public/Session/Exceptions/SessionNotAvailableException.php',
|
||||
'OCP\\Settings\\IIconSection' => __DIR__ . '/../../..' . '/lib/public/Settings/IIconSection.php',
|
||||
'OCP\\Settings\\IManager' => __DIR__ . '/../../..' . '/lib/public/Settings/IManager.php',
|
||||
|
|
@ -1400,6 +1402,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OC\\Security\\RateLimiting\\Limiter' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Limiter.php',
|
||||
'OC\\Security\\SecureRandom' => __DIR__ . '/../../..' . '/lib/private/Security/SecureRandom.php',
|
||||
'OC\\Security\\TrustedDomainHelper' => __DIR__ . '/../../..' . '/lib/private/Security/TrustedDomainHelper.php',
|
||||
'OC\\Security\\VerificationToken\\CleanUpJob' => __DIR__ . '/../../..' . '/lib/private/Security/VerificationToken/CleanUpJob.php',
|
||||
'OC\\Security\\VerificationToken\\VerificationToken' => __DIR__ . '/../../..' . '/lib/private/Security/VerificationToken/VerificationToken.php',
|
||||
'OC\\Server' => __DIR__ . '/../../..' . '/lib/private/Server.php',
|
||||
'OC\\ServerContainer' => __DIR__ . '/../../..' . '/lib/private/ServerContainer.php',
|
||||
'OC\\ServerNotAvailableException' => __DIR__ . '/../../..' . '/lib/private/ServerNotAvailableException.php',
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
*/
|
||||
namespace OC\Accounts;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use libphonenumber\NumberParseException;
|
||||
use libphonenumber\PhoneNumber;
|
||||
|
|
@ -45,9 +46,17 @@ use OCP\Accounts\IAccountPropertyCollection;
|
|||
use OCP\Accounts\PropertyDoesNotExistException;
|
||||
use OCP\BackgroundJob\IJobList;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\Defaults;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\Mail\IMailer;
|
||||
use OCP\Security\ICrypto;
|
||||
use OCP\Security\VerificationToken\IVerificationToken;
|
||||
use OCP\Util;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\GenericEvent;
|
||||
|
|
@ -88,17 +97,46 @@ class AccountManager implements IAccountManager {
|
|||
|
||||
/** @var LoggerInterface */
|
||||
private $logger;
|
||||
/** @var IVerificationToken */
|
||||
private $verificationToken;
|
||||
/** @var IMailer */
|
||||
private $mailer;
|
||||
/** @var Defaults */
|
||||
private $defaults;
|
||||
/** @var IL10N */
|
||||
private $l10n;
|
||||
/** @var IURLGenerator */
|
||||
private $urlGenerator;
|
||||
/** @var ICrypto */
|
||||
private $crypto;
|
||||
/** @var IFactory */
|
||||
private $l10nfactory;
|
||||
|
||||
public function __construct(IDBConnection $connection,
|
||||
IConfig $config,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
IJobList $jobList,
|
||||
LoggerInterface $logger) {
|
||||
public function __construct(
|
||||
IDBConnection $connection,
|
||||
IConfig $config,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
IJobList $jobList,
|
||||
LoggerInterface $logger,
|
||||
IVerificationToken $verificationToken,
|
||||
IMailer $mailer,
|
||||
Defaults $defaults,
|
||||
IFactory $factory,
|
||||
IURLGenerator $urlGenerator,
|
||||
ICrypto $crypto
|
||||
) {
|
||||
$this->connection = $connection;
|
||||
$this->config = $config;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->jobList = $jobList;
|
||||
$this->logger = $logger;
|
||||
$this->verificationToken = $verificationToken;
|
||||
$this->mailer = $mailer;
|
||||
$this->defaults = $defaults;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->crypto = $crypto;
|
||||
// DIing IL10N results in a dependency loop
|
||||
$this->l10nfactory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -337,7 +375,6 @@ class AccountManager implements IAccountManager {
|
|||
|
||||
/**
|
||||
* check if we need to ask the server for email verification, if yes we create a cronjob
|
||||
*
|
||||
*/
|
||||
protected function checkEmailVerification(IAccount $updatedAccount, array $oldData): void {
|
||||
try {
|
||||
|
|
@ -358,13 +395,75 @@ class AccountManager implements IAccountManager {
|
|||
]
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
$property->setVerified(self::VERIFICATION_IN_PROGRESS);
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkLocalEmailVerification(IAccount $updatedAccount, array $oldData): void {
|
||||
$mailCollection = $updatedAccount->getPropertyCollection(self::COLLECTION_EMAIL);
|
||||
foreach ($mailCollection->getProperties() as $property) {
|
||||
if ($property->getLocallyVerified() !== self::NOT_VERIFIED) {
|
||||
continue;
|
||||
}
|
||||
if ($this->sendEmailVerificationEmail($updatedAccount->getUser(), $property->getValue())) {
|
||||
$property->setLocallyVerified(self::VERIFICATION_IN_PROGRESS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function sendEmailVerificationEmail(IUser $user, string $email): bool {
|
||||
$ref = \substr(hash('sha256', $email), 0, 8);
|
||||
$key = $this->crypto->encrypt($email);
|
||||
$token = $this->verificationToken->create($user, 'verifyMail' . $ref, $email);
|
||||
|
||||
$link = $this->urlGenerator->linkToRouteAbsolute('provisioning_api.Verification.verifyMail',
|
||||
[
|
||||
'userId' => $user->getUID(),
|
||||
'token' => $token,
|
||||
'key' => $key
|
||||
]);
|
||||
|
||||
$emailTemplate = $this->mailer->createEMailTemplate('core.EmailVerification', [
|
||||
'link' => $link,
|
||||
]);
|
||||
|
||||
if (!$this->l10n) {
|
||||
$this->l10n = $this->l10nfactory->get('core');
|
||||
}
|
||||
|
||||
$emailTemplate->setSubject($this->l10n->t('%s email verification', [$this->defaults->getName()]));
|
||||
$emailTemplate->addHeader();
|
||||
$emailTemplate->addHeading($this->l10n->t('Email verification'));
|
||||
|
||||
$emailTemplate->addBodyText(
|
||||
htmlspecialchars($this->l10n->t('Click the following button to confirm your email.')),
|
||||
$this->l10n->t('Click the following link to confirm your email.')
|
||||
);
|
||||
|
||||
$emailTemplate->addBodyButton(
|
||||
htmlspecialchars($this->l10n->t('Confirm your email')),
|
||||
$link,
|
||||
false
|
||||
);
|
||||
$emailTemplate->addFooter();
|
||||
|
||||
try {
|
||||
$message = $this->mailer->createMessage();
|
||||
$message->setTo([$email => $user->getDisplayName()]);
|
||||
$message->setFrom([Util::getDefaultEmailAddress('verification-noreply') => $this->defaults->getName()]);
|
||||
$message->useTemplate($emailTemplate);
|
||||
$this->mailer->send($message);
|
||||
} catch (Exception $e) {
|
||||
// Log the exception and continue
|
||||
$this->logger->info('Failed to send verification mail', [
|
||||
'app' => 'core',
|
||||
'exception' => $e
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* make sure that all expected data are set
|
||||
*
|
||||
|
|
@ -406,7 +505,6 @@ class AccountManager implements IAccountManager {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* add new user to accounts table
|
||||
*
|
||||
|
|
@ -435,6 +533,12 @@ class AccountManager implements IAccountManager {
|
|||
foreach ($data as $dataRow) {
|
||||
$propertyName = $dataRow['name'];
|
||||
unset($dataRow['name']);
|
||||
|
||||
if (isset($dataRow['locallyVerified']) && $dataRow['locallyVerified'] === self::NOT_VERIFIED) {
|
||||
// do not write default value, save DB space
|
||||
unset($dataRow['locallyVerified']);
|
||||
}
|
||||
|
||||
if (!$this->isCollection($propertyName)) {
|
||||
$preparedData[$propertyName] = $dataRow;
|
||||
continue;
|
||||
|
|
@ -511,7 +615,6 @@ class AccountManager implements IAccountManager {
|
|||
continue;
|
||||
}
|
||||
|
||||
|
||||
$query->setParameter('name', $property['name'])
|
||||
->setParameter('value', $property['value'] ?? '');
|
||||
$query->executeStatement();
|
||||
|
|
@ -587,6 +690,7 @@ class AccountManager implements IAccountManager {
|
|||
$data['verified'] ?? self::NOT_VERIFIED,
|
||||
''
|
||||
);
|
||||
$p->setLocallyVerified($data['locallyVerified'] ?? self::NOT_VERIFIED);
|
||||
$collection->addProperty($p);
|
||||
|
||||
return $collection;
|
||||
|
|
@ -599,6 +703,10 @@ class AccountManager implements IAccountManager {
|
|||
$account->setPropertyCollection($this->arrayDataToCollection($account, $accountData));
|
||||
} else {
|
||||
$account->setProperty($accountData['name'], $accountData['value'] ?? '', $accountData['scope'] ?? self::SCOPE_LOCAL, $accountData['verified'] ?? self::NOT_VERIFIED);
|
||||
if (isset($accountData['locallyVerified'])) {
|
||||
$property = $account->getProperty($accountData['name']);
|
||||
$property->setLocallyVerified($accountData['locallyVerified']);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $account;
|
||||
|
|
@ -640,14 +748,17 @@ class AccountManager implements IAccountManager {
|
|||
$oldData = $this->getUser($account->getUser(), false);
|
||||
$this->updateVerificationStatus($account, $oldData);
|
||||
$this->checkEmailVerification($account, $oldData);
|
||||
$this->checkLocalEmailVerification($account, $oldData);
|
||||
|
||||
$data = [];
|
||||
foreach ($account->getAllProperties() as $property) {
|
||||
/** @var IAccountProperty $property */
|
||||
$data[] = [
|
||||
'name' => $property->getName(),
|
||||
'value' => $property->getValue(),
|
||||
'scope' => $property->getScope(),
|
||||
'verified' => $property->getVerified(),
|
||||
'locallyVerified' => $property->getLocallyVerified(),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ declare(strict_types=1);
|
|||
*/
|
||||
namespace OC\Accounts;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use OCP\Accounts\IAccountManager;
|
||||
use OCP\Accounts\IAccountProperty;
|
||||
|
||||
|
|
@ -42,6 +43,8 @@ class AccountProperty implements IAccountProperty {
|
|||
private $verified;
|
||||
/** @var string */
|
||||
private $verificationData;
|
||||
/** @var string */
|
||||
private $locallyVerified = IAccountManager::NOT_VERIFIED;
|
||||
|
||||
public function __construct(string $name, string $value, string $scope, string $verified, string $verificationData) {
|
||||
$this->name = $name;
|
||||
|
|
@ -90,7 +93,7 @@ class AccountProperty implements IAccountProperty {
|
|||
IAccountManager::SCOPE_PRIVATE,
|
||||
IAccountManager::SCOPE_PUBLISHED
|
||||
])) {
|
||||
throw new \InvalidArgumentException('Invalid scope');
|
||||
throw new InvalidArgumentException('Invalid scope');
|
||||
}
|
||||
$this->scope = $newScope;
|
||||
return $this;
|
||||
|
|
@ -178,4 +181,20 @@ class AccountProperty implements IAccountProperty {
|
|||
public function getVerificationData(): string {
|
||||
return $this->verificationData;
|
||||
}
|
||||
|
||||
public function setLocallyVerified(string $verified): IAccountProperty {
|
||||
if (!in_array($verified, [
|
||||
IAccountManager::NOT_VERIFIED,
|
||||
IAccountManager::VERIFICATION_IN_PROGRESS,
|
||||
IAccountManager::VERIFIED,
|
||||
])) {
|
||||
throw new InvalidArgumentException('Provided verification value is invalid');
|
||||
}
|
||||
$this->locallyVerified = $verified;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLocallyVerified(): string {
|
||||
return $this->locallyVerified;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,15 @@ class AccountPropertyCollection implements IAccountPropertyCollection {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getPropertyByValue(string $value): ?IAccountProperty {
|
||||
foreach ($this->properties as $i => $property) {
|
||||
if ($property->getValue() === $value) {
|
||||
return $property;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function removePropertyByValue(string $value): IAccountPropertyCollection {
|
||||
foreach ($this->properties as $i => $property) {
|
||||
if ($property->getValue() === $value) {
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ class UserPlugin implements ISearchPlugin {
|
|||
$userStatuses = $this->userStatusManager->getUserStatuses(array_keys($users));
|
||||
foreach ($users as $uid => $user) {
|
||||
$userDisplayName = $user->getDisplayName();
|
||||
$userEmail = $user->getEMailAddress();
|
||||
$userEmail = $user->getSystemEMailAddress();
|
||||
$uid = (string) $uid;
|
||||
|
||||
$status = [];
|
||||
|
|
@ -244,7 +244,7 @@ class UserPlugin implements ISearchPlugin {
|
|||
if ($addUser) {
|
||||
$status = [];
|
||||
$uid = $user->getUID();
|
||||
$userEmail = $user->getEMailAddress();
|
||||
$userEmail = $user->getSystemEMailAddress();
|
||||
if (array_key_exists($user->getUID(), $userStatuses)) {
|
||||
$userStatus = $userStatuses[$user->getUID()];
|
||||
$status = [
|
||||
|
|
|
|||
|
|
@ -568,7 +568,7 @@ EOF;
|
|||
*
|
||||
* @param string $text Text of button; Note: When $plainText falls back to this, HTML is automatically escaped in the HTML email
|
||||
* @param string $url URL of button
|
||||
* @param string $plainText Text of button in plain text version
|
||||
* @param string|false $plainText Text of button in plain text version
|
||||
* if empty the $text is used, if false none will be used
|
||||
*
|
||||
* @since 12.0.0
|
||||
|
|
|
|||
90
lib/private/Security/VerificationToken/CleanUpJob.php
Normal file
90
lib/private/Security/VerificationToken/CleanUpJob.php
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<?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 OC\Security\VerificationToken;
|
||||
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\IConfig;
|
||||
use OCP\ILogger;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Security\VerificationToken\InvalidTokenException;
|
||||
use OCP\Security\VerificationToken\IVerificationToken;
|
||||
|
||||
class CleanUpJob extends \OCP\BackgroundJob\Job {
|
||||
|
||||
/** @var int */
|
||||
protected $runNotBefore;
|
||||
/** @var string */
|
||||
protected $userId;
|
||||
/** @var string */
|
||||
protected $subject;
|
||||
/** @var string */
|
||||
protected $pwdPrefix;
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
/** @var IVerificationToken */
|
||||
private $verificationToken;
|
||||
/** @var IUserManager */
|
||||
private $userManager;
|
||||
|
||||
public function __construct(ITimeFactory $time, IConfig $config, IVerificationToken $verificationToken, IUserManager $userManager) {
|
||||
parent::__construct($time);
|
||||
$this->config = $config;
|
||||
$this->verificationToken = $verificationToken;
|
||||
$this->userManager = $userManager;
|
||||
}
|
||||
|
||||
public function setArgument($argument) {
|
||||
parent::setArgument($argument);
|
||||
$args = \json_decode($argument);
|
||||
$this->userId = (string)$args['userId'];
|
||||
$this->subject = (string)$args['subject'];
|
||||
$this->pwdPrefix = (string)$args['pp'];
|
||||
$this->runNotBefore = (int)$args['notBefore'];
|
||||
}
|
||||
|
||||
protected function run($argument) {
|
||||
try {
|
||||
$user = $this->userManager->get($this->userId);
|
||||
if ($user === null) {
|
||||
return;
|
||||
}
|
||||
$this->verificationToken->check('irrelevant', $user, $this->subject, $this->pwdPrefix);
|
||||
} catch (InvalidTokenException $e) {
|
||||
if ($e->getCode() === InvalidTokenException::TOKEN_EXPIRED) {
|
||||
// make sure to only remove expired tokens
|
||||
$this->config->deleteUserValue($this->userId, 'core', $this->subject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function execute($jobList, ILogger $logger = null) {
|
||||
if ($this->time->getTime() >= $this->runNotBefore) {
|
||||
$jobList->remove($this, $this->argument);
|
||||
parent::execute($jobList, $logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
129
lib/private/Security/VerificationToken/VerificationToken.php
Normal file
129
lib/private/Security/VerificationToken/VerificationToken.php
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
<?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 OC\Security\VerificationToken;
|
||||
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\IJobList;
|
||||
use OCP\IConfig;
|
||||
use OCP\IUser;
|
||||
use OCP\Security\ICrypto;
|
||||
use OCP\Security\ISecureRandom;
|
||||
use OCP\Security\VerificationToken\InvalidTokenException;
|
||||
use OCP\Security\VerificationToken\IVerificationToken;
|
||||
use function json_encode;
|
||||
|
||||
class VerificationToken implements IVerificationToken {
|
||||
protected const TOKEN_LIFETIME = 60 * 60 * 24 * 7;
|
||||
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
/** @var ICrypto */
|
||||
private $crypto;
|
||||
/** @var ITimeFactory */
|
||||
private $timeFactory;
|
||||
/** @var ISecureRandom */
|
||||
private $secureRandom;
|
||||
/** @var IJobList */
|
||||
private $jobList;
|
||||
|
||||
public function __construct(
|
||||
IConfig $config,
|
||||
ICrypto $crypto,
|
||||
ITimeFactory $timeFactory,
|
||||
ISecureRandom $secureRandom,
|
||||
IJobList $jobList
|
||||
) {
|
||||
$this->config = $config;
|
||||
$this->crypto = $crypto;
|
||||
$this->timeFactory = $timeFactory;
|
||||
$this->secureRandom = $secureRandom;
|
||||
$this->jobList = $jobList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidTokenException
|
||||
*/
|
||||
protected function throwInvalidTokenException(int $code): void {
|
||||
throw new InvalidTokenException($code);
|
||||
}
|
||||
|
||||
public function check(string $token, ?IUser $user, string $subject, string $passwordPrefix = '', bool $expiresWithLogin = false): void {
|
||||
if ($user === null || !$user->isEnabled()) {
|
||||
$this->throwInvalidTokenException(InvalidTokenException::USER_UNKNOWN);
|
||||
}
|
||||
|
||||
$encryptedToken = $this->config->getUserValue($user->getUID(), 'core', $subject, null);
|
||||
if ($encryptedToken === null) {
|
||||
$this->throwInvalidTokenException(InvalidTokenException::TOKEN_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$decryptedToken = $this->crypto->decrypt($encryptedToken, $passwordPrefix.$this->config->getSystemValue('secret'));
|
||||
} catch (\Exception $e) {
|
||||
$this->throwInvalidTokenException(InvalidTokenException::TOKEN_DECRYPTION_ERROR);
|
||||
}
|
||||
|
||||
$splitToken = explode(':', $decryptedToken ?? '');
|
||||
if (count($splitToken) !== 2) {
|
||||
$this->throwInvalidTokenException(InvalidTokenException::TOKEN_INVALID_FORMAT);
|
||||
}
|
||||
|
||||
if ($splitToken[0] < ($this->timeFactory->getTime() - self::TOKEN_LIFETIME)
|
||||
|| ($expiresWithLogin && $user->getLastLogin() > $splitToken[0])) {
|
||||
$this->throwInvalidTokenException(InvalidTokenException::TOKEN_EXPIRED);
|
||||
}
|
||||
|
||||
if (!hash_equals($splitToken[1], $token)) {
|
||||
$this->throwInvalidTokenException(InvalidTokenException::TOKEN_MISMATCH);
|
||||
}
|
||||
}
|
||||
|
||||
public function create(IUser $user, string $subject, string $passwordPrefix = ''): string {
|
||||
$token = $this->secureRandom->generate(
|
||||
21,
|
||||
ISecureRandom::CHAR_DIGITS.
|
||||
ISecureRandom::CHAR_LOWER.
|
||||
ISecureRandom::CHAR_UPPER
|
||||
);
|
||||
$tokenValue = $this->timeFactory->getTime() .':'. $token;
|
||||
$encryptedValue = $this->crypto->encrypt($tokenValue, $passwordPrefix . $this->config->getSystemValue('secret'));
|
||||
$this->config->setUserValue($user->getUID(), 'core', $subject, $encryptedValue);
|
||||
$jobArgs = json_encode([
|
||||
'userId' => $user->getUID(),
|
||||
'subject' => $subject,
|
||||
'pp' => $passwordPrefix,
|
||||
'notBefore' => $this->timeFactory->getTime() + self::TOKEN_LIFETIME * 2, // multiply to provide a grace period
|
||||
]);
|
||||
$this->jobList->add(CleanUpJob::class, $jobArgs);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function delete(string $token, IUser $user, string $subject): void {
|
||||
$this->config->deleteUserValue($user->getUID(), 'core', $subject);
|
||||
}
|
||||
}
|
||||
|
|
@ -135,6 +135,7 @@ use OC\Security\CSRF\TokenStorage\SessionStorage;
|
|||
use OC\Security\Hasher;
|
||||
use OC\Security\SecureRandom;
|
||||
use OC\Security\TrustedDomainHelper;
|
||||
use OC\Security\VerificationToken\VerificationToken;
|
||||
use OC\Session\CryptoWrapper;
|
||||
use OC\Share20\ProviderFactory;
|
||||
use OC\Share20\ShareHelper;
|
||||
|
|
@ -224,6 +225,7 @@ use OCP\Security\ICredentialsManager;
|
|||
use OCP\Security\ICrypto;
|
||||
use OCP\Security\IHasher;
|
||||
use OCP\Security\ISecureRandom;
|
||||
use OCP\Security\VerificationToken\IVerificationToken;
|
||||
use OCP\Share\IShareHelper;
|
||||
use OCP\SystemTag\ISystemTagManager;
|
||||
use OCP\SystemTag\ISystemTagObjectMapper;
|
||||
|
|
@ -795,6 +797,8 @@ class Server extends ServerContainer implements IServerContainer {
|
|||
/** @deprecated 19.0.0 */
|
||||
$this->registerDeprecatedAlias('SecureRandom', \OCP\Security\ISecureRandom::class);
|
||||
|
||||
$this->registerAlias(IVerificationToken::class, VerificationToken::class);
|
||||
|
||||
$this->registerAlias(ICrypto::class, Crypto::class);
|
||||
/** @deprecated 19.0.0 */
|
||||
$this->registerDeprecatedAlias('Crypto', ICrypto::class);
|
||||
|
|
|
|||
|
|
@ -436,7 +436,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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -115,4 +115,24 @@ interface IAccountProperty extends \JsonSerializable {
|
|||
* @since 22.0.0
|
||||
*/
|
||||
public function getVerificationData(): string;
|
||||
|
||||
/**
|
||||
* Set the instance-based verification status of a property
|
||||
*
|
||||
* @since 22.2.0
|
||||
*
|
||||
* @param string $verified must be one of the verification constants of IAccountManager
|
||||
* @return IAccountProperty
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setLocallyVerified(string $verified): IAccountProperty;
|
||||
|
||||
/**
|
||||
* Get the instance-based verification status of a property
|
||||
*
|
||||
* @since 22.2.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocallyVerified(): string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,4 +89,13 @@ interface IAccountPropertyCollection extends JsonSerializable {
|
|||
* @since 22.0.0
|
||||
*/
|
||||
public function removePropertyByValue(string $value): IAccountPropertyCollection;
|
||||
|
||||
/**
|
||||
* retrieves a property identified by its value. null, if none was found.
|
||||
*
|
||||
* Returns only the first property if there are more with the same value.
|
||||
*
|
||||
* @since 22.2.0
|
||||
*/
|
||||
public function getPropertyByValue(string $value): ?IAccountProperty;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 22.2.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 22.2.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 22.2.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 22.2.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 22.2.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
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ interface IEMailTemplate {
|
|||
*
|
||||
* @param string $text Text of button; Note: When $plainText falls back to this, HTML is automatically escaped in the HTML email
|
||||
* @param string $url URL of button
|
||||
* @param string $plainText Text of button in plain text version
|
||||
* @param string|false $plainText Text of button in plain text version
|
||||
* if empty the $text is used, if false none will be used
|
||||
*
|
||||
* @since 12.0.0
|
||||
|
|
|
|||
62
lib/public/Security/VerificationToken/IVerificationToken.php
Normal file
62
lib/public/Security/VerificationToken/IVerificationToken.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?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 OCP\Security\VerificationToken;
|
||||
|
||||
use OCP\IUser;
|
||||
|
||||
/**
|
||||
* @since 22.2.0
|
||||
*/
|
||||
interface IVerificationToken {
|
||||
|
||||
/**
|
||||
* Checks whether the a provided tokent matches a stored token and its
|
||||
* constraints. An InvalidTokenException is thrown on issues, otherwise
|
||||
* the check is successful.
|
||||
*
|
||||
* null can be passed as $user, but mind that this is for conveniently
|
||||
* passing the return of IUserManager::getUser() to this method. When
|
||||
* $user is null, InvalidTokenException is thrown for all the issued
|
||||
* tokens are user related.
|
||||
*
|
||||
* @throws InvalidTokenException
|
||||
* @since 22.2.0
|
||||
*/
|
||||
public function check(string $token, ?IUser $user, string $subject, string $passwordPrefix = '', bool $expiresWithLogin = false): void;
|
||||
|
||||
/**
|
||||
* @since 22.2.0
|
||||
*/
|
||||
public function create(IUser $user, string $subject, string $passwordPrefix = ''): string;
|
||||
|
||||
/**
|
||||
* Deletes the token identified by the provided parameters
|
||||
*
|
||||
* @since 22.2.0
|
||||
*/
|
||||
public function delete(string $token, IUser $user, string $subject): void;
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<?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 OCP\Security\VerificationToken;
|
||||
|
||||
/** @since 22.2.0 */
|
||||
class InvalidTokenException extends \Exception {
|
||||
|
||||
/**
|
||||
* @since 22.2.0
|
||||
*/
|
||||
public function __construct(int $code) {
|
||||
parent::__construct('', $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @since 22.2.0
|
||||
*/
|
||||
public const USER_UNKNOWN = 1;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @since 22.2.0
|
||||
*/
|
||||
public const TOKEN_NOT_FOUND = 2;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @since 22.2.0
|
||||
*/
|
||||
public const TOKEN_DECRYPTION_ERROR = 3;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @since 22.2.0
|
||||
*/
|
||||
public const TOKEN_INVALID_FORMAT = 4;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @since 22.2.0
|
||||
*/
|
||||
public const TOKEN_EXPIRED = 5;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @since 22.2.0
|
||||
*/
|
||||
public const TOKEN_MISMATCH = 6;
|
||||
}
|
||||
|
|
@ -26,7 +26,6 @@ use OC\Core\Controller\LostController;
|
|||
use OC\Mail\Message;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Defaults;
|
||||
use OCP\Encryption\IEncryptionModule;
|
||||
use OCP\Encryption\IManager;
|
||||
|
|
@ -40,8 +39,8 @@ use OCP\IUser;
|
|||
use OCP\IUserManager;
|
||||
use OCP\Mail\IEMailTemplate;
|
||||
use OCP\Mail\IMailer;
|
||||
use OCP\Security\ICrypto;
|
||||
use OCP\Security\ISecureRandom;
|
||||
use OCP\Security\VerificationToken\InvalidTokenException;
|
||||
use OCP\Security\VerificationToken\IVerificationToken;
|
||||
|
||||
/**
|
||||
* Class LostControllerTest
|
||||
|
|
@ -66,22 +65,18 @@ class LostControllerTest extends \Test\TestCase {
|
|||
private $config;
|
||||
/** @var IMailer | \PHPUnit\Framework\MockObject\MockObject */
|
||||
private $mailer;
|
||||
/** @var ISecureRandom | \PHPUnit\Framework\MockObject\MockObject */
|
||||
private $secureRandom;
|
||||
/** @var IManager|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $encryptionManager;
|
||||
/** @var ITimeFactory | \PHPUnit\Framework\MockObject\MockObject */
|
||||
private $timeFactory;
|
||||
/** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $request;
|
||||
/** @var ICrypto|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $crypto;
|
||||
/** @var ILogger|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $logger;
|
||||
/** @var Manager|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $twofactorManager;
|
||||
/** @var IInitialStateService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $initialStateService;
|
||||
/** @var IVerificationToken|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $verificationToken;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
|
@ -123,10 +118,6 @@ class LostControllerTest extends \Test\TestCase {
|
|||
->disableOriginalConstructor()->getMock();
|
||||
$this->mailer = $this->getMockBuilder('\OCP\Mail\IMailer')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
$this->secureRandom = $this->getMockBuilder('\OCP\Security\ISecureRandom')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
$this->timeFactory = $this->getMockBuilder('\OCP\AppFramework\Utility\ITimeFactory')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
$this->request = $this->getMockBuilder(IRequest::class)
|
||||
->disableOriginalConstructor()->getMock();
|
||||
$this->encryptionManager = $this->getMockBuilder(IManager::class)
|
||||
|
|
@ -134,10 +125,10 @@ class LostControllerTest extends \Test\TestCase {
|
|||
$this->encryptionManager->expects($this->any())
|
||||
->method('isEnabled')
|
||||
->willReturn(true);
|
||||
$this->crypto = $this->createMock(ICrypto::class);
|
||||
$this->logger = $this->createMock(ILogger::class);
|
||||
$this->twofactorManager = $this->createMock(Manager::class);
|
||||
$this->initialStateService = $this->createMock(IInitialStateService::class);
|
||||
$this->verificationToken = $this->createMock(IVerificationToken::class);
|
||||
$this->lostController = new LostController(
|
||||
'Core',
|
||||
$this->request,
|
||||
|
|
@ -146,89 +137,31 @@ class LostControllerTest extends \Test\TestCase {
|
|||
$this->defaults,
|
||||
$this->l10n,
|
||||
$this->config,
|
||||
$this->secureRandom,
|
||||
'lostpassword-noreply@localhost',
|
||||
$this->encryptionManager,
|
||||
$this->mailer,
|
||||
$this->timeFactory,
|
||||
$this->crypto,
|
||||
$this->logger,
|
||||
$this->twofactorManager,
|
||||
$this->initialStateService
|
||||
$this->initialStateService,
|
||||
$this->verificationToken
|
||||
);
|
||||
}
|
||||
|
||||
public function testResetFormWithNotExistingUser() {
|
||||
$this->userManager->method('get')
|
||||
->with('NotExistingUser')
|
||||
->willReturn(null);
|
||||
|
||||
$expectedResponse = new TemplateResponse(
|
||||
'core',
|
||||
'error',
|
||||
[
|
||||
'errors' => [
|
||||
['error' => 'Couldn\'t reset password because the token is invalid'],
|
||||
]
|
||||
],
|
||||
'guest'
|
||||
);
|
||||
$this->assertEquals($expectedResponse, $this->lostController->resetform('MySecretToken', 'NotExistingUser'));
|
||||
}
|
||||
|
||||
public function testResetFormInvalidTokenMatch() {
|
||||
$this->config->method('getUserValue')
|
||||
->with('ValidTokenUser', 'core', 'lostpassword', null)
|
||||
->willReturn('encryptedToken');
|
||||
$this->existingUser->method('getLastLogin')
|
||||
->willReturn(12344);
|
||||
public function testResetFormTokenError() {
|
||||
$this->userManager->method('get')
|
||||
->with('ValidTokenUser')
|
||||
->willReturn($this->existingUser);
|
||||
$this->crypto->method('decrypt')
|
||||
->with(
|
||||
$this->equalTo('encryptedToken'),
|
||||
$this->equalTo('test@example.comSECRET')
|
||||
)->willReturn('12345:TheOnlyAndOnlyOneTokenToResetThePassword');
|
||||
$this->verificationToken->expects($this->once())
|
||||
->method('check')
|
||||
->with('12345:MySecretToken', $this->existingUser, 'lostpassword')
|
||||
->willThrowException(new InvalidTokenException(InvalidTokenException::TOKEN_DECRYPTION_ERROR));
|
||||
|
||||
$response = $this->lostController->resetform('12345:MySecretToken', 'ValidTokenUser');
|
||||
$expectedResponse = new TemplateResponse('core',
|
||||
'error',
|
||||
[
|
||||
'errors' => [
|
||||
['error' => 'Couldn\'t reset password because the token is invalid'],
|
||||
]
|
||||
],
|
||||
'guest');
|
||||
$this->assertEquals($expectedResponse, $response);
|
||||
}
|
||||
|
||||
|
||||
public function testResetFormExpiredToken() {
|
||||
$this->userManager->method('get')
|
||||
->with('ValidTokenUser')
|
||||
->willReturn($this->existingUser);
|
||||
$this->config
|
||||
->expects($this->once())
|
||||
->method('getUserValue')
|
||||
->with('ValidTokenUser', 'core', 'lostpassword', null)
|
||||
->willReturn('encryptedToken');
|
||||
$this->crypto->method('decrypt')
|
||||
->with(
|
||||
$this->equalTo('encryptedToken'),
|
||||
$this->equalTo('test@example.comSECRET')
|
||||
)->willReturn('12345:TheOnlyAndOnlyOneTokenToResetThePassword');
|
||||
$this->timeFactory
|
||||
->expects($this->once())
|
||||
->method('getTime')
|
||||
->willReturn(999999);
|
||||
|
||||
$response = $this->lostController->resetform('TheOnlyAndOnlyOneTokenToResetThePassword', 'ValidTokenUser');
|
||||
$expectedResponse = new TemplateResponse('core',
|
||||
'error',
|
||||
[
|
||||
'errors' => [
|
||||
['error' => 'Couldn\'t reset password because the token is expired'],
|
||||
['error' => 'Could not reset password because the token is invalid'],
|
||||
]
|
||||
],
|
||||
'guest');
|
||||
|
|
@ -236,39 +169,14 @@ class LostControllerTest extends \Test\TestCase {
|
|||
}
|
||||
|
||||
public function testResetFormValidToken() {
|
||||
$this->existingUser->method('getLastLogin')
|
||||
->willReturn(12344);
|
||||
$this->userManager->method('get')
|
||||
->with('ValidTokenUser')
|
||||
->willReturn($this->existingUser);
|
||||
$this->timeFactory
|
||||
->expects($this->once())
|
||||
->method('getTime')
|
||||
->willReturn(12348);
|
||||
$this->verificationToken->expects($this->once())
|
||||
->method('check')
|
||||
->with('MySecretToken', $this->existingUser, 'lostpassword');
|
||||
|
||||
$this->config->method('getUserValue')
|
||||
->with('ValidTokenUser', 'core', 'lostpassword', null)
|
||||
->willReturn('encryptedToken');
|
||||
|
||||
$this->crypto->method('decrypt')
|
||||
->with(
|
||||
$this->equalTo('encryptedToken'),
|
||||
$this->equalTo('test@example.comSECRET')
|
||||
)->willReturn('12345:TheOnlyAndOnlyOneTokenToResetThePassword');
|
||||
$this->urlGenerator
|
||||
->expects($this->once())
|
||||
->method('linkToRouteAbsolute')
|
||||
->with('core.lost.setPassword', ['userId' => 'ValidTokenUser', 'token' => 'TheOnlyAndOnlyOneTokenToResetThePassword'])
|
||||
->willReturn('https://example.tld/index.php/lostpassword/');
|
||||
|
||||
$this->initialStateService->expects($this->at(0))
|
||||
->method('provideInitialState')
|
||||
->with('core', 'resetPasswordUser', 'ValidTokenUser');
|
||||
$this->initialStateService->expects($this->at(1))
|
||||
->method('provideInitialState')
|
||||
->with('core', 'resetPasswordTarget', 'https://example.tld/index.php/lostpassword/');
|
||||
|
||||
$response = $this->lostController->resetform('TheOnlyAndOnlyOneTokenToResetThePassword', 'ValidTokenUser');
|
||||
$response = $this->lostController->resetform('MySecretToken', 'ValidTokenUser');
|
||||
$expectedResponse = new TemplateResponse('core',
|
||||
'login',
|
||||
[],
|
||||
|
|
@ -319,24 +227,14 @@ class LostControllerTest extends \Test\TestCase {
|
|||
}
|
||||
|
||||
public function testEmailSuccessful() {
|
||||
$this->secureRandom
|
||||
->expects($this->once())
|
||||
->method('generate')
|
||||
->with('21')
|
||||
->willReturn('ThisIsMaybeANotSoSecretToken!');
|
||||
$this->userManager
|
||||
->expects($this->any())
|
||||
->method('get')
|
||||
->with('ExistingUser')
|
||||
->willReturn($this->existingUser);
|
||||
$this->timeFactory
|
||||
->expects($this->once())
|
||||
->method('getTime')
|
||||
->willReturn(12348);
|
||||
$this->config
|
||||
->expects($this->once())
|
||||
->method('setUserValue')
|
||||
->with('ExistingUser', 'core', 'lostpassword', 'encryptedToken');
|
||||
$this->verificationToken->expects($this->once())
|
||||
->method('create')
|
||||
->willReturn('ThisIsMaybeANotSoSecretToken!');
|
||||
$this->urlGenerator
|
||||
->expects($this->once())
|
||||
->method('linkToRouteAbsolute')
|
||||
|
|
@ -379,12 +277,6 @@ class LostControllerTest extends \Test\TestCase {
|
|||
->method('send')
|
||||
->with($message);
|
||||
|
||||
$this->crypto->method('encrypt')
|
||||
->with(
|
||||
$this->equalTo('12348:ThisIsMaybeANotSoSecretToken!'),
|
||||
$this->equalTo('test@example.comSECRET')
|
||||
)->willReturn('encryptedToken');
|
||||
|
||||
$response = $this->lostController->email('ExistingUser');
|
||||
$expectedResponse = new JSONResponse(['status' => 'success']);
|
||||
$expectedResponse->throttle();
|
||||
|
|
@ -392,11 +284,6 @@ class LostControllerTest extends \Test\TestCase {
|
|||
}
|
||||
|
||||
public function testEmailWithMailSuccessful() {
|
||||
$this->secureRandom
|
||||
->expects($this->once())
|
||||
->method('generate')
|
||||
->with('21')
|
||||
->willReturn('ThisIsMaybeANotSoSecretToken!');
|
||||
$this->userManager
|
||||
->expects($this->any())
|
||||
->method('get')
|
||||
|
|
@ -407,14 +294,9 @@ class LostControllerTest extends \Test\TestCase {
|
|||
->method('getByEmail')
|
||||
->with('test@example.com')
|
||||
->willReturn([$this->existingUser]);
|
||||
$this->timeFactory
|
||||
->expects($this->once())
|
||||
->method('getTime')
|
||||
->willReturn(12348);
|
||||
$this->config
|
||||
->expects($this->once())
|
||||
->method('setUserValue')
|
||||
->with('ExistingUser', 'core', 'lostpassword', 'encryptedToken');
|
||||
$this->verificationToken->expects($this->once())
|
||||
->method('create')
|
||||
->willReturn('ThisIsMaybeANotSoSecretToken!');
|
||||
$this->urlGenerator
|
||||
->expects($this->once())
|
||||
->method('linkToRouteAbsolute')
|
||||
|
|
@ -457,12 +339,6 @@ class LostControllerTest extends \Test\TestCase {
|
|||
->method('send')
|
||||
->with($message);
|
||||
|
||||
$this->crypto->method('encrypt')
|
||||
->with(
|
||||
$this->equalTo('12348:ThisIsMaybeANotSoSecretToken!'),
|
||||
$this->equalTo('test@example.comSECRET')
|
||||
)->willReturn('encryptedToken');
|
||||
|
||||
$response = $this->lostController->email('test@example.com');
|
||||
$expectedResponse = new JSONResponse(['status' => 'success']);
|
||||
$expectedResponse->throttle();
|
||||
|
|
@ -470,24 +346,14 @@ class LostControllerTest extends \Test\TestCase {
|
|||
}
|
||||
|
||||
public function testEmailCantSendException() {
|
||||
$this->secureRandom
|
||||
->expects($this->once())
|
||||
->method('generate')
|
||||
->with('21')
|
||||
->willReturn('ThisIsMaybeANotSoSecretToken!');
|
||||
$this->userManager
|
||||
->expects($this->any())
|
||||
->method('get')
|
||||
->with('ExistingUser')
|
||||
->willReturn($this->existingUser);
|
||||
$this->config
|
||||
->expects($this->once())
|
||||
->method('setUserValue')
|
||||
->with('ExistingUser', 'core', 'lostpassword', 'encryptedToken');
|
||||
$this->timeFactory
|
||||
->expects($this->once())
|
||||
->method('getTime')
|
||||
->willReturn(12348);
|
||||
$this->verificationToken->expects($this->once())
|
||||
->method('create')
|
||||
->willReturn('ThisIsMaybeANotSoSecretToken!');
|
||||
$this->urlGenerator
|
||||
->expects($this->once())
|
||||
->method('linkToRouteAbsolute')
|
||||
|
|
@ -530,12 +396,6 @@ class LostControllerTest extends \Test\TestCase {
|
|||
->with($message)
|
||||
->will($this->throwException(new \Exception()));
|
||||
|
||||
$this->crypto->method('encrypt')
|
||||
->with(
|
||||
$this->equalTo('12348:ThisIsMaybeANotSoSecretToken!'),
|
||||
$this->equalTo('test@example.comSECRET')
|
||||
)->willReturn('encryptedToken');
|
||||
|
||||
$this->logger->expects($this->exactly(1))
|
||||
->method('logException');
|
||||
|
||||
|
|
@ -560,14 +420,6 @@ class LostControllerTest extends \Test\TestCase {
|
|||
->willReturn($this->existingUser);
|
||||
$this->config->expects($this->never())
|
||||
->method('deleteUserValue');
|
||||
$this->timeFactory->method('getTime')
|
||||
->willReturn(12348);
|
||||
|
||||
$this->crypto->method('decrypt')
|
||||
->with(
|
||||
$this->equalTo('encryptedData'),
|
||||
$this->equalTo('test@example.comSECRET')
|
||||
)->willReturn('12345:TheOnlyAndOnlyOneTokenToResetThePassword');
|
||||
|
||||
$response = $this->lostController->setPassword('TheOnlyAndOnlyOneTokenToResetThePassword', 'ValidTokenUser', 'NewPassword', true);
|
||||
$expectedResponse = ['status' => 'error', 'msg' => ''];
|
||||
|
|
@ -590,14 +442,6 @@ class LostControllerTest extends \Test\TestCase {
|
|||
$this->config->expects($this->once())
|
||||
->method('deleteUserValue')
|
||||
->with('ValidTokenUser', 'core', 'lostpassword');
|
||||
$this->timeFactory->method('getTime')
|
||||
->willReturn(12348);
|
||||
|
||||
$this->crypto->method('decrypt')
|
||||
->with(
|
||||
$this->equalTo('encryptedData'),
|
||||
$this->equalTo('test@example.comSECRET')
|
||||
)->willReturn('12345:TheOnlyAndOnlyOneTokenToResetThePassword');
|
||||
|
||||
$response = $this->lostController->setPassword('TheOnlyAndOnlyOneTokenToResetThePassword', 'ValidTokenUser', 'NewPassword', true);
|
||||
$expectedResponse = ['user' => 'ValidTokenUser', 'status' => 'success'];
|
||||
|
|
@ -611,19 +455,14 @@ class LostControllerTest extends \Test\TestCase {
|
|||
$this->userManager->method('get')
|
||||
->with('ValidTokenUser')
|
||||
->willReturn($this->existingUser);
|
||||
$this->timeFactory->method('getTime')
|
||||
->willReturn(617146);
|
||||
|
||||
$this->crypto->method('decrypt')
|
||||
->with(
|
||||
$this->equalTo('encryptedData'),
|
||||
$this->equalTo('test@example.comSECRET')
|
||||
)->willReturn('12345:TheOnlyAndOnlyOneTokenToResetThePassword');
|
||||
$this->verificationToken->expects($this->atLeastOnce())
|
||||
->method('check')
|
||||
->willThrowException(new InvalidTokenException(InvalidTokenException::TOKEN_EXPIRED));
|
||||
|
||||
$response = $this->lostController->setPassword('TheOnlyAndOnlyOneTokenToResetThePassword', 'ValidTokenUser', 'NewPassword', true);
|
||||
$expectedResponse = [
|
||||
'status' => 'error',
|
||||
'msg' => 'Couldn\'t reset password because the token is expired',
|
||||
'msg' => 'Could not reset password because the token is expired',
|
||||
];
|
||||
$this->assertSame($expectedResponse, $response);
|
||||
}
|
||||
|
|
@ -636,45 +475,14 @@ class LostControllerTest extends \Test\TestCase {
|
|||
->method('get')
|
||||
->with('ValidTokenUser')
|
||||
->willReturn($this->existingUser);
|
||||
|
||||
$this->crypto->method('decrypt')
|
||||
->with(
|
||||
$this->equalTo('invalidEncryptedData'),
|
||||
$this->equalTo('test@example.comSECRET')
|
||||
)->willReturn('TheOnlyAndOnlyOneTokenToResetThePassword');
|
||||
$this->verificationToken->expects($this->atLeastOnce())
|
||||
->method('check')
|
||||
->willThrowException(new InvalidTokenException(InvalidTokenException::TOKEN_INVALID_FORMAT));
|
||||
|
||||
$response = $this->lostController->setPassword('TheOnlyAndOnlyOneTokenToResetThePassword', 'ValidTokenUser', 'NewPassword', true);
|
||||
$expectedResponse = [
|
||||
'status' => 'error',
|
||||
'msg' => 'Couldn\'t reset password because the token is invalid',
|
||||
];
|
||||
$this->assertSame($expectedResponse, $response);
|
||||
}
|
||||
|
||||
public function testSetPasswordExpiredTokenDueToLogin() {
|
||||
$this->config->method('getUserValue')
|
||||
->with('ValidTokenUser', 'core', 'lostpassword', null)
|
||||
->willReturn('encryptedData');
|
||||
$this->existingUser->method('getLastLogin')
|
||||
->willReturn(12346);
|
||||
$this->userManager
|
||||
->method('get')
|
||||
->with('ValidTokenUser')
|
||||
->willReturn($this->existingUser);
|
||||
$this->timeFactory
|
||||
->method('getTime')
|
||||
->willReturn(12345);
|
||||
|
||||
$this->crypto->method('decrypt')
|
||||
->with(
|
||||
$this->equalTo('encryptedData'),
|
||||
$this->equalTo('test@example.comSECRET')
|
||||
)->willReturn('12345:TheOnlyAndOnlyOneTokenToResetThePassword');
|
||||
|
||||
$response = $this->lostController->setPassword('TheOnlyAndOnlyOneTokenToResetThePassword', 'ValidTokenUser', 'NewPassword', true);
|
||||
$expectedResponse = [
|
||||
'status' => 'error',
|
||||
'msg' => 'Couldn\'t reset password because the token is expired',
|
||||
'msg' => 'Could not reset password because the token is invalid',
|
||||
];
|
||||
$this->assertSame($expectedResponse, $response);
|
||||
}
|
||||
|
|
@ -686,33 +494,14 @@ class LostControllerTest extends \Test\TestCase {
|
|||
$this->userManager->method('get')
|
||||
->with('ValidTokenUser')
|
||||
->willReturn($this->existingUser);
|
||||
|
||||
$this->crypto->method('decrypt')
|
||||
->with(
|
||||
$this->equalTo('aValidtoken'),
|
||||
$this->equalTo('test@example.comSECRET')
|
||||
)->willThrowException(new \Exception());
|
||||
$this->verificationToken->expects($this->atLeastOnce())
|
||||
->method('check')
|
||||
->willThrowException(new InvalidTokenException(InvalidTokenException::TOKEN_MISMATCH));
|
||||
|
||||
$response = $this->lostController->setPassword('', 'ValidTokenUser', 'NewPassword', true);
|
||||
$expectedResponse = [
|
||||
'status' => 'error',
|
||||
'msg' => 'Couldn\'t reset password because the token is invalid'
|
||||
];
|
||||
$this->assertSame($expectedResponse, $response);
|
||||
}
|
||||
|
||||
public function testIsSetPasswordTokenNullFailing() {
|
||||
$this->config->method('getUserValue')
|
||||
->with('ValidTokenUser', 'core', 'lostpassword', null)
|
||||
->willReturn(null);
|
||||
$this->userManager->method('get')
|
||||
->with('ValidTokenUser')
|
||||
->willReturn($this->existingUser);
|
||||
|
||||
$response = $this->lostController->setPassword('', 'ValidTokenUser', 'NewPassword', true);
|
||||
$expectedResponse = [
|
||||
'status' => 'error',
|
||||
'msg' => 'Couldn\'t reset password because the token is invalid'
|
||||
'msg' => 'Could not reset password because the token is invalid'
|
||||
];
|
||||
$this->assertSame($expectedResponse, $response);
|
||||
}
|
||||
|
|
@ -732,10 +521,14 @@ class LostControllerTest extends \Test\TestCase {
|
|||
->with('DisabledUser')
|
||||
->willReturn($user);
|
||||
|
||||
$this->verificationToken->expects($this->atLeastOnce())
|
||||
->method('check')
|
||||
->willThrowException(new InvalidTokenException(InvalidTokenException::USER_UNKNOWN));
|
||||
|
||||
$response = $this->lostController->setPassword('TheOnlyAndOnlyOneTokenToResetThePassword', 'DisabledUser', 'NewPassword', true);
|
||||
$expectedResponse = [
|
||||
'status' => 'error',
|
||||
'msg' => 'Couldn\'t reset password because the token is invalid'
|
||||
'msg' => 'Could not reset password because the token is invalid'
|
||||
];
|
||||
$this->assertSame($expectedResponse, $response);
|
||||
}
|
||||
|
|
@ -798,14 +591,6 @@ class LostControllerTest extends \Test\TestCase {
|
|||
$this->config->expects($this->once())
|
||||
->method('deleteUserValue')
|
||||
->with('ValidTokenUser', 'core', 'lostpassword');
|
||||
$this->timeFactory->method('getTime')
|
||||
->willReturn(12348);
|
||||
|
||||
$this->crypto->method('decrypt')
|
||||
->with(
|
||||
$this->equalTo('encryptedData'),
|
||||
$this->equalTo('test@example.comSECRET')
|
||||
)->willReturn('12345:TheOnlyAndOnlyOneTokenToResetThePassword');
|
||||
|
||||
$response = $this->lostController->setPassword('TheOnlyAndOnlyOneTokenToResetThePassword', 'ValidTokenUser', 'NewPassword', false);
|
||||
$expectedResponse = ['user' => 'ValidTokenUser', 'status' => 'success'];
|
||||
|
|
|
|||
|
|
@ -25,9 +25,15 @@ use OC\Accounts\Account;
|
|||
use OC\Accounts\AccountManager;
|
||||
use OCP\Accounts\IAccountManager;
|
||||
use OCP\BackgroundJob\IJobList;
|
||||
use OCP\Defaults;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\Mail\IMailer;
|
||||
use OCP\Security\ICrypto;
|
||||
use OCP\Security\VerificationToken\IVerificationToken;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
|
@ -41,6 +47,18 @@ use Test\TestCase;
|
|||
* @package Test\Accounts
|
||||
*/
|
||||
class AccountManagerTest extends TestCase {
|
||||
/** @var IVerificationToken|MockObject */
|
||||
protected $verificationToken;
|
||||
/** @var IMailer|MockObject */
|
||||
protected $mailer;
|
||||
/** @var ICrypto|MockObject */
|
||||
protected $crypto;
|
||||
/** @var IURLGenerator|MockObject */
|
||||
protected $urlGenerator;
|
||||
/** @var Defaults|MockObject */
|
||||
protected $defaults;
|
||||
/** @var IFactory|MockObject */
|
||||
protected $l10nFactory;
|
||||
|
||||
/** @var \OCP\IDBConnection */
|
||||
private $connection;
|
||||
|
|
@ -70,6 +88,12 @@ class AccountManagerTest extends TestCase {
|
|||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->jobList = $this->createMock(IJobList::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
$this->verificationToken = $this->createMock(IVerificationToken::class);
|
||||
$this->mailer = $this->createMock(IMailer::class);
|
||||
$this->defaults = $this->createMock(Defaults::class);
|
||||
$this->l10nFactory = $this->createMock(IFactory::class);
|
||||
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
||||
$this->crypto = $this->createMock(ICrypto::class);
|
||||
|
||||
$this->accountManager = new AccountManager(
|
||||
$this->connection,
|
||||
|
|
@ -77,6 +101,12 @@ class AccountManagerTest extends TestCase {
|
|||
$this->eventDispatcher,
|
||||
$this->jobList,
|
||||
$this->logger,
|
||||
$this->verificationToken,
|
||||
$this->mailer,
|
||||
$this->defaults,
|
||||
$this->l10nFactory,
|
||||
$this->urlGenerator,
|
||||
$this->crypto
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -310,6 +340,12 @@ class AccountManagerTest extends TestCase {
|
|||
$this->eventDispatcher,
|
||||
$this->jobList,
|
||||
$this->logger,
|
||||
$this->verificationToken,
|
||||
$this->mailer,
|
||||
$this->defaults,
|
||||
$this->l10nFactory,
|
||||
$this->urlGenerator,
|
||||
$this->crypto
|
||||
])
|
||||
->setMethods($mockedMethods)
|
||||
->getMock();
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ namespace Test;
|
|||
*
|
||||
* @package Test
|
||||
*/
|
||||
|
||||
use OC\SystemConfig;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
class AllConfigTest extends \Test\TestCase {
|
||||
|
|
@ -145,7 +147,7 @@ class AllConfigTest extends \Test\TestCase {
|
|||
$config->setUserValue('userSetBool', 'appSetBool', 'keySetBool', $value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function testSetUserValueWithPreConditionFailure() {
|
||||
$this->expectException(\OCP\PreConditionNotMetException::class);
|
||||
|
||||
|
|
@ -437,4 +439,22 @@ class AllConfigTest extends \Test\TestCase {
|
|||
// cleanup
|
||||
$this->connection->executeUpdate('DELETE FROM `*PREFIX*preferences`');
|
||||
}
|
||||
|
||||
public function testGetUsersForUserValueCaseInsensitive() {
|
||||
// mock the check for the database to run the correct SQL statements for each database type
|
||||
$systemConfig = $this->createMock(SystemConfig::class);
|
||||
$systemConfig->expects($this->once())
|
||||
->method('getValue')
|
||||
->with($this->equalTo('dbtype'), $this->equalTo('sqlite'))
|
||||
->willReturn(\OC::$server->getConfig()->getSystemValue('dbtype', 'sqlite'));
|
||||
$config = $this->getConfig($systemConfig);
|
||||
|
||||
$config->setUserValue('user1', 'myApp', 'myKey', 'test123');
|
||||
$config->setUserValue('user2', 'myApp', 'myKey', 'TEST123');
|
||||
$config->setUserValue('user3', 'myApp', 'myKey', 'test12345');
|
||||
|
||||
$users = $config->getUsersForUserValueCaseInsensitive('myApp', 'myKey', 'test123');
|
||||
$this->assertSame(2, count($users));
|
||||
$this->assertSame(['user1', 'user2'], $users);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
309
tests/lib/Security/VerificationToken/VerificationTokenTest.php
Normal file
309
tests/lib/Security/VerificationToken/VerificationTokenTest.php
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
<?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 Test\Security\VerificationToken;
|
||||
|
||||
use OC\Security\VerificationToken\VerificationToken;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\IJobList;
|
||||
use OCP\IConfig;
|
||||
use OCP\IUser;
|
||||
use OCP\Security\ICrypto;
|
||||
use OCP\Security\ISecureRandom;
|
||||
use OCP\Security\VerificationToken\InvalidTokenException;
|
||||
use Test\TestCase;
|
||||
|
||||
class VerificationTokenTest extends TestCase {
|
||||
/** @var VerificationToken */
|
||||
protected $token;
|
||||
/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
|
||||
protected $config;
|
||||
/** @var ISecureRandom|\PHPUnit\Framework\MockObject\MockObject */
|
||||
protected $secureRandom;
|
||||
/** @var ICrypto|\PHPUnit\Framework\MockObject\MockObject */
|
||||
protected $crypto;
|
||||
/** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */
|
||||
protected $timeFactory;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->crypto = $this->createMock(ICrypto::class);
|
||||
$this->timeFactory = $this->createMock(ITimeFactory::class);
|
||||
$this->secureRandom = $this->createMock(ISecureRandom::class);
|
||||
$this->jobList = $this->createMock(IJobList::class);
|
||||
|
||||
$this->token = new VerificationToken(
|
||||
$this->config,
|
||||
$this->crypto,
|
||||
$this->timeFactory,
|
||||
$this->secureRandom,
|
||||
$this->jobList
|
||||
);
|
||||
}
|
||||
|
||||
public function testTokenUserUnknown() {
|
||||
$this->expectException(InvalidTokenException::class);
|
||||
$this->expectExceptionCode(InvalidTokenException::USER_UNKNOWN);
|
||||
$this->token->check('encryptedToken', null, 'fingerprintToken', 'foobar');
|
||||
}
|
||||
|
||||
public function testTokenUserUnknown2() {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->expects($this->atLeastOnce())
|
||||
->method('isEnabled')
|
||||
->willReturn(false);
|
||||
|
||||
$this->expectException(InvalidTokenException::class);
|
||||
$this->expectExceptionCode(InvalidTokenException::USER_UNKNOWN);
|
||||
$this->token->check('encryptedToken', $user, 'fingerprintToken', 'foobar');
|
||||
}
|
||||
|
||||
public function testTokenNotFound() {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->expects($this->atLeastOnce())
|
||||
->method('isEnabled')
|
||||
->willReturn(true);
|
||||
$user->expects($this->atLeastOnce())
|
||||
->method('getUID')
|
||||
->willReturn('alice');
|
||||
|
||||
// implicit: IConfig::getUserValue returns null by default
|
||||
|
||||
$this->expectException(InvalidTokenException::class);
|
||||
$this->expectExceptionCode(InvalidTokenException::TOKEN_NOT_FOUND);
|
||||
$this->token->check('encryptedToken', $user, 'fingerprintToken', 'foobar');
|
||||
}
|
||||
|
||||
public function testTokenDecryptionError() {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->expects($this->atLeastOnce())
|
||||
->method('isEnabled')
|
||||
->willReturn(true);
|
||||
$user->expects($this->atLeastOnce())
|
||||
->method('getUID')
|
||||
->willReturn('alice');
|
||||
|
||||
$this->config->expects($this->atLeastOnce())
|
||||
->method('getUserValue')
|
||||
->with('alice', 'core', 'fingerprintToken', null)
|
||||
->willReturn('encryptedToken');
|
||||
$this->config->expects($this->any())
|
||||
->method('getSystemValue')
|
||||
->with('secret')
|
||||
->willReturn('357111317');
|
||||
|
||||
$this->crypto->method('decrypt')
|
||||
->with('encryptedToken', 'foobar' . '357111317')
|
||||
->willThrowException(new \Exception('decryption failed'));
|
||||
|
||||
$this->expectException(InvalidTokenException::class);
|
||||
$this->expectExceptionCode(InvalidTokenException::TOKEN_DECRYPTION_ERROR);
|
||||
$this->token->check('encryptedToken', $user, 'fingerprintToken', 'foobar');
|
||||
}
|
||||
|
||||
public function testTokenInvalidFormat() {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->expects($this->atLeastOnce())
|
||||
->method('isEnabled')
|
||||
->willReturn(true);
|
||||
$user->expects($this->atLeastOnce())
|
||||
->method('getUID')
|
||||
->willReturn('alice');
|
||||
|
||||
$this->config->expects($this->atLeastOnce())
|
||||
->method('getUserValue')
|
||||
->with('alice', 'core', 'fingerprintToken', null)
|
||||
->willReturn('encryptedToken');
|
||||
$this->config->expects($this->any())
|
||||
->method('getSystemValue')
|
||||
->with('secret')
|
||||
->willReturn('357111317');
|
||||
|
||||
$this->crypto->method('decrypt')
|
||||
->with('encryptedToken', 'foobar' . '357111317')
|
||||
->willReturn('decrypted^nonsense');
|
||||
|
||||
$this->expectException(InvalidTokenException::class);
|
||||
$this->expectExceptionCode(InvalidTokenException::TOKEN_INVALID_FORMAT);
|
||||
$this->token->check('encryptedToken', $user, 'fingerprintToken', 'foobar');
|
||||
}
|
||||
|
||||
public function testTokenExpired() {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->expects($this->atLeastOnce())
|
||||
->method('isEnabled')
|
||||
->willReturn(true);
|
||||
$user->expects($this->atLeastOnce())
|
||||
->method('getUID')
|
||||
->willReturn('alice');
|
||||
$user->expects($this->any())
|
||||
->method('getLastLogin')
|
||||
->willReturn(604803);
|
||||
|
||||
$this->config->expects($this->atLeastOnce())
|
||||
->method('getUserValue')
|
||||
->with('alice', 'core', 'fingerprintToken', null)
|
||||
->willReturn('encryptedToken');
|
||||
$this->config->expects($this->any())
|
||||
->method('getSystemValue')
|
||||
->with('secret')
|
||||
->willReturn('357111317');
|
||||
|
||||
$this->crypto->method('decrypt')
|
||||
->with('encryptedToken', 'foobar' . '357111317')
|
||||
->willReturn('604800:mY70K3n');
|
||||
|
||||
$this->timeFactory->expects($this->any())
|
||||
->method('getTime')
|
||||
->willReturn(604800 * 3);
|
||||
|
||||
$this->expectException(InvalidTokenException::class);
|
||||
$this->expectExceptionCode(InvalidTokenException::TOKEN_EXPIRED);
|
||||
$this->token->check('encryptedToken', $user, 'fingerprintToken', 'foobar');
|
||||
}
|
||||
|
||||
public function testTokenExpiredByLogin() {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->expects($this->atLeastOnce())
|
||||
->method('isEnabled')
|
||||
->willReturn(true);
|
||||
$user->expects($this->atLeastOnce())
|
||||
->method('getUID')
|
||||
->willReturn('alice');
|
||||
$user->expects($this->any())
|
||||
->method('getLastLogin')
|
||||
->willReturn(604803);
|
||||
|
||||
$this->config->expects($this->atLeastOnce())
|
||||
->method('getUserValue')
|
||||
->with('alice', 'core', 'fingerprintToken', null)
|
||||
->willReturn('encryptedToken');
|
||||
$this->config->expects($this->any())
|
||||
->method('getSystemValue')
|
||||
->with('secret')
|
||||
->willReturn('357111317');
|
||||
|
||||
$this->crypto->method('decrypt')
|
||||
->with('encryptedToken', 'foobar' . '357111317')
|
||||
->willReturn('604800:mY70K3n');
|
||||
|
||||
$this->timeFactory->expects($this->any())
|
||||
->method('getTime')
|
||||
->willReturn(604801);
|
||||
|
||||
$this->expectException(InvalidTokenException::class);
|
||||
$this->expectExceptionCode(InvalidTokenException::TOKEN_EXPIRED);
|
||||
$this->token->check('encryptedToken', $user, 'fingerprintToken', 'foobar', true);
|
||||
}
|
||||
|
||||
public function testTokenMismatch() {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->expects($this->atLeastOnce())
|
||||
->method('isEnabled')
|
||||
->willReturn(true);
|
||||
$user->expects($this->atLeastOnce())
|
||||
->method('getUID')
|
||||
->willReturn('alice');
|
||||
$user->expects($this->any())
|
||||
->method('getLastLogin')
|
||||
->willReturn(604703);
|
||||
|
||||
$this->config->expects($this->atLeastOnce())
|
||||
->method('getUserValue')
|
||||
->with('alice', 'core', 'fingerprintToken', null)
|
||||
->willReturn('encryptedToken');
|
||||
$this->config->expects($this->any())
|
||||
->method('getSystemValue')
|
||||
->with('secret')
|
||||
->willReturn('357111317');
|
||||
|
||||
$this->crypto->method('decrypt')
|
||||
->with('encryptedToken', 'foobar' . '357111317')
|
||||
->willReturn('604802:mY70K3n');
|
||||
|
||||
$this->timeFactory->expects($this->any())
|
||||
->method('getTime')
|
||||
->willReturn(604801);
|
||||
|
||||
$this->expectException(InvalidTokenException::class);
|
||||
$this->expectExceptionCode(InvalidTokenException::TOKEN_MISMATCH);
|
||||
$this->token->check('encryptedToken', $user, 'fingerprintToken', 'foobar');
|
||||
}
|
||||
|
||||
public function testTokenSuccess() {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->expects($this->atLeastOnce())
|
||||
->method('isEnabled')
|
||||
->willReturn(true);
|
||||
$user->expects($this->atLeastOnce())
|
||||
->method('getUID')
|
||||
->willReturn('alice');
|
||||
$user->expects($this->any())
|
||||
->method('getLastLogin')
|
||||
->willReturn(604703);
|
||||
|
||||
$this->config->expects($this->atLeastOnce())
|
||||
->method('getUserValue')
|
||||
->with('alice', 'core', 'fingerprintToken', null)
|
||||
->willReturn('encryptedToken');
|
||||
$this->config->expects($this->any())
|
||||
->method('getSystemValue')
|
||||
->with('secret')
|
||||
->willReturn('357111317');
|
||||
|
||||
$this->crypto->method('decrypt')
|
||||
->with('encryptedToken', 'foobar' . '357111317')
|
||||
->willReturn('604802:barfoo');
|
||||
|
||||
$this->timeFactory->expects($this->any())
|
||||
->method('getTime')
|
||||
->willReturn(604801);
|
||||
|
||||
$this->token->check('barfoo', $user, 'fingerprintToken', 'foobar');
|
||||
}
|
||||
|
||||
public function testCreate() {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->expects($this->any())
|
||||
->method('getUID')
|
||||
->willReturn('alice');
|
||||
|
||||
$this->secureRandom->expects($this->atLeastOnce())
|
||||
->method('generate')
|
||||
->willReturn('barfoo');
|
||||
$this->crypto->expects($this->atLeastOnce())
|
||||
->method('encrypt')
|
||||
->willReturn('encryptedToken');
|
||||
$this->config->expects($this->atLeastOnce())
|
||||
->method('setUserValue')
|
||||
->with('alice', 'core', 'fingerprintToken', 'encryptedToken');
|
||||
|
||||
$vToken = $this->token->create($user, 'fingerprintToken', 'foobar');
|
||||
$this->assertSame('barfoo', $vToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -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…
Reference in a new issue