Redirect to ClientLoginFlow and ClientLoginFlowV2 when it was used

Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
Joas Schilling 2020-07-16 21:15:39 +02:00
parent 4d5fb2628a
commit 0b4fac2edf
No known key found for this signature in database
GPG Key ID: 7076EA9751AACDDA
6 changed files with 231 additions and 138 deletions

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/**
* ownCloud - registration
*
@ -7,53 +10,63 @@
*
* @author Pellaeon Lin <pellaeon@hs.ntnu.edu.tw>
* @author Julius Härtl <jus@bitgrid.net>
* @author 2020 Joas Schilling <coding@schilljs.com>
* @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 <a href="%s">log in now</a>.', [$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');
}
}

View File

@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Joas Schilling <coding@schilljs.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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;
}
}

View File

@ -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 <a href="%s">log in now</a>.', [$this->urlGenerator->getAbsoluteURL('/')])]
, 'guest'
);
$this->usersession->login($username, $password);
$this->usersession->createSessionToken($this->request, $userId, $username, $password);
}
/**

View File

@ -0,0 +1,13 @@
<?php
/** @var array $_ */
/** @var \OCP\IL10N $l */
style('registration', 'style');
?>
<div class="error">
<h2><?php p($l->t('Approval required')) ?></h2>
<ul>
<li>
<p><?php p($l->t('Your account has been successfully created, but it still needs approval from an administrator.')) ?></p>
</li>
</ul>
</div>

View File

@ -1,16 +0,0 @@
<?php
/** @var array $_ */
/** @var \OCP\IL10N $l */
style('registration', 'style');
?>
<ul class="error-wide">
<li class='error'><?php p($l->t('Registration is only allowed for the following domains:')); ?>
<?php
foreach ($_['domains'] as $domain) {
echo "<p class='hint'>";
p($domain);
echo "</p>";
}
?>
</li>
</ul>

View File

@ -1,8 +0,0 @@
<?php
/** @var array $_ */
/** @var \OCP\IL10N $l */
style('registration', 'style');
?>
<ul class="msg error-wide nc-theming-main-text">
<li><?php print_unescaped($_['msg'])?></li>
</ul>