From 4d5fb2628a1862d2e80ed38fffc7a3a233c14d7f Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 16 Jul 2020 17:05:45 +0200 Subject: [PATCH 01/10] Login Flow v2 compatibility Signed-off-by: Joas Schilling --- appinfo/routes.php | 12 ++- css/style.css | 10 +- img/verify.svg | 1 + lib/Controller/RegisterController.php | 142 ++++++++++++++++++++------ lib/RegistrationLoginOption.php | 2 +- lib/Service/MailService.php | 9 +- lib/Service/RegistrationService.php | 36 ++++--- templates/form/email.php | 26 +++++ templates/{form.php => form/user.php} | 0 templates/form/verification.php | 26 +++++ templates/register.php | 58 ----------- 11 files changed, 209 insertions(+), 113 deletions(-) create mode 100644 img/verify.svg create mode 100644 templates/form/email.php rename templates/{form.php => form/user.php} (100%) create mode 100644 templates/form/verification.php delete mode 100644 templates/register.php diff --git a/appinfo/routes.php b/appinfo/routes.php index 7db7778..d83acb7 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -13,14 +13,16 @@ return [ 'routes' => [ ['name' => 'settings#admin', 'url' => '/settings', 'verb' => 'POST'], - ['name' => 'register#askEmail', 'url' => '/', 'verb' => 'GET'], - ['name' => 'register#validateEmail', 'url' => '/', 'verb' => 'POST'], - ['name' => 'register#verifyToken', 'url' => '/verify/{token}', 'verb' => 'GET'], - ['name' => 'register#createAccount', 'url' => '/verify/{token}', 'verb' => 'POST'] + ['name' => 'register#showEmailForm', 'url' => '/', 'verb' => 'GET'], + ['name' => 'register#submitEmailForm', 'url' => '/', 'verb' => 'POST'], + ['name' => 'register#showVerificationForm', 'url' => '/verify/{secret}', 'verb' => 'GET'], + ['name' => 'register#submitVerificationForm', 'url' => '/verify/{secret}', 'verb' => 'POST'], + ['name' => 'register#showUserForm', 'url' => '/register/{secret}/{token}', 'verb' => 'GET'], + ['name' => 'register#submitUserForm', 'url' => '/register/{secret}/{token}', 'verb' => 'POST'], ], 'ocs' => [ ['root' => '/registration', 'name' => 'api#validate', 'url' => '/v1/validate', 'verb' => 'POST'], ['root' => '/registration', 'name' => 'api#status', 'url' => '/v1/status', 'verb' => 'POST'], - ['root' => '/registration', 'name' => 'api#register', 'url' => '/v1/register', 'verb' => 'POST'] + ['root' => '/registration', 'name' => 'api#register', 'url' => '/v1/register', 'verb' => 'POST'], ] ]; diff --git a/css/style.css b/css/style.css index 3987ce6..7171a26 100644 --- a/css/style.css +++ b/css/style.css @@ -1,8 +1,14 @@ -#body-login #email, #body-login #username, #body-login #password { +#body-login #email, +#body-login #token, +#body-login #username, +#body-login #password { width: calc(100% - 56px); padding-left: 36px; } -#email-icon, #username-icon, #password-icon { +#email-icon, +#token-icon, +#username-icon, +#password-icon { position: absolute; left: 16px; top: 22px; diff --git a/img/verify.svg b/img/verify.svg new file mode 100644 index 0000000..f1b2091 --- /dev/null +++ b/img/verify.svg @@ -0,0 +1 @@ + diff --git a/lib/Controller/RegisterController.php b/lib/Controller/RegisterController.php index deb7e55..ee0b795 100644 --- a/lib/Controller/RegisterController.php +++ b/lib/Controller/RegisterController.php @@ -16,6 +16,8 @@ use OCA\Registration\Db\Registration; use OCA\Registration\Service\MailService; use OCA\Registration\Service\RegistrationException; use OCA\Registration\Service\RegistrationService; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Http\Response; use \OCP\IRequest; use \OCP\AppFramework\Http\TemplateResponse; use \OCP\AppFramework\Http\RedirectResponse; @@ -59,64 +61,138 @@ class RegisterController extends Controller { * @NoCSRFRequired * @PublicPage * - * @param $errormsg - * @param $entered + * @param string $email + * @param string $message * @return TemplateResponse */ - public function askEmail($errormsg, $entered) { + public function showEmailForm(string $email = '', string $message = ''): TemplateResponse { $params = [ - 'errormsg' => $errormsg ? $errormsg : $this->request->getParam('errormsg'), - 'entered' => $entered ? $entered : $this->request->getParam('entered') + 'email' => $email, + 'message' => $message, ]; - return new TemplateResponse('registration', 'register', $params, 'guest'); + return new TemplateResponse('registration', 'form/email', $params, 'guest'); } /** - * User POST email, if email is valid and not duplicate, we send token by mail * @PublicPage * @AnonRateThrottle(limit=5, period=1) * * @param string $email * @return TemplateResponse */ - public function validateEmail($email) {//TODO rename to receiveUserEmail - if (!$this->registrationService->checkAllowedDomains($email)) {//TODO Duplicate code with Service - return new TemplateResponse('registration', 'domains', [ - 'domains' => $this->registrationService->getAllowedDomains() - ], 'guest'); - } + public function submitEmailForm(string $email): Response { try { - $reg = $this->registrationService->validateEmail($email); - if ($reg === true) { + // Registration already in progress, update token and continue with verification + $registration = $this->registrationService->getRegistrationForEmail($email); + $this->registrationService->generateNewToken($registration); + } catch (DoesNotExistException $e) { + // No registration in progress + try { + $this->registrationService->validateEmail($email); $registration = $this->registrationService->createRegistration($email); - $this->mailService->sendTokenByMail($registration); - } else { - $this->registrationService->generateNewToken($reg); - $this->mailService->sendTokenByMail($reg); - return new TemplateResponse('registration', 'message', ['msg' => - $this->l10n->t('There is already a pending registration with this email, a new verification email has been sent to the address.') - ], 'guest'); + } catch (RegistrationException $e) { + return $this->showEmailForm($email, $e->getMessage()); } - } catch (RegistrationException $e) { - return new TemplateResponse('registration', 'message', ['msg' => - $e->getMessage().'
'.$e->getHint() - ], 'guest'); } + try { + $this->mailService->sendTokenByMail($registration); + } catch (RegistrationException $e) { + return $this->showEmailForm($email, $e->getMessage()); + } - return new TemplateResponse('registration', 'message', ['msg' => - $this->l10n->t('Verification email successfully sent.') - ], 'guest'); + return new RedirectResponse( + $this->urlgenerator->linkToRoute( + 'registration.register.showVerificationForm', + ['secret' => $registration->getClientSecret()] + ) + ); } /** * @NoCSRFRequired * @PublicPage * - * @param $token + * @param string $secret + * @param string $message * @return TemplateResponse */ - public function verifyToken($token) { + public function showVerificationForm(string $secret, string $message = ''): TemplateResponse { + try { + $this->registrationService->getRegistrationForSecret($secret); + } catch (RegistrationException $e) { + return new TemplateResponse('core', 'error', [ + 'errors' => [ + $this->l10n->t('The verification secret does not exist anymore'), + ], + ], 'error'); + } + + return new TemplateResponse('registration', 'form/verification', [ + 'message' => $message, + ], 'guest'); + } + + /** + * @PublicPage + * @AnonRateThrottle(limit=5, period=1) + * + * @param string $secret + * @param string $token + * @return Response + */ + public function submitVerificationForm(string $secret, string $token): Response { + try { + $registration = $this->registrationService->getRegistrationForSecret($secret); + + if ($registration->getToken() !== $token) { + return $this->showVerificationForm( + $secret, + $this->l10n->t('The entered verification code is wrong') + ); + } + } catch (RegistrationException $e) { + return new TemplateResponse('core', 'error', [ + 'errors' => [ + $this->l10n->t('The verification secret does not exist anymore'), + ], + ], 'error'); + } + + return new RedirectResponse( + $this->urlgenerator->linkToRoute( + 'registration.register.showUserForm', + [ + 'secret' => $secret, + 'token' => $token, + ] + ) + ); + } + + /** + * @NoCSRFRequired + * @PublicPage + * + * @param string $secret + * @param string $token + * @return TemplateResponse + */ + public function showUserForm(string $secret, string $token): TemplateResponse { + try { + $registration = $this->registrationService->getRegistrationForSecret($secret); + + if ($registration->getToken() !== $token) { + throw new RegistrationException('Invalid verification token'); + } + } catch (RegistrationException $e) { + return new TemplateResponse('core', 'error', [ + 'errors' => [ + $this->l10n->t('The verification secret does not exist anymore or the verification token is invalid'), + ], + ], 'error'); + } + try { /** @var Registration $registration */ $registration = $this->registrationService->verifyToken($token); @@ -131,7 +207,7 @@ class RegisterController extends Controller { ); } - return new TemplateResponse('registration', 'form', [ + return new TemplateResponse('registration', 'form/user', [ 'email' => $registration->getEmail(), 'email_is_login' => $this->config->getAppValue('registration', 'email_is_login', '0') === '1', 'token' => $registration->getToken(), @@ -148,7 +224,7 @@ class RegisterController extends Controller { * @param $token * @return RedirectResponse|TemplateResponse */ - public function createAccount($token) { + public function submitUserForm($token) { $registration = $this->registrationService->getRegistrationForToken($token); if ($this->config->getAppValue('registration', 'email_is_login', '0') === '1') { $username = $registration->getEmail(); diff --git a/lib/RegistrationLoginOption.php b/lib/RegistrationLoginOption.php index d436574..ceda53e 100644 --- a/lib/RegistrationLoginOption.php +++ b/lib/RegistrationLoginOption.php @@ -47,7 +47,7 @@ class RegistrationLoginOption implements IAlternativeLogin { } public function getLink(): string { - return $this->url->linkToRoute('registration.register.askEmail'); + return $this->url->linkToRoute('registration.register.showEmailForm'); } public function getClass(): string { diff --git a/lib/Service/MailService.php b/lib/Service/MailService.php index 5d8f581..0a8b688 100644 --- a/lib/Service/MailService.php +++ b/lib/Service/MailService.php @@ -81,7 +81,10 @@ class MailService { * @throws RegistrationException */ public function sendTokenByMail(Registration $registration): void { - $link = $this->urlGenerator->linkToRouteAbsolute('registration.register.verifyToken', ['token' => $registration->getToken()]); + $link = $this->urlGenerator->linkToRouteAbsolute('registration.register.showUserForm', [ + 'secret' => $registration->getClientSecret(), + 'token' => $registration->getToken(), + ]); $subject = $this->l10n->t('Verify your %s registration request', [$this->defaults->getName()]); $template = $this->mailer->createEMailTemplate('registration_verify', [ @@ -100,6 +103,10 @@ class MailService { $body ); + $template->addBodyText( + $this->l10n->t('Verification code: %s', $registration->getToken()) + ); + $template->addBodyButton( $this->l10n->t('Continue registration'), $link diff --git a/lib/Service/RegistrationService.php b/lib/Service/RegistrationService.php index e29c2ee..3d5bb85 100644 --- a/lib/Service/RegistrationService.php +++ b/lib/Service/RegistrationService.php @@ -135,24 +135,24 @@ class RegistrationService { $registration->setPassword($password); } $this->registrationMapper->generateNewToken($registration); - if ($password !== '' && $username !== '') { - $this->registrationMapper->generateClientSecret($registration); - } + $this->registrationMapper->generateClientSecret($registration); $this->registrationMapper->insert($registration); return $registration; } /** * @param string $email - * @return Registration|true if there is a pending reg with this email, return the pending reg, if there are no problems with the email, return true. * @throws RegistrationException */ - public function validateEmail($email) { + public function validateEmail(string $email): void { $this->mailService->validateEmail($email); // check for pending registrations try { - return $this->registrationMapper->find($email);//if not found DB will throw a exception + $this->registrationMapper->find($email);//if not found DB will throw a exception + throw new RegistrationException( + $this->l10n->t('A user has already taken this email, maybe you already have an account?') + ); } catch (DoesNotExistException $e) { } @@ -171,7 +171,6 @@ class RegistrationService { ) ); } - return true; } /** @@ -235,10 +234,10 @@ class RegistrationService { * Find registration entity for token * * @param string $token - * @return string + * @return Registration * @throws RegistrationException */ - public function verifyToken($token) { + public function verifyToken(string $token): Registration { try { return $this->registrationMapper->findByToken($token); } catch (DoesNotExistException $exception) { @@ -327,18 +326,29 @@ class RegistrationService { } /** - * @param $token + * @param string $email * @return Registration + * @throws DoesNotExistException */ - public function getRegistrationForToken($token) { + public function getRegistrationForEmail(string $email): Registration { + return $this->registrationMapper->find($email); + } + + /** + * @param string $token + * @return Registration + * @throws DoesNotExistException + */ + public function getRegistrationForToken(string $token): Registration { return $this->registrationMapper->findByToken($token); } /** - * @param $secret + * @param string $secret * @return Registration + * @throws DoesNotExistException */ - public function getRegistrationForSecret($secret) { + public function getRegistrationForSecret(string $secret): Registration { return $this->registrationMapper->findBySecret($secret); } diff --git a/templates/form/email.php b/templates/form/email.php new file mode 100644 index 0000000..e68727e --- /dev/null +++ b/templates/form/email.php @@ -0,0 +1,26 @@ + +
+
+ +
    +
  • +
