Files
nextcloud-registration/lib/Controller/RegisterController.php
Andy Scherzinger b8b63cf0bf docs(reuse): Add SPDX header
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2024-10-14 08:49:14 +02:00

357 lines
13 KiB
PHP

<?php
declare(strict_types=1);
/*
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2015 Johannes Starosta <j.starosta@tu-braunschweig.de>
* SPDX-FileCopyrightText: 2014 Pellaeon Lin <pellaeon@hs.ntnu.edu.tw>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Registration\Controller;
use Exception;
use OCA\Registration\AppInfo\Application;
use OCA\Registration\Db\Registration;
use OCA\Registration\Events\PassedFormEvent;
use OCA\Registration\Events\ShowFormEvent;
use OCA\Registration\Events\ValidateFormEvent;
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\ContentSecurityPolicy;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\StandaloneTemplateResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\HintException;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\Util;
class RegisterController extends Controller {
private IL10N $l10n;
private IURLGenerator $urlGenerator;
private IConfig $config;
private RegistrationService $registrationService;
private MailService $mailService;
private LoginFlowService $loginFlowService;
private IEventDispatcher $eventDispatcher;
private IInitialState $initialState;
public function __construct(
string $appName,
IRequest $request,
IL10N $l10n,
IURLGenerator $urlGenerator,
IConfig $config,
RegistrationService $registrationService,
LoginFlowService $loginFlowService,
MailService $mailService,
IEventDispatcher $eventDispatcher,
IInitialState $initialState
) {
parent::__construct($appName, $request);
$this->l10n = $l10n;
$this->urlGenerator = $urlGenerator;
$this->config = $config;
$this->registrationService = $registrationService;
$this->loginFlowService = $loginFlowService;
$this->mailService = $mailService;
$this->eventDispatcher = $eventDispatcher;
$this->initialState = $initialState;
}
/**
* @NoCSRFRequired
* @PublicPage
*/
public function showEmailForm(string $email = '', string $message = ''): TemplateResponse {
$emailHint = '';
$domainList = $this->registrationService->getAllowedDomains();
if (!empty($domainList) && $this->config->getAppValue(Application::APP_ID, 'show_domains', 'no') === 'yes') {
$domainList = implode(', ', $domainList);
if ($this->config->getAppValue(Application::APP_ID, 'domains_is_blocklist', 'no') === 'yes') {
$emailHint = $this->l10n->t(
'Registration is not allowed with the following domains: %s',
[$domainList]
);
} else {
$emailHint = $this->l10n->t(
'Registration is only allowed with the following domains: %s',
[$domainList]
);
}
}
$this->eventDispatcher->dispatchTyped(new ShowFormEvent(ShowFormEvent::STEP_EMAIL));
$this->initialState->provideInitialState('email', $email);
$this->initialState->provideInitialState('message', $message ?: $emailHint);
$this->initialState->provideInitialState('emailIsOptional', $this->config->getAppValue($this->appName, 'email_is_optional', 'no') === 'yes');
$this->initialState->provideInitialState('disableEmailVerification', $this->config->getAppValue($this->appName, 'disable_email_verification', 'no') === 'yes');
$this->initialState->provideInitialState('isLoginFlow', $this->loginFlowService->isUsingLoginFlow());
$this->initialState->provideInitialState('loginFormLink', $this->urlGenerator->linkToRoute('core.login.showLoginForm'));
return new TemplateResponse('registration', 'form/email', [], 'guest');
}
/**
* @PublicPage
* @AnonRateThrottle(limit=5, period=300)
*/
public function submitEmailForm(string $email): Response {
$validateFormEvent = new ValidateFormEvent(ValidateFormEvent::STEP_EMAIL);
$this->eventDispatcher->dispatchTyped($validateFormEvent);
if (!empty($validateFormEvent->getErrors())) {
return $this->showEmailForm($email, implode(' ', $validateFormEvent->getErrors()));
}
try {
// 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 {
$email = trim($email);
$this->registrationService->validateEmail($email);
} catch (RegistrationException $e) {
return $this->showEmailForm($email, $e->getMessage());
}
$registration = $this->registrationService->createRegistration($email);
}
if ($this->config->getAppValue($this->appName, 'disable_email_verification', 'no') === 'yes') {
$this->eventDispatcher->dispatchTyped(new PassedFormEvent(PassedFormEvent::STEP_EMAIL, $registration->getClientSecret()));
return new RedirectResponse(
$this->urlGenerator->linkToRoute(
'registration.register.showUserForm',
[
'secret' => $registration->getClientSecret(),
'token' => $registration->getToken()
]
)
);
}
try {
$this->mailService->sendTokenByMail($registration);
} catch (RegistrationException $e) {
return $this->showEmailForm($email, $e->getMessage());
} catch (\Exception $e) {
return $this->showEmailForm($email, $this->l10n->t('A problem occurred sending email, please contact your administrator.'));
}
$this->eventDispatcher->dispatchTyped(new PassedFormEvent(PassedFormEvent::STEP_EMAIL, $registration->getClientSecret()));
return new RedirectResponse(
$this->urlGenerator->linkToRoute(
'registration.register.showVerificationForm',
['secret' => $registration->getClientSecret()]
)
);
}
/**
* @NoCSRFRequired
* @PublicPage
*/
public function showVerificationForm(string $secret, string $message = ''): TemplateResponse {
try {
$this->registrationService->getRegistrationForSecret($secret);
} catch (DoesNotExistException $e) {
return $this->validateSecretAndTokenErrorPage();
}
$this->eventDispatcher->dispatchTyped(new ShowFormEvent(ShowFormEvent::STEP_VERIFICATION, $secret));
$this->initialState->provideInitialState('message', $message);
$this->initialState->provideInitialState('loginFormLink', $this->urlGenerator->linkToRoute('core.login.showLoginForm'));
return new TemplateResponse('registration', 'form/verification', [], 'guest');
}
/**
* @PublicPage
* @AnonRateThrottle(limit=5, period=300)
*
* @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 (DoesNotExistException $e) {
return $this->validateSecretAndTokenErrorPage();
}
$validateFormEvent = new ValidateFormEvent(ValidateFormEvent::STEP_VERIFICATION, $secret);
$this->eventDispatcher->dispatchTyped($validateFormEvent);
if (!empty($validateFormEvent->getErrors())) {
return $this->showVerificationForm($secret, implode(' ', $validateFormEvent->getErrors()));
}
$this->eventDispatcher->dispatchTyped(new PassedFormEvent(PassedFormEvent::STEP_VERIFICATION, $secret));
return new RedirectResponse(
$this->urlGenerator->linkToRoute(
'registration.register.showUserForm',
[
'secret' => $secret,
'token' => $token,
]
)
);
}
/**
* @NoCSRFRequired
* @PublicPage
*/
public function showUserForm(string $secret, string $token, string $loginname = '', string $fullname = '', string $phone = '', string $password = '', string $message = ''): TemplateResponse {
try {
$registration = $this->validateSecretAndToken($secret, $token);
} catch (RegistrationException $e) {
return $this->validateSecretAndTokenErrorPage();
}
$additional_hint = $this->config->getAppValue('registration', 'additional_hint');
$this->eventDispatcher->dispatchTyped(new ShowFormEvent(ShowFormEvent::STEP_USER, $secret));
$this->initialState->provideInitialState('email', $registration->getEmail());
$this->initialState->provideInitialState('emailIsLogin', $this->config->getAppValue('registration', 'email_is_login', 'no') === 'yes');
$this->initialState->provideInitialState('emailIsOptional', $this->config->getAppValue('registration', 'email_is_optional', 'no') === 'yes');
$this->initialState->provideInitialState('loginname', $loginname);
$this->initialState->provideInitialState('fullname', $fullname);
$this->initialState->provideInitialState('showFullname', $this->config->getAppValue('registration', 'show_fullname', 'no') === 'yes');
$this->initialState->provideInitialState('enforceFullname', $this->config->getAppValue('registration', 'enforce_fullname', 'no') === 'yes');
$this->initialState->provideInitialState('phone', $phone);
$this->initialState->provideInitialState('showPhone', $this->config->getAppValue('registration', 'show_phone', 'no') === 'yes');
$this->initialState->provideInitialState('enforcePhone', $this->config->getAppValue('registration', 'enforce_phone', 'no') === 'yes');
$this->initialState->provideInitialState('message', $message);
$this->initialState->provideInitialState('password', $password);
$this->initialState->provideInitialState('additionalHint', $additional_hint);
$this->initialState->provideInitialState('loginFormLink', $this->urlGenerator->linkToRoute('core.login.showLoginForm'));
$response = new TemplateResponse('registration', 'form/user', [], 'guest');
if ($this->loginFlowService->isUsingLoginFlow(1)) {
$csp = new ContentSecurityPolicy();
$csp->addAllowedFormActionDomain('nc://*');
$response->setContentSecurityPolicy($csp);
}
return $response;
}
/**
* @PublicPage
* @UseSession
* @AnonRateThrottle(limit=5, period=300)
*
* @return RedirectResponse|TemplateResponse
*/
public function submitUserForm(string $secret, string $token, string $loginname, string $fullname, string $phone, string $password): Response {
try {
$registration = $this->validateSecretAndToken($secret, $token);
} catch (RegistrationException $e) {
return $this->validateSecretAndTokenErrorPage();
}
if ($this->config->getAppValue('registration', 'email_is_login', 'no') === 'yes') {
$loginname = $registration->getEmail();
}
$validateFormEvent = new ValidateFormEvent(ValidateFormEvent::STEP_USER, $secret);
$this->eventDispatcher->dispatchTyped($validateFormEvent);
if (!empty($validateFormEvent->getErrors())) {
return $this->showUserForm($secret, $token, $loginname, $fullname, $phone, $password, implode(' ', $validateFormEvent->getErrors()));
}
try {
$user = $this->registrationService->createAccount($registration, $loginname, $fullname, $phone, $password);
} catch (HintException $exception) {
return $this->showUserForm($secret, $token, $loginname, $fullname, $phone, $password, $exception->getHint());
} catch (Exception $exception) {
return $this->showUserForm($secret, $token, $loginname, $fullname, $phone, $password, $exception->getMessage());
}
// Delete registration
$this->registrationService->deleteRegistration($registration);
$this->eventDispatcher->dispatchTyped(new PassedFormEvent(PassedFormEvent::STEP_USER, $secret, $user));
if ($user->isEnabled()) {
$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 RedirectResponse($this->urlGenerator->linkToDefaultPageUrl());
}
Util::addStyle('registration', 'style');
// warn the user their account needs admin validation
return new StandaloneTemplateResponse('registration', 'approval-required', [], 'guest');
}
/**
* @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' => [
['error' => $this->l10n->t('The verification failed.')],
],
], 'error');
}
}