+ + +

+ + + +

+ + + + + t('Back to login')); ?> + +
+
diff --git a/templates/form.php b/templates/form/user.php similarity index 100% rename from templates/form.php rename to templates/form/user.php diff --git a/templates/form/verification.php b/templates/form/verification.php new file mode 100644 index 0000000..816f351 --- /dev/null +++ b/templates/form/verification.php @@ -0,0 +1,26 @@ + +
+
+ +
    +
  • +
+ + +

+ + + +

+ + + + + t('Back to login')); ?> + +
+
diff --git a/templates/register.php b/templates/register.php deleted file mode 100644 index 6c2d542..0000000 --- a/templates/register.php +++ /dev/null @@ -1,58 +0,0 @@ - - - - -
-
-
    -
  • -
-

- - - -

- - - - - t('Back to login')); ?> - -
-
- - -
-
- -
    -
  • -
  • t('Please re-enter a valid email address')); ?>
  • -
- -
    -
  • t('You will receive an email with a verification link')); ?>
  • -
- -

- - - -

- - - - - t('Back to login')); ?> - -
-
- From 0b4fac2edfd1aa8dd7bd8225fb9ef239bfa565f6 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 16 Jul 2020 21:15:39 +0200 Subject: [PATCH 02/10] Redirect to ClientLoginFlow and ClientLoginFlowV2 when it was used Signed-off-by: Joas Schilling --- lib/Controller/RegisterController.php | 187 ++++++++++++++------------ lib/Service/LoginFlowService.php | 107 +++++++++++++++ lib/Service/RegistrationService.php | 38 ++---- templates/approval-required.php | 13 ++ templates/domains.php | 16 --- templates/message.php | 8 -- 6 files changed, 231 insertions(+), 138 deletions(-) create mode 100644 lib/Service/LoginFlowService.php create mode 100644 templates/approval-required.php delete mode 100644 templates/domains.php delete mode 100644 templates/message.php diff --git a/lib/Controller/RegisterController.php b/lib/Controller/RegisterController.php index ee0b795..94abb43 100644 --- a/lib/Controller/RegisterController.php +++ b/lib/Controller/RegisterController.php @@ -1,4 +1,7 @@ * @author Julius Härtl + * @author 2020 Joas Schilling * @copyright Pellaeon Lin 2014 */ namespace OCA\Registration\Controller; +use Exception; use OCA\Registration\Db\Registration; +use OCA\Registration\Service\LoginFlowService; use OCA\Registration\Service\MailService; use OCA\Registration\Service\RegistrationException; use OCA\Registration\Service\RegistrationService; +use OCP\AppFramework\Controller; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\RedirectResponse; +use OCP\AppFramework\Http\RedirectToDefaultAppResponse; use OCP\AppFramework\Http\Response; -use \OCP\IRequest; -use \OCP\AppFramework\Http\TemplateResponse; -use \OCP\AppFramework\Http\RedirectResponse; -use \OCP\AppFramework\Controller; +use OCP\AppFramework\Http\StandaloneTemplateResponse; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\IL10N; +use OCP\IRequest; use OCP\IURLGenerator; -use \OCP\IConfig; -use \OCP\IL10N; +use OCP\IConfig; +use OCP\IL10N; class RegisterController extends Controller { /** @var IL10N */ private $l10n; /** @var IURLGenerator */ - private $urlgenerator; + private $urlGenerator; /** @var IConfig */ private $config; /** @var RegistrationService */ private $registrationService; /** @var MailService */ private $mailService; - + /** @var LoginFlowService */ + private $loginFlowService; public function __construct( - $appName, + string $appName, IRequest $request, IL10N $l10n, - IURLGenerator $urlgenerator, + IURLGenerator $urlGenerator, IConfig $config, RegistrationService $registrationService, + LoginFlowService $loginFlowService, MailService $mailService ) { parent::__construct($appName, $request); $this->l10n = $l10n; - $this->urlgenerator = $urlgenerator; + $this->urlGenerator = $urlGenerator; $this->config = $config; $this->registrationService = $registrationService; + $this->loginFlowService = $loginFlowService; $this->mailService = $mailService; } @@ -102,7 +115,7 @@ class RegisterController extends Controller { } return new RedirectResponse( - $this->urlgenerator->linkToRoute( + $this->urlGenerator->linkToRoute( 'registration.register.showVerificationForm', ['secret' => $registration->getClientSecret()] ) @@ -120,12 +133,8 @@ class RegisterController extends Controller { public function showVerificationForm(string $secret, string $message = ''): TemplateResponse { try { $this->registrationService->getRegistrationForSecret($secret); - } catch (RegistrationException $e) { - return new TemplateResponse('core', 'error', [ - 'errors' => [ - $this->l10n->t('The verification secret does not exist anymore'), - ], - ], 'error'); + } catch (DoesNotExistException $e) { + return $this->validateSecretAndTokenErrorPage(); } return new TemplateResponse('registration', 'form/verification', [ @@ -151,16 +160,12 @@ class RegisterController extends Controller { $this->l10n->t('The entered verification code is wrong') ); } - } catch (RegistrationException $e) { - return new TemplateResponse('core', 'error', [ - 'errors' => [ - $this->l10n->t('The verification secret does not exist anymore'), - ], - ], 'error'); + } catch (DoesNotExistException $e) { + return $this->validateSecretAndTokenErrorPage(); } return new RedirectResponse( - $this->urlgenerator->linkToRoute( + $this->urlGenerator->linkToRoute( 'registration.register.showUserForm', [ 'secret' => $secret, @@ -176,95 +181,101 @@ class RegisterController extends Controller { * * @param string $secret * @param string $token + * @param string $username + * @param string $message * @return TemplateResponse */ - public function showUserForm(string $secret, string $token): TemplateResponse { + public function showUserForm(string $secret, string $token, string $username = '', string $message = ''): TemplateResponse { try { - $registration = $this->registrationService->getRegistrationForSecret($secret); - - if ($registration->getToken() !== $token) { - throw new RegistrationException('Invalid verification token'); - } + $registration = $this->validateSecretAndToken($secret, $token); } catch (RegistrationException $e) { - return new TemplateResponse('core', 'error', [ - 'errors' => [ - $this->l10n->t('The verification secret does not exist anymore or the verification token is invalid'), - ], - ], 'error'); + return $this->validateSecretAndTokenErrorPage(); } - try { - /** @var Registration $registration */ - $registration = $this->registrationService->verifyToken($token); - $this->registrationService->confirmEmail($registration); - - // create account without form if username/password are already stored - if ($registration->getUsername() !== "" && $registration->getPassword() !== "") { - $this->registrationService->createAccount($registration); - return new TemplateResponse('registration', 'message', - ['msg' => $this->l10n->t('Your account has been successfully created, you can log in now.', [$this->urlgenerator->getAbsoluteURL('/')])], - 'guest' - ); - } - - return new TemplateResponse('registration', 'form/user', [ - 'email' => $registration->getEmail(), - 'email_is_login' => $this->config->getAppValue('registration', 'email_is_login', '0') === '1', - 'token' => $registration->getToken(), - ], 'guest'); - } catch (RegistrationException $exception) { - return $this->renderError($exception->getMessage(), $exception->getHint()); - } + return new TemplateResponse('registration', 'form/user', [ + 'email' => $registration->getEmail(), + 'email_is_login' => $this->config->getAppValue('registration', 'email_is_login', '0') === '1', + 'username' => $username, + 'message' => $message, + ], 'guest'); } /** * @PublicPage * @UseSession * - * @param $token + * @param string $secret + * @param string $token + * @param string $username + * @param string $password * @return RedirectResponse|TemplateResponse */ - public function submitUserForm($token) { - $registration = $this->registrationService->getRegistrationForToken($token); + public function submitUserForm(string $secret, string $token, string $username, string $password): Response { + try { + $registration = $this->validateSecretAndToken($secret, $token); + } catch (RegistrationException $e) { + return $this->validateSecretAndTokenErrorPage(); + } + if ($this->config->getAppValue('registration', 'email_is_login', '0') === '1') { $username = $registration->getEmail(); - } else { - $username = $this->request->getParam('username'); } - $password = $this->request->getParam('password'); try { $user = $this->registrationService->createAccount($registration, $username, $password); - } catch (\Exception $exception) { - // Render form with previously sent values - return new TemplateResponse('registration', 'form', - [ - 'email' => $registration->getEmail(), - 'entered_data' => ['user' => $username], - 'errormsgs' => [$exception->getMessage()], - 'token' => $token - ], 'guest'); + } catch (Exception $exception) { + return $this->showUserForm($secret, $token, $username, $exception->getMessage()); } if ($user->isEnabled()) { - // log the user - return $this->registrationService->loginUser($user->getUID(), $username, $password, false); - } else { - // warn the user their account needs admin validation - return new TemplateResponse( - 'registration', - 'message', - ['msg' => $this->l10n->t("Your account has been successfully created, but it still needs approval from an administrator.")], - 'guest'); + $this->registrationService->loginUser($user->getUID(), $user->getUID(), $password); + + if ($this->loginFlowService->isUsingLoginFlow(2)) { + $response = $this->loginFlowService->tryLoginFlowV2($user); + if ($response instanceof Response) { + return $response; + } + } + + if ($this->loginFlowService->isUsingLoginFlow(1)) { + $response = $this->loginFlowService->tryLoginFlowV1(); + if ($response instanceof Response && $response->getStatus() === Http::STATUS_SEE_OTHER) { + return $response; + } + } + + return new RedirectToDefaultAppResponse(); } + + // warn the user their account needs admin validation + return new StandaloneTemplateResponse('registration', 'approval-required', [], 'guest'); } - private function renderError($error, $hint="") { - return new TemplateResponse('', 'error', [ - 'errors' => [[ - 'error' => $error, - 'hint' => $hint - ]] + /** + * @param string $secret + * @param string $token + * @return Registration + * @throws RegistrationException + */ + protected function validateSecretAndToken(string $secret, string $token): Registration { + try { + $registration = $this->registrationService->getRegistrationForSecret($secret); + } catch (DoesNotExistException $e) { + throw new RegistrationException('Invalid secret'); + } + + if ($registration->getToken() !== $token) { + throw new RegistrationException('Invalid token'); + } + + return $registration; + } + + protected function validateSecretAndTokenErrorPage(): TemplateResponse { + return new TemplateResponse('core', 'error', [ + 'errors' => [ + $this->l10n->t('The verification failed.'), + ], ], 'error'); } } diff --git a/lib/Service/LoginFlowService.php b/lib/Service/LoginFlowService.php new file mode 100644 index 0000000..9e5e7e3 --- /dev/null +++ b/lib/Service/LoginFlowService.php @@ -0,0 +1,107 @@ + + * + * @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 . + * + */ + +namespace OCA\Registration\Service; + +use OC\Core\Controller\ClientFlowLoginController; +use OC\Core\Controller\ClientFlowLoginV2Controller; +use OC\Core\Service\LoginFlowV2Service; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Http\StandaloneTemplateResponse; +use OCP\IRequest; +use OCP\ISession; +use OCP\IUser; + +class LoginFlowService { + + /** @var IRequest */ + protected $request; + /** @var ISession */ + protected $session; + /** @var LoginFlowV2Service */ + protected $loginFlowV2Service; + + public function __construct( + IRequest $request, + ISession $session, + LoginFlowV2Service $loginFlowV2Service + ) { + $this->request = $request; + $this->session = $session; + $this->loginFlowV2Service = $loginFlowV2Service; + } + + public function isUsingLoginFlow(?int $version = null): bool { + if (($version === 1 || $version === null) && $this->session->get(ClientFlowLoginController::STATE_NAME) !== null) { + return true; + } + + if (($version === 2 || $version === null) && $this->session->get(ClientFlowLoginV2Controller::TOKEN_NAME) !== null) { + return true; + } + + return false; + } + + public function tryLoginFlowV1(): ?Response { + /** @var ClientFlowLoginController $controller */ + $container = \OC::$server->getRegisteredAppContainer('core'); + $controller = $container->query(ClientFlowLoginController::class); + return $controller->generateAppPassword( + $this->session->get(ClientFlowLoginController::STATE_NAME) + ); + } + + public function tryLoginFlowV2(IUser $user): ?StandaloneTemplateResponse { + $result = $this->loginFlowV2Service->flowDone( + $this->session->get(ClientFlowLoginV2Controller::TOKEN_NAME), + $this->session->getId(), + $this->getServerPath(), + $user->getUID() + ); + + if (!$result) { + return null; + } + + return new StandaloneTemplateResponse( + 'core', + 'loginflowv2/done', + [], + 'guest' + ); + } + + private function getServerPath(): string { + $serverPostfix = ''; + + if (strpos($this->request->getRequestUri(), '/index.php') !== false) { + $serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/index.php')); + } elseif (strpos($this->request->getRequestUri(), '/login/v2') !== false) { + $serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/login/v2')); + } + + $protocol = $this->request->getServerProtocol(); + return $protocol . '://' . $this->request->getServerHost() . $serverPostfix; + } +} diff --git a/lib/Service/RegistrationService.php b/lib/Service/RegistrationService.php index 3d5bb85..49528d8 100644 --- a/lib/Service/RegistrationService.php +++ b/lib/Service/RegistrationService.php @@ -40,6 +40,7 @@ use OCP\ILogger; use OCP\IRequest; use OCP\ISession; use OCP\IURLGenerator; +use OCP\IUser; use OCP\Security\ICrypto; use OCP\Session\Exceptions\SessionNotAvailableException; use \OCP\IUserManager; @@ -247,12 +248,12 @@ class RegistrationService { /** * @param $registration - * @param string $username - * @param string $password + * @param string|null $username + * @param string|null $password * @return \OCP\IUser * @throws RegistrationException|InvalidTokenException */ - public function createAccount(Registration $registration, $username = null, $password = null) { + public function createAccount(Registration $registration, ?string $username = null, ?string $password = null) { if ($password === null && $registration->getPassword() === null) { $generatedPassword = $this->generateRandomDeviceToken(); $registration->setPassword($this->crypto->encrypt($generatedPassword)); @@ -414,33 +415,18 @@ class RegistrationService { } /** - * @param $userId - * @param $username - * @param $password - * @param $decrypt - * @return RedirectResponse|TemplateResponse + * @param string $userId + * @param string $username + * @param string $password + * @param bool $decrypt */ - public function loginUser($userId, $username, $password, $decrypt = false) { + public function loginUser(string $userId, string $username, string $password, bool $decrypt = false): void { if ($decrypt) { $password = $this->crypto->decrypt($password); } - if (method_exists($this->usersession, 'createSessionToken')) { - $this->usersession->login($username, $password); - $this->usersession->createSessionToken($this->request, $userId, $username, $password); - return new RedirectResponse($this->urlGenerator->linkTo('', 'index.php')); - } elseif (\OC_User::login($username, $password)) { - $this->cleanupLoginTokens($userId); - // FIXME unsetMagicInCookie will fail from session already closed, so now we always remember - $logintoken = $this->random->generate(32); - $this->config->setUserValue($userId, 'login_token', $logintoken, time()); - \OC_User::setMagicInCookie($userId, $logintoken); - \OC_Util::redirectToDefaultPage(); - } - // Render message in case redirect failed - return new TemplateResponse('registration', 'message', - ['msg' => $this->l10n->t('Your account has been successfully created, you can log in now.', [$this->urlGenerator->getAbsoluteURL('/')])] - , 'guest' - ); + + $this->usersession->login($username, $password); + $this->usersession->createSessionToken($this->request, $userId, $username, $password); } /** diff --git a/templates/approval-required.php b/templates/approval-required.php new file mode 100644 index 0000000..fd20b3c --- /dev/null +++ b/templates/approval-required.php @@ -0,0 +1,13 @@ + +
+

t('Approval required')) ?>

+
    +
  • +

    t('Your account has been successfully created, but it still needs approval from an administrator.')) ?>

    +
  • +
+
diff --git a/templates/domains.php b/templates/domains.php deleted file mode 100644 index 13ce4ef..0000000 --- a/templates/domains.php +++ /dev/null @@ -1,16 +0,0 @@ - -
    -
  • t('Registration is only allowed for the following domains:')); ?> - "; - p($domain); - echo "

    "; - } - ?> -
  • -
diff --git a/templates/message.php b/templates/message.php deleted file mode 100644 index c895520..0000000 --- a/templates/message.php +++ /dev/null @@ -1,8 +0,0 @@ - -
    -
  • -
From f0aa35b5807109e00ca8524251bf801241269f82 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 17 Jul 2020 11:14:02 +0200 Subject: [PATCH 03/10] Make RegistrationService strict Signed-off-by: Joas Schilling --- lib/Service/RegistrationService.php | 97 +++++++++++++++-------------- 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/lib/Service/RegistrationService.php b/lib/Service/RegistrationService.php index 49528d8..2dfd3b3 100644 --- a/lib/Service/RegistrationService.php +++ b/lib/Service/RegistrationService.php @@ -1,4 +1,7 @@ * @copyright Copyright (c) 2017 Pellaeon Lin @@ -34,8 +37,6 @@ use OC\Authentication\Token\IToken; use OCA\Registration\Db\Registration; use OCA\Registration\Db\RegistrationMapper; use OCP\AppFramework\Db\DoesNotExistException; -use \OCP\AppFramework\Http\TemplateResponse; -use \OCP\AppFramework\Http\RedirectResponse; use OCP\ILogger; use OCP\IRequest; use OCP\ISession; @@ -71,7 +72,7 @@ class RegistrationService { /** @var ISecureRandom */ private $random; /** @var IUserSession */ - private $usersession; + private $userSession; /** @var IRequest */ private $request; /** @var ILogger */ @@ -83,9 +84,23 @@ class RegistrationService { /** @var ICrypto */ private $crypto; - public function __construct($appName, MailService $mailService, IL10N $l10n, IURLGenerator $urlGenerator, - RegistrationMapper $registrationMapper, IUserManager $userManager, IConfig $config, IGroupManager $groupManager, - ISecureRandom $random, IUserSession $us, IRequest $request, ILogger $logger, ISession $session, IProvider $tokenProvider, ICrypto $crypto) { + public function __construct( + string $appName, + MailService $mailService, + IL10N $l10n, + IURLGenerator $urlGenerator, + RegistrationMapper $registrationMapper, + IUserManager $userManager, + IConfig $config, + IGroupManager $groupManager, + ISecureRandom $random, + IUserSession $userSession, + IRequest $request, + ILogger $logger, + ISession $session, + IProvider $tokenProvider, + ICrypto $crypto + ) { $this->appName = $appName; $this->mailService = $mailService; $this->l10n = $l10n; @@ -95,7 +110,7 @@ class RegistrationService { $this->config = $config; $this->groupManager = $groupManager; $this->random = $random; - $this->usersession = $us; + $this->userSession = $userSession; $this->request = $request; $this->logger = $logger; $this->session = $session; @@ -103,21 +118,16 @@ class RegistrationService { $this->crypto = $crypto; } - /** - * @param Registration $registration - */ - public function confirmEmail(Registration $registration) { + public function confirmEmail(Registration $registration): void { $registration->setEmailConfirmed(true); $this->registrationMapper->update($registration); } - /** - * @param Registration $registration - */ - public function generateNewToken(Registration $registration) { + public function generateNewToken(Registration $registration): void { $this->registrationMapper->generateNewToken($registration); $this->registrationMapper->update($registration); } + /** * Create registration request, used by both the API and form * @param string $email @@ -126,7 +136,7 @@ class RegistrationService { * @param string $displayname * @return Registration */ - public function createRegistration($email, $username="", $password="", $displayname="") { + public function createRegistration(string $email, string $username = '', string $password = '', string $displayname = ''): Registration { $registration = new Registration(); $registration->setEmail($email); $registration->setUsername($username); @@ -178,8 +188,8 @@ class RegistrationService { * @param string $displayname * @throws RegistrationException */ - public function validateDisplayname($displayname) { - if ($displayname === "") { + public function validateDisplayname(string $displayname): void { + if ($displayname === '') { throw new RegistrationException($this->l10n->t('Please provide a valid display name.')); } } @@ -188,7 +198,7 @@ class RegistrationService { * @param string $username * @throws RegistrationException */ - public function validateUsername($username) { + public function validateUsername(string $username): void { if ($username === "") { throw new RegistrationException($this->l10n->t('Please provide a valid user name.')); } @@ -204,7 +214,7 @@ class RegistrationService { * @param string $email * @return bool */ - public function checkAllowedDomains($email) { + public function checkAllowedDomains(string $email): bool { $allowed_domains = $this->config->getAppValue($this->appName, 'allowed_domains', ''); if ($allowed_domains !== '') { $allowed_domains = explode(';', $allowed_domains); @@ -223,9 +233,9 @@ class RegistrationService { } /** - * @return array + * @return string[] */ - public function getAllowedDomains() { + public function getAllowedDomains(): array { $allowed_domains = $this->config->getAppValue($this->appName, 'allowed_domains', ''); $allowed_domains = explode(';', $allowed_domains); return $allowed_domains; @@ -250,10 +260,10 @@ class RegistrationService { * @param $registration * @param string|null $username * @param string|null $password - * @return \OCP\IUser + * @return IUser * @throws RegistrationException|InvalidTokenException */ - public function createAccount(Registration $registration, ?string $username = null, ?string $password = null) { + public function createAccount(Registration $registration, ?string $username = null, ?string $password = null): IUser { if ($password === null && $registration->getPassword() === null) { $generatedPassword = $this->generateRandomDeviceToken(); $registration->setPassword($this->crypto->encrypt($generatedPassword)); @@ -281,6 +291,7 @@ class RegistrationService { throw new RegistrationException($this->l10n->t('Unable to create user, there are problems with the user backend.')); } $userId = $user->getUID(); + // Set user email try { $user->setEMailAddress($registration->getEmail()); @@ -289,25 +300,25 @@ class RegistrationService { } // Add user to group - $registered_user_group = $this->config->getAppValue($this->appName, 'registered_user_group', 'none'); - if ($registered_user_group !== 'none') { - $group = $this->groupManager->get($registered_user_group); + $registeredUserGroup = $this->config->getAppValue($this->appName, 'registered_user_group', 'none'); + if ($registeredUserGroup !== 'none') { + $group = $this->groupManager->get($registeredUserGroup); if ($group === null) { // This might happen if $registered_user_group is deleted after setting the value // Here I choose to log error instead of stopping the user to register - $this->logger->error("You specified newly registered users be added to '$registered_user_group' group, but it does not exist."); + $this->logger->error("You specified newly registered users be added to '$registeredUserGroup' group, but it does not exist."); $groupId = ''; } else { $group->addUser($user); $groupId = $group->getGID(); } } else { - $groupId = ""; + $groupId = ''; } // disable user if this is requested by config - $admin_approval_required = $this->config->getAppValue($this->appName, 'admin_approval_required', "no"); - if ($admin_approval_required === "yes") { + $adminApprovalRequired = $this->config->getAppValue($this->appName, 'admin_approval_required', 'no'); + if ($adminApprovalRequired === 'yes') { $user->setEnabled(false); } @@ -353,19 +364,11 @@ class RegistrationService { return $this->registrationMapper->findBySecret($secret); } - /** - * @param Registration $registation - * @return null|\OCP\IUser - */ - public function getUserAccount(Registration $registation) { - $user = $this->userManager->get($registation->getUsername()); - return $user; + public function getUserAccount(Registration $registration): ?IUser { + return $this->userManager->get($registration->getUsername()); } - /** - * @param Registration $registration - */ - public function deleteRegistration(Registration $registration) { + public function deleteRegistration(Registration $registration): void { $this->registrationMapper->delete($registration); } @@ -376,7 +379,7 @@ class RegistrationService { * * @return string */ - private function generateRandomDeviceToken() { + private function generateRandomDeviceToken(): string { $groups = []; for ($i = 0; $i < 5; $i++) { $groups[] = $this->random->generate(5, ISecureRandom::CHAR_HUMAN_READABLE); @@ -389,7 +392,7 @@ class RegistrationService { * @return string * @throws RegistrationException */ - public function generateAppPassword($uid) { + public function generateAppPassword(string $uid): string { $name = $this->l10n->t('Registration app auto setup'); try { $sessionId = $this->session->getId(); @@ -425,15 +428,15 @@ class RegistrationService { $password = $this->crypto->decrypt($password); } - $this->usersession->login($username, $password); - $this->usersession->createSessionToken($this->request, $userId, $username, $password); + $this->userSession->login($username, $password); + $this->userSession->createSessionToken($this->request, $userId, $username, $password); } /** * Replicates OC::cleanupLoginTokens() since it's protected * @param string $userId */ - public function cleanupLoginTokens($userId) { + public function cleanupLoginTokens(string $userId): void { $cutoff = time() - $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15); $tokens = $this->config->getUserKeys($userId, 'login_token'); foreach ($tokens as $token) { From 201f388dee2f6fcd338f35ae573b0b342b47f784 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 17 Jul 2020 11:14:29 +0200 Subject: [PATCH 04/10] Form also uses ClientSecret now, so manually remove the registration at the end of the form too Signed-off-by: Joas Schilling --- lib/Controller/RegisterController.php | 3 +++ lib/Service/RegistrationService.php | 11 ----------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/Controller/RegisterController.php b/lib/Controller/RegisterController.php index 94abb43..30994b0 100644 --- a/lib/Controller/RegisterController.php +++ b/lib/Controller/RegisterController.php @@ -227,6 +227,9 @@ class RegisterController extends Controller { return $this->showUserForm($secret, $token, $username, $exception->getMessage()); } + // Delete registration + $this->registrationService->deleteRegistration($registration); + if ($user->isEnabled()) { $this->registrationService->loginUser($user->getUID(), $user->getUID(), $password); diff --git a/lib/Service/RegistrationService.php b/lib/Service/RegistrationService.php index 2dfd3b3..a5c4c80 100644 --- a/lib/Service/RegistrationService.php +++ b/lib/Service/RegistrationService.php @@ -322,17 +322,6 @@ class RegistrationService { $user->setEnabled(false); } - // Delete pending registration if no client secret is stored - // with client secret implies registered via API - // without client secret implies registered via form - // if registered via API, the registration request will be deleted in apicontroller::status - if ($registration->getClientSecret() === null) { - $res = $this->registrationMapper->delete($registration); - if ($res === false) { - throw new RegistrationException($this->l10n->t('Failed to delete pending registration request')); - } - } - $this->mailService->notifyAdmins($userId, $user->isEnabled(), $groupId); return $user; } From 6f6057d0a949596dc41b776fe56f7e765760833f Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 17 Jul 2020 14:41:50 +0200 Subject: [PATCH 05/10] Add unit tests Signed-off-by: Joas Schilling --- css/register-button.css | 6 +- lib/Controller/RegisterController.php | 4 +- .../Controller/RegisterControllerTest.php | 470 ++++++++++++++++++ 3 files changed, 478 insertions(+), 2 deletions(-) create mode 100644 tests/Unit/Controller/RegisterControllerTest.php diff --git a/css/register-button.css b/css/register-button.css index a732b81..9cf50b4 100644 --- a/css/register-button.css +++ b/css/register-button.css @@ -2,7 +2,11 @@ color: var(--color-primary-element); } -.register-button:only-child { +#alternative-logins .register-button:only-child { width: 220px; margin-left: 70px !important; } + +#alternative-logins .register-button.hidden { + display: none; +} diff --git a/lib/Controller/RegisterController.php b/lib/Controller/RegisterController.php index 30994b0..4a4e167 100644 --- a/lib/Controller/RegisterController.php +++ b/lib/Controller/RegisterController.php @@ -102,10 +102,11 @@ class RegisterController extends Controller { // No registration in progress try { $this->registrationService->validateEmail($email); - $registration = $this->registrationService->createRegistration($email); } catch (RegistrationException $e) { return $this->showEmailForm($email, $e->getMessage()); } + + $registration = $this->registrationService->createRegistration($email); } try { @@ -203,6 +204,7 @@ class RegisterController extends Controller { /** * @PublicPage * @UseSession + * @AnonRateThrottle(limit=5, period=1) * * @param string $secret * @param string $token diff --git a/tests/Unit/Controller/RegisterControllerTest.php b/tests/Unit/Controller/RegisterControllerTest.php new file mode 100644 index 0000000..25ab347 --- /dev/null +++ b/tests/Unit/Controller/RegisterControllerTest.php @@ -0,0 +1,470 @@ + + * + * @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 . + * + */ + +namespace OCA\Registration\Tests\Unit\Controller; + +use OCA\Registration\Controller\RegisterController; +use OCA\Registration\Db\Registration; +use OCA\Registration\Service\LoginFlowService; +use OCA\Registration\Service\MailService; +use OCA\Registration\Service\RegistrationException; +use OCA\Registration\Service\RegistrationService; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Http\RedirectResponse; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\IL10N; +use OCP\IRequest; +use OCP\IURLGenerator; +use ChristophWurst\Nextcloud\Testing\TestCase; +use PHPUnit\Framework\MockObject\MockObject; + +class RegisterControllerTest extends TestCase { + + /** @var IRequest */ + private $request; + /** @var IL10N|MockObject */ + private $l10n; + /** @var IURLGenerator|MockObject */ + private $urlGenerator; + /** @var RegistrationService|MockObject */ + private $registrationService; + /** @var LoginFlowService|MockObject */ + private $loginFlowService; + /** @var MailService|MockObject */ + private $mailService; + + public function setUp(): void { + parent::setUp(); + $this->request = $this->createMock(IRequest::class); + $this->l10n = $this->createMock(IL10N::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->registrationService = $this->createMock(RegistrationService::class); + $this->loginFlowService = $this->createMock(LoginFlowService::class); + $this->mailService = $this->createMock(MailService::class); + + $this->l10n->expects($this->any()) + ->method('t') + ->willReturnCallback(function ($text, $parameters = []) { + return vsprintf($text, $parameters); + }); + } + + /** + * @param string[] $methods + * @return RegisterController|MockObject + */ + protected function getController(array $methods = []) { + if (empty($methods)) { + return new RegisterController( + 'registration', + $this->request, + $this->l10n, + $this->urlGenerator, + $this->registrationService, + $this->loginFlowService, + $this->mailService + ); + } + + return $this->getMockBuilder(RegisterController::class) + ->onlyMethods($methods) + ->setConstructorArgs([ + 'registration', + $this->request, + $this->l10n, + $this->urlGenerator, + $this->registrationService, + $this->loginFlowService, + $this->mailService, + ]) + ->getMock(); + } + + public function dataShowEmailForm(): array { + return [ + ['', ''], + ['test@example.tld', 'Registration is only allowed for the following domains: nextcloud.com'], + ]; + } + + /** + * @dataProvider dataShowEmailForm + * @param string $email + * @param string $message + */ + public function testShowEmailForm(string $email, string $message): void { + $controller = $this->getController(); + $response = $controller->showEmailForm($email, $message); + + self::assertSame(TemplateResponse::RENDER_AS_GUEST, $response->getRenderAs()); + self::assertSame('form/email', $response->getTemplateName()); + + self::assertSame([ + 'email' => $email, + 'message' => $message, + ], $response->getParams()); + } + + public function testSubmitEmailForm(): void { + $email = 'nextcloud@example.tld'; + + $this->registrationService + ->method('getRegistrationForEmail') + ->with($email) + ->willThrowException(new DoesNotExistException($email)); + + $registration = Registration::fromParams([ + 'clientSecret' => 'clientSecret', + ]); + + $this->registrationService + ->expects($this->once()) + ->method('validateEmail') + ->with($email); + $this->registrationService + ->expects($this->once()) + ->method('createRegistration') + ->with($email) + ->willReturn($registration); + + $this->mailService + ->expects($this->once()) + ->method('sendTokenByMail') + ->with($registration); + + $this->urlGenerator + ->method('linkToRoute') + ->willReturnCallback(function () { + return json_encode(func_get_args()); + }); + + $controller = $this->getController(); + $response = $controller->submitEmailForm($email); + + self::assertInstanceOf(RedirectResponse::class, $response); + /** @var RedirectResponse $response */ + self::assertSame('["registration.register.showVerificationForm",{"secret":"clientSecret"}]', $response->getRedirectURL()); + } + + public function testSubmitEmailFormInvalidEmail(): void { + $email = 'nextcloud@example.tld'; + + $this->registrationService + ->method('getRegistrationForEmail') + ->with($email) + ->willThrowException(new DoesNotExistException($email)); + + $this->registrationService + ->expects($this->once()) + ->method('validateEmail') + ->with($email) + ->willThrowException(new RegistrationException('Invalid email')); + $this->registrationService + ->expects($this->never()) + ->method('createRegistration'); + + $controller = $this->getController([ + 'showEmailForm', + ]); + + $response = $this->createMock(TemplateResponse::class); + $controller->expects($this->once()) + ->method('showEmailForm') + ->with($email, 'Invalid email') + ->willReturn($response); + + self::assertSame($response, $controller->submitEmailForm($email)); + } + + public function testSubmitEmailFormErrorSendingEmail(): void { + $email = 'nextcloud@example.tld'; + + $this->registrationService + ->method('getRegistrationForEmail') + ->with($email) + ->willThrowException(new DoesNotExistException($email)); + + $registration = Registration::fromParams([ + 'clientSecret' => 'clientSecret', + ]); + + $this->registrationService + ->expects($this->once()) + ->method('validateEmail') + ->with($email); + $this->registrationService + ->expects($this->once()) + ->method('createRegistration') + ->with($email) + ->willReturn($registration); + + $this->mailService + ->expects($this->once()) + ->method('sendTokenByMail') + ->with($registration) + ->willThrowException(new RegistrationException('Error sending email')); + + $controller = $this->getController([ + 'showEmailForm', + ]); + + $response = $this->createMock(TemplateResponse::class); + $controller->expects($this->once()) + ->method('showEmailForm') + ->with($email, 'Error sending email') + ->willReturn($response); + + self::assertSame($response, $controller->submitEmailForm($email)); + } + + public function testSubmitEmailFormResendPendingRequest(): void { + $email = 'nextcloud@example.tld'; + + $registration = Registration::fromParams([ + 'clientSecret' => 'clientSecret', + ]); + + $this->registrationService + ->method('getRegistrationForEmail') + ->with($email) + ->willReturn($registration); + + $this->registrationService + ->expects($this->once()) + ->method('generateNewToken') + ->with($registration); + + $this->mailService + ->expects($this->once()) + ->method('sendTokenByMail') + ->with($registration); + + $this->urlGenerator + ->method('linkToRoute') + ->willReturnCallback(function () { + return json_encode(func_get_args()); + }); + + $controller = $this->getController(); + $response = $controller->submitEmailForm($email); + self::assertInstanceOf(RedirectResponse::class, $response); + /** @var RedirectResponse $response */ + self::assertSame('["registration.register.showVerificationForm",{"secret":"clientSecret"}]', $response->getRedirectURL()); + } + + public function dataShowVerificationForm(): array { + return [ + [''], + ['The entered verification code is wrong'], + ]; + } + + /** + * @dataProvider dataShowVerificationForm + * @param string $message + */ + public function testShowVerificationForm(string $message): void { + $secret = '123456789'; + + $this->registrationService + ->expects($this->once()) + ->method('getRegistrationForSecret') + ->with($secret); + + $controller = $this->getController(); + $response = $controller->showVerificationForm($secret, $message); + + self::assertSame(TemplateResponse::RENDER_AS_GUEST, $response->getRenderAs()); + self::assertSame('form/verification', $response->getTemplateName()); + + self::assertSame([ + 'message' => $message, + ], $response->getParams()); + } + + public function testShowVerificationFormInvalidSecret(): void { + $secret = '123456789'; + $message = ''; + + $this->registrationService + ->expects($this->once()) + ->method('getRegistrationForSecret') + ->with($secret) + ->willThrowException(new DoesNotExistException('Not found')); + + $response = $this->createMock(TemplateResponse::class); + $controller = $this->getController([ + 'validateSecretAndTokenErrorPage' + ]); + + $controller->expects($this->once()) + ->method('validateSecretAndTokenErrorPage') + ->willReturn($response); + + self::assertSame($response, $controller->showVerificationForm($secret, $message)); + } + + public function testSubmitVerificationForm(): void { + $secret = '123456789'; + $token = 'abcdefghi'; + + $registration = Registration::fromParams([ + 'clientSecret' => $secret, + 'token' => $token, + ]); + + $this->registrationService + ->expects($this->once()) + ->method('getRegistrationForSecret') + ->with($secret) + ->willReturn($registration); + + $this->urlGenerator + ->method('linkToRoute') + ->willReturnCallback(function () { + return json_encode(func_get_args()); + }); + + $controller = $this->getController(); + $response = $controller->submitVerificationForm($secret, $token); + self::assertInstanceOf(RedirectResponse::class, $response); + /** @var RedirectResponse $response */ + self::assertSame('["registration.register.showUserForm",{"secret":"123456789","token":"abcdefghi"}]', $response->getRedirectURL()); + } + + public function testSubmitVerificationFormInvalidToken(): void { + $secret = '123456789'; + $token = 'abcdefghi'; + + $registration = Registration::fromParams([ + 'clientSecret' => $secret, + 'token' => 'zyxwvu', + ]); + + $this->registrationService + ->expects($this->once()) + ->method('getRegistrationForSecret') + ->with($secret) + ->willReturn($registration); + + $response = $this->createMock(TemplateResponse::class); + $controller = $this->getController([ + 'showVerificationForm', + ]); + $controller->expects($this->once()) + ->method('showVerificationForm') + ->with($secret, 'The entered verification code is wrong') + ->willReturn($response); + + self::assertSame($response, $controller->submitVerificationForm($secret, $token)); + } + + public function testSubmitVerificationFormInvalidSecret(): void { + $secret = '123456789'; + $token = 'abcdefghi'; + + $registration = Registration::fromParams([ + 'clientSecret' => $secret, + 'token' => $token, + ]); + + $this->registrationService + ->expects($this->once()) + ->method('getRegistrationForSecret') + ->with($secret) + ->willThrowException(new DoesNotExistException('Invalid secret')); + + $response = $this->createMock(TemplateResponse::class); + $controller = $this->getController([ + 'validateSecretAndTokenErrorPage', + ]); + $controller->expects($this->once()) + ->method('validateSecretAndTokenErrorPage') + ->willReturn($response); + + self::assertSame($response, $controller->submitVerificationForm($secret, $token)); + } + + public function dataShowUserForm(): array { + return [ + ['', ''], + ['tester', ''], + ['', 'Unable to create user, there are problems with the user backend.'], + ]; + } + + /** + * @dataProvider dataShowUserForm + * @param string $username + * @param string $message + */ + public function testShowUserForm(string $username, string $message): void { + $secret = '123456789'; + $token = 'abcdefghi'; + $email = 'nextcloud@example.tld'; + + $registration = Registration::fromParams([ + 'email' => 'nextcloud@example.tld', + ]); + + $controller = $this->getController([ + 'validateSecretAndToken' + ]); + + $controller->expects($this->once()) + ->method('validateSecretAndToken') + ->willReturn($registration); + + $response = $controller->showUserForm($secret, $token, $username, $message); + + self::assertSame(TemplateResponse::RENDER_AS_GUEST, $response->getRenderAs()); + self::assertSame('form/user', $response->getTemplateName()); + + self::assertSame([ + 'email' => $email, + 'username' => $username, + 'message' => $message, + ], $response->getParams()); + } + + public function testShowUserFormInvalidSecretAndToken(): void { + $secret = '123456789'; + $token = 'abcdefghi'; + + $controller = $this->getController([ + 'validateSecretAndToken', + 'validateSecretAndTokenErrorPage', + ]); + + $controller->expects($this->once()) + ->method('validateSecretAndToken') + ->willThrowException(new RegistrationException('Invalid secret or token')); + + $response = $this->createMock(TemplateResponse::class); + $controller->expects($this->once()) + ->method('validateSecretAndTokenErrorPage') + ->willReturn($response); + + self::assertSame($response, $controller->showUserForm($secret, $token)); + } +} From 5f9ca2a8aa16cbacbd1ee6aa60b022965ff10909 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 21 Jul 2020 21:16:18 +0200 Subject: [PATCH 06/10] Add a warning about admin approval breaking login flows Signed-off-by: Joas Schilling --- templates/admin.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/templates/admin.php b/templates/admin.php index 16ff9e5..e9aeb12 100644 --- a/templates/admin.php +++ b/templates/admin.php @@ -26,9 +26,13 @@ foreach ($_['groups'] as $group) {

- + > - + +

+ + t('Enabling "admin approval" will prevent registrations from mobile and desktop clients to complete as the credentials can not be verified by the client until the user was enabled.'));?>
From b5ccb3e1846d9e81b9e5f7ad137e67f1f8e1277d Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 29 Jul 2020 17:45:52 +0200 Subject: [PATCH 07/10] Add tests for submitUserForm Signed-off-by: Joas Schilling --- .../Controller/RegisterControllerTest.php | 240 ++++++++++++++++++ 1 file changed, 240 insertions(+) diff --git a/tests/Unit/Controller/RegisterControllerTest.php b/tests/Unit/Controller/RegisterControllerTest.php index 25ab347..f9721f2 100644 --- a/tests/Unit/Controller/RegisterControllerTest.php +++ b/tests/Unit/Controller/RegisterControllerTest.php @@ -30,12 +30,16 @@ use OCA\Registration\Service\MailService; use OCA\Registration\Service\RegistrationException; use OCA\Registration\Service\RegistrationService; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Http; use OCP\AppFramework\Http\RedirectResponse; +use OCP\AppFramework\Http\RedirectToDefaultAppResponse; +use OCP\AppFramework\Http\StandaloneTemplateResponse; use OCP\AppFramework\Http\TemplateResponse; use OCP\IL10N; use OCP\IRequest; use OCP\IURLGenerator; use ChristophWurst\Nextcloud\Testing\TestCase; +use OCP\IUser; use PHPUnit\Framework\MockObject\MockObject; class RegisterControllerTest extends TestCase { @@ -467,4 +471,240 @@ class RegisterControllerTest extends TestCase { self::assertSame($response, $controller->showUserForm($secret, $token)); } + + public function testSubmitUserFormInvalidSecretAndToken(): void { + $secret = '123456789'; + $token = 'abcdefghi'; + + $controller = $this->getController([ + 'validateSecretAndToken', + 'validateSecretAndTokenErrorPage', + ]); + + $controller->expects($this->once()) + ->method('validateSecretAndToken') + ->willThrowException(new RegistrationException('Invalid secret or token')); + + $response = $this->createMock(TemplateResponse::class); + $controller->expects($this->once()) + ->method('validateSecretAndTokenErrorPage') + ->willReturn($response); + + self::assertSame($response, $controller->submitUserForm($secret, $token, '', '')); + } + + public function testSubmitUserFormCreateAccountException(): void { + $secret = '123456789'; + $token = 'abcdefghi'; + $username = 'user'; + $password = 'password'; + + $registration = Registration::fromParams([ + 'email' => 'nextcloud@example.tld', + ]); + + $controller = $this->getController([ + 'validateSecretAndToken', + 'showUserForm' + ]); + + $controller->expects($this->once()) + ->method('validateSecretAndToken') + ->willReturn($registration); + + $response = $this->createMock(TemplateResponse::class); + $controller->expects($this->once()) + ->method('showUserForm') + ->willReturn($response); + + $this->registrationService->expects($this->once()) + ->method('createAccount') + ->with($registration, $username, $password) + ->willThrowException(new RegistrationException('Invalid account data')); + + self::assertSame($response, $controller->submitUserForm($secret, $token, $username, $password)); + } + + public function testSubmitUserFormRequiresAdminApproval(): void { + $secret = '123456789'; + $token = 'abcdefghi'; + $username = 'user'; + $password = 'password'; + + $registration = Registration::fromParams([ + 'email' => 'nextcloud@example.tld', + ]); + + $controller = $this->getController([ + 'validateSecretAndToken', + 'showUserForm' + ]); + + $controller->expects($this->once()) + ->method('validateSecretAndToken') + ->willReturn($registration); + + $user = $this->createMock(IUser::class); + $user->method('isEnabled') + ->willReturn(false); + + $this->registrationService->expects($this->once()) + ->method('createAccount') + ->with($registration, $username, $password) + ->willReturn($user); + + $this->registrationService->expects($this->once()) + ->method('deleteRegistration') + ->with($registration); + + $response = $controller->submitUserForm($secret, $token, $username, $password); + + self::assertInstanceOf(StandaloneTemplateResponse::class, $response); + self::assertSame(TemplateResponse::RENDER_AS_GUEST, $response->getRenderAs()); + self::assertSame('approval-required', $response->getTemplateName()); + } + + public function testSubmitUserFormSuccessful(): void { + $secret = '123456789'; + $token = 'abcdefghi'; + $username = 'user'; + $password = 'password'; + + $registration = Registration::fromParams([ + 'email' => 'nextcloud@example.tld', + ]); + + $controller = $this->getController([ + 'validateSecretAndToken', + 'showUserForm' + ]); + + $controller->expects($this->once()) + ->method('validateSecretAndToken') + ->willReturn($registration); + + $user = $this->createMock(IUser::class); + $user->method('isEnabled') + ->willReturn(true); + $user->method('getUID') + ->willReturn($username); + + $this->registrationService->expects($this->once()) + ->method('createAccount') + ->with($registration, $username, $password) + ->willReturn($user); + + $this->registrationService->expects($this->once()) + ->method('deleteRegistration') + ->with($registration); + + $this->registrationService->expects($this->once()) + ->method('loginUser') + ->with($username, $username, $password); + + $response = $controller->submitUserForm($secret, $token, $username, $password); + + self::assertInstanceOf(RedirectToDefaultAppResponse::class, $response); + } + + public function testSubmitUserFormSuccessfulLoginFlow2(): void { + $secret = '123456789'; + $token = 'abcdefghi'; + $username = 'user'; + $password = 'password'; + + $registration = Registration::fromParams([ + 'email' => 'nextcloud@example.tld', + ]); + + $controller = $this->getController([ + 'validateSecretAndToken', + 'showUserForm' + ]); + + $controller->expects($this->once()) + ->method('validateSecretAndToken') + ->willReturn($registration); + + $user = $this->createMock(IUser::class); + $user->method('isEnabled') + ->willReturn(true); + $user->method('getUID') + ->willReturn($username); + + $this->registrationService->expects($this->once()) + ->method('createAccount') + ->with($registration, $username, $password) + ->willReturn($user); + + $this->registrationService->expects($this->once()) + ->method('deleteRegistration') + ->with($registration); + + $this->registrationService->expects($this->once()) + ->method('loginUser') + ->with($username, $username, $password); + + $this->loginFlowService->method('isUsingLoginFlow') + ->with(2) + ->willReturn(true); + + $response = $this->createMock(StandaloneTemplateResponse::class); + $this->loginFlowService->method('tryLoginFlowV2') + ->with($user) + ->willReturn($response); + + self::assertSame($response, $controller->submitUserForm($secret, $token, $username, $password)); + } + + public function testSubmitUserFormSuccessfulLoginFlow1(): void { + $secret = '123456789'; + $token = 'abcdefghi'; + $username = 'user'; + $password = 'password'; + + $registration = Registration::fromParams([ + 'email' => 'nextcloud@example.tld', + ]); + + $controller = $this->getController([ + 'validateSecretAndToken', + 'showUserForm' + ]); + + $controller->expects($this->once()) + ->method('validateSecretAndToken') + ->willReturn($registration); + + $user = $this->createMock(IUser::class); + $user->method('isEnabled') + ->willReturn(true); + $user->method('getUID') + ->willReturn($username); + + $this->registrationService->expects($this->once()) + ->method('createAccount') + ->with($registration, $username, $password) + ->willReturn($user); + + $this->registrationService->expects($this->once()) + ->method('deleteRegistration') + ->with($registration); + + $this->registrationService->expects($this->once()) + ->method('loginUser') + ->with($username, $username, $password); + + $this->loginFlowService->method('isUsingLoginFlow') + ->withConsecutive([2], [1]) + ->willReturnOnConsecutiveCalls(false, true); + + $response = $this->createMock(RedirectResponse::class); + $response->method('getStatus') + ->willReturn(Http::STATUS_SEE_OTHER); + $this->loginFlowService->method('tryLoginFlowV1') + ->willReturn($response); + + self::assertSame($response, $controller->submitUserForm($secret, $token, $username, $password)); + } } From 906b1f8db2b6254dd4f224eaf692b672251b8886 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 29 Jul 2020 17:46:38 +0200 Subject: [PATCH 08/10] Improve code coverage handling Signed-off-by: Joas Schilling --- .gitignore | 2 ++ tests/phpunit.xml | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5f4186b..48cf206 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /.php_cs.cache +/tests/clover.xml +/tests/coverage-html /tests/.phpunit.result.cache /vendor diff --git a/tests/phpunit.xml b/tests/phpunit.xml index b833956..b437e68 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -9,7 +9,8 @@ - ../ + ../../registration/appinfo + ../../registration/lib From 150e58880e2df492f458745d43edfe88bc8978db Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 29 Jul 2020 18:06:05 +0200 Subject: [PATCH 09/10] Fix other tests Signed-off-by: Joas Schilling --- lib/Controller/RegisterController.php | 1 - lib/Service/RegistrationService.php | 20 +-- .../Controller/RegisterControllerTest.php | 138 ------------------ .../Service/RegistrationServiceTest.php | 39 +++-- tests/Unit/Controller/ApiControllerTest.php | 4 + .../Controller/RegisterControllerTest.php | 7 + 6 files changed, 45 insertions(+), 164 deletions(-) delete mode 100644 tests/Integration/Controller/RegisterControllerTest.php diff --git a/lib/Controller/RegisterController.php b/lib/Controller/RegisterController.php index 4a4e167..cc5ea0e 100644 --- a/lib/Controller/RegisterController.php +++ b/lib/Controller/RegisterController.php @@ -34,7 +34,6 @@ use OCP\IL10N; use OCP\IRequest; use OCP\IURLGenerator; use OCP\IConfig; -use OCP\IL10N; class RegisterController extends Controller { diff --git a/lib/Service/RegistrationService.php b/lib/Service/RegistrationService.php index a5c4c80..26bf83b 100644 --- a/lib/Service/RegistrationService.php +++ b/lib/Service/RegistrationService.php @@ -215,14 +215,14 @@ class RegistrationService { * @return bool */ public function checkAllowedDomains(string $email): bool { - $allowed_domains = $this->config->getAppValue($this->appName, 'allowed_domains', ''); - if ($allowed_domains !== '') { - $allowed_domains = explode(';', $allowed_domains); + $allowedDomains = $this->config->getAppValue($this->appName, 'allowed_domains', ''); + if ($allowedDomains !== '') { + $allowedDomains = explode(';', $allowedDomains); $allowed = false; - foreach ($allowed_domains as $domain) { - $maildomain = explode("@", $email)[1]; - // valid domain, everythings fine - if ($maildomain === $domain) { + foreach ($allowedDomains as $domain) { + [,$mailDomain] = explode('@', $email, 2); + // valid domain, everything's fine + if ($mailDomain === $domain) { $allowed = true; break; } @@ -236,9 +236,9 @@ class RegistrationService { * @return string[] */ public function getAllowedDomains(): array { - $allowed_domains = $this->config->getAppValue($this->appName, 'allowed_domains', ''); - $allowed_domains = explode(';', $allowed_domains); - return $allowed_domains; + $allowedDomains = $this->config->getAppValue($this->appName, 'allowed_domains', ''); + $allowedDomains = explode(';', $allowedDomains); + return $allowedDomains; } /** diff --git a/tests/Integration/Controller/RegisterControllerTest.php b/tests/Integration/Controller/RegisterControllerTest.php deleted file mode 100644 index b3bb45b..0000000 --- a/tests/Integration/Controller/RegisterControllerTest.php +++ /dev/null @@ -1,138 +0,0 @@ -mailService = $this->createMock(MailService::class); - $this->l10n = $this->createMock(IL10N::class); - $this->urlGenerator = $this->createMock(IURLGenerator::class); - #$this->userManager = $this->createMock(IUserManager::class); - $this->userManager = \OC::$server->getUserManager(); - $this->config = $this->createMock(IConfig::class); - $this->groupManager = \OC::$server->getGroupManager(); - $this->random = \OC::$server->getSecureRandom(); - $this->usersession = $this->createMock(IUserSession::class); - $this->request = $this->createMock(IRequest::class); - $this->logger = $this->createMock(ILogger::class); - $this->session = $this->createMock(ISession::class); - $this->tokenProvider = $this->createMock(IProvider::class); - $this->crypto = $this->createMock(ICrypto::class); - - $this->registrationMapper = new RegistrationMapper( - \OC::$server->getDatabaseConnection(), - $this->random - ); - - $this->registrationService = new RegistrationService( - 'registration', - $this->mailService, - $this->l10n, - $this->urlGenerator, - $this->registrationMapper, - $this->userManager, - $this->config, - $this->groupManager, - $this->random, - $this->usersession, - $this->request, - $this->logger, - $this->session, - $this->tokenProvider, - $this->crypto - ); - - $this->controller = new RegisterController( - 'registration', - $this->request, - $this->l10n, - $this->urlGenerator, - $this->config, - $this->registrationService, - $this->mailService - ); - } - - public function testValidateEmailNormal() { - $email = 'aaaa@example.com'; - - $this->config->expects($this->atLeastOnce()) - ->method('getAppValue') - ->with("registration", 'allowed_domains', '') - ->willReturn(''); - $this->mailService->expects($this->once()) - ->method('sendTokenByMail'); - - $this->assertEquals($this->registrationService->validateEmail($email), true); - - $ret = $this->controller->validateEmail($email); - - $expected = new TemplateResponse('registration', 'message', ['msg' => - $this->l10n->t('Verification email successfully sent.') - ], 'guest'); - - - $this->assertEquals($expected, $ret, print_r($ret, true)); - } -} diff --git a/tests/Integration/Service/RegistrationServiceTest.php b/tests/Integration/Service/RegistrationServiceTest.php index 0ab0aaa..4130ffd 100644 --- a/tests/Integration/Service/RegistrationServiceTest.php +++ b/tests/Integration/Service/RegistrationServiceTest.php @@ -61,6 +61,9 @@ class RegistrationServiceTest extends TestCase { /** @var ICrypto */ private $crypto; + /** @var RegistrationService */ + private $service; + public function setUp(): void { parent::setUp(); $this->mailService = $this->createMock(MailService::class); @@ -107,13 +110,10 @@ class RegistrationServiceTest extends TestCase { $this->config->expects($this->once()) ->method('getAppValue') - ->with("registration", 'allowed_domains', '') + ->with('registration', 'allowed_domains', '') ->willReturn(''); - $ret = $this->service->validateEmail($email); - - //$this->assertInstanceOf(Registration::class, $ret); - $this->assertTrue($ret); + $this->service->validateEmail($email); } public function testValidateNewEmailWithinAllowedDomain() { @@ -121,18 +121,23 @@ class RegistrationServiceTest extends TestCase { $this->config->expects($this->atLeastOnce()) ->method('getAppValue') - ->with("registration", 'allowed_domains', '') + ->with('registration', 'allowed_domains', '') ->willReturn('example.com'); - $ret = $this->service->validateEmail($email); - $this->assertTrue($ret, print_r($ret, true)); + $this->service->validateEmail($email); } + /** * @depends testValidateNewEmailWithinAllowedDomain */ public function testValidateNewEmailNotWithinAllowedDomain() { $email2 = 'bbbb@gmail.com'; + $this->config->expects($this->atLeastOnce()) + ->method('getAppValue') + ->with('registration', 'allowed_domains', '') + ->willReturn('example.com'); + $this->expectException(RegistrationException::class); $this->service->validateEmail($email2); } @@ -143,18 +148,24 @@ class RegistrationServiceTest extends TestCase { $this->config->expects($this->atLeastOnce()) ->method('getAppValue') - ->with("registration", 'allowed_domains', '') + ->with('registration', 'allowed_domains', '') ->willReturn('example.com;gmail.com'); - $this->assertTrue($this->service->validateEmail($email)); - $this->assertTrue($this->service->validateEmail($email2)); + $this->service->validateEmail($email); + $this->service->validateEmail($email2); } + /** * @depends testValidateNewEmailWithinMultipleAllowedDomain */ public function testValidateNewEmailNotWithinMultipleAllowedDomain() { $email2 = 'cccc@yahoo.com'; + $this->config->expects($this->atLeastOnce()) + ->method('getAppValue') + ->with('registration', 'allowed_domains', '') + ->willReturn('example.com;gmail.com'); + $this->expectException(RegistrationException::class); $this->service->validateEmail($email2); } @@ -180,10 +191,8 @@ class RegistrationServiceTest extends TestCase { $email = 'aaaa@example.com'; $this->service->createRegistration($email, 'alice'); - $ret = $this->service->validateEmail($email); - - $this->assertInstanceOf(Registration::class, $ret); - $this->assertEquals($email, $ret->getEmail()); + $this->expectException(RegistrationException::class); + $this->service->validateEmail($email); } public function testCreateAccountWebForm() { diff --git a/tests/Unit/Controller/ApiControllerTest.php b/tests/Unit/Controller/ApiControllerTest.php index 2701f42..e37d46a 100644 --- a/tests/Unit/Controller/ApiControllerTest.php +++ b/tests/Unit/Controller/ApiControllerTest.php @@ -157,7 +157,11 @@ class ApiControllerTest extends TestCase { $registration = new Registration(); $registration->setEmailConfirmed(true); $registration->setClientSecret('mysecret'); + $registration->setUsername('user'); + $registration->setPassword('password'); $user = $this->createMock(IUser::class); + $user->method('getUID') + ->willReturn('user'); $this->registrationService ->method('getRegistrationForSecret') ->with('mysecret') diff --git a/tests/Unit/Controller/RegisterControllerTest.php b/tests/Unit/Controller/RegisterControllerTest.php index f9721f2..896f209 100644 --- a/tests/Unit/Controller/RegisterControllerTest.php +++ b/tests/Unit/Controller/RegisterControllerTest.php @@ -35,6 +35,7 @@ use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\RedirectToDefaultAppResponse; use OCP\AppFramework\Http\StandaloneTemplateResponse; use OCP\AppFramework\Http\TemplateResponse; +use OCP\IConfig; use OCP\IL10N; use OCP\IRequest; use OCP\IURLGenerator; @@ -50,6 +51,8 @@ class RegisterControllerTest extends TestCase { private $l10n; /** @var IURLGenerator|MockObject */ private $urlGenerator; + /** @var IConfig|MockObject */ + private $config; /** @var RegistrationService|MockObject */ private $registrationService; /** @var LoginFlowService|MockObject */ @@ -62,6 +65,7 @@ class RegisterControllerTest extends TestCase { $this->request = $this->createMock(IRequest::class); $this->l10n = $this->createMock(IL10N::class); $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->config = $this->createMock(IConfig::class); $this->registrationService = $this->createMock(RegistrationService::class); $this->loginFlowService = $this->createMock(LoginFlowService::class); $this->mailService = $this->createMock(MailService::class); @@ -84,6 +88,7 @@ class RegisterControllerTest extends TestCase { $this->request, $this->l10n, $this->urlGenerator, + $this->config, $this->registrationService, $this->loginFlowService, $this->mailService @@ -97,6 +102,7 @@ class RegisterControllerTest extends TestCase { $this->request, $this->l10n, $this->urlGenerator, + $this->config, $this->registrationService, $this->loginFlowService, $this->mailService, @@ -446,6 +452,7 @@ class RegisterControllerTest extends TestCase { self::assertSame([ 'email' => $email, + 'email_is_login' => false, 'username' => $username, 'message' => $message, ], $response->getParams()); From ff71229016b0a7bf42c7aafcf0a3cdae9ced9dad Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 18 Aug 2020 17:30:41 +0200 Subject: [PATCH 10/10] Fix registration flow with email as name Signed-off-by: Joas Schilling --- templates/form/user.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/templates/form/user.php b/templates/form/user.php index c9e81b0..c3f3dc0 100644 --- a/templates/form/user.php +++ b/templates/form/user.php @@ -23,7 +23,7 @@ script('registration', 'form');

- +

t('Username')); ?>

+ +