From 55c04b21ff2a3d13c027232c5614fe5e17419bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Fri, 9 Jun 2017 15:19:38 +0200 Subject: [PATCH 1/7] Some refactoring to make code simpler and more readable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Refactor database classes to use entity/mapper pattern - Use automatic class loading - Move logic to RegistrationService class so it is reusable for the api Signed-off-by: Julius Härtl --- admin.php | 5 +- appinfo/app.php | 10 +- appinfo/application.php | 100 -------- controller/registercontroller.php | 363 ++++++------------------------ controller/settingscontroller.php | 9 +- db/pendingregist.php | 49 ---- db/registration.php | 39 ++++ db/registrationmapper.php | 69 ++++++ service/registrationexception.php | 42 ++++ service/registrationservice.php | 342 ++++++++++++++++++++++++++++ 10 files changed, 576 insertions(+), 452 deletions(-) delete mode 100644 appinfo/application.php delete mode 100644 db/pendingregist.php create mode 100644 db/registration.php create mode 100644 db/registrationmapper.php create mode 100644 service/registrationexception.php create mode 100644 service/registrationservice.php diff --git a/admin.php b/admin.php index b085198..b8f3323 100644 --- a/admin.php +++ b/admin.php @@ -11,6 +11,7 @@ namespace OCA\Registration\AppInfo; -$app = new Application(); -$controller = $app->getContainer()->query('SettingsController'); +use OCA\Registration\Controller\SettingsController; + +$controller = \OC::$server->query(SettingsController::class); return $controller->displayPanel()->render(); diff --git a/appinfo/app.php b/appinfo/app.php index a29f1c5..feadb9d 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -11,9 +11,9 @@ namespace OCA\Registration\AppInfo; -$app = new Application(); -$c = $app->getContainer(); +\OC_App::registerLogIn([ + 'name' => \OC::$server->getL10N('registration')->t('Register'), + 'href' => \OC::$server->getURLGenerator()->linkToRoute('registration.register.askEmail') +]); -\OC_App::registerLogIn(array('name' => $c->query('L10N')->t('Register'), 'href' => $c->query('URLGenerator')->linkToRoute('registration.register.askEmail'))); - -\OCP\App::registerAdmin($c->getAppName(), 'admin'); +\OCP\App::registerAdmin('registration', 'admin'); \ No newline at end of file diff --git a/appinfo/application.php b/appinfo/application.php deleted file mode 100644 index 3ab1f54..0000000 --- a/appinfo/application.php +++ /dev/null @@ -1,100 +0,0 @@ - - * @copyright Pellaeon Lin 2014 - */ - -namespace OCA\Registration\AppInfo; - -use \OC\AppFramework\Utility\SimpleContainer; - -use \OCP\AppFramework\App; - -use \OCA\Registration\Controller\RegisterController; -use \OCA\Registration\Controller\SettingsController; -use \OCA\Registration\Wrapper; -use \OCA\Registration\Db\PendingRegist; - - -class Application extends App { - - public function __construct (array $urlParams=array()) { - parent::__construct('registration', $urlParams); - - $container = $this->getContainer(); - - /** - * Controllers - */ - $container->registerService('RegisterController', function(SimpleContainer $c) { - return new RegisterController( - $c->query('AppName'), - $c->query('Request'), - $c->query('Mailer'), - $c->query('L10N'), - $c->query('URLGenerator'), - $c->query('PendingRegist'), - $c->query('UserManager'), - $c->query('Config'), - $c->query('GroupManager'), - $c->query('Defaults'), - $c->query('ServerContainer')->getSecureRandom()->getMediumStrengthGenerator(), - $c->query('ServerContainer')->getUserSession() - ); - }); - - $container->registerService('SettingsController', function(SimpleContainer $c) { - return new SettingsController( - $c->query('AppName'), - $c->query('Request'), - $c->query('L10N'), - $c->query('Config'), - $c->query('GroupManager') - ); - }); - - - /** - * Core - */ - $container->registerService('UserManager', function(SimpleContainer $c) { - return $c->query('ServerContainer')->getUserManager(); - }); - - $container->registerService('GroupManager', function(SimpleContainer $c) { - return $c->query('ServerContainer')->getGroupManager(); - }); - - $container->registerService('Config', function(SimpleContainer $c) { - return $c->query('ServerContainer')->getConfig(); - }); - - $container->registerService('Mailer', function(SimpleContainer $c) { - return $c->query('ServerContainer')->getMailer(); - }); - - $container->registerService('L10N', function(SimpleContainer $c) { - return $c->query('ServerContainer')->getL10N($c->query('AppName')); - }); - - $container->registerService('URLGenerator', function(SimpleContainer $c) { - return $c->getServer()->getURLGenerator(); - }); - - $container->registerService('PendingRegist', function(SimpleContainer $c) { - return new PendingRegist($c->query('ServerContainer')->getDatabaseConnection(), - $c->query('ServerContainer')->getSecureRandom()->getMediumStrengthGenerator()); - }); - - $container->registerService('Defaults', function(SimpleContainer $c) { - return new \OCP\Defaults; - }); - } - - -} diff --git a/controller/registercontroller.php b/controller/registercontroller.php index 464e8c6..1770cfc 100644 --- a/controller/registercontroller.php +++ b/controller/registercontroller.php @@ -6,64 +6,50 @@ * later. See the COPYING file. * * @author Pellaeon Lin + * @author Julius Härtl * @copyright Pellaeon Lin 2014 */ namespace OCA\Registration\Controller; - +use OCA\Registration\Service\RegistrationException; +use OCA\Registration\Service\RegistrationService; use \OCP\IRequest; use \OCP\AppFramework\Http\TemplateResponse; use \OCP\AppFramework\Http\RedirectResponse; use \OCP\AppFramework\Controller; -use \OCP\Defaults; -use \OCP\Util; -use \OCA\Registration\Wrapper; -use \OCP\IUserManager; -use \OCP\IUserSession; -use \OCP\IGroupManager; +use OCP\IURLGenerator; use \OCP\IL10N; -use \OCP\IConfig; -use \OCP\Mail\IMailer; -use \OCP\Security\ISecureRandom; -use \OC_User; -use \OC_Util; class RegisterController extends Controller { - private $mailer; private $l10n; private $urlgenerator; - private $pendingreg; - private $usermanager; - private $config; - private $groupmanager; - /** @var \OCP\Defaults */ - private $defaults; - private $random; - private $usersession; - protected $appName; + /** @var RegistrationService */ + private $registrationService; - public function __construct($appName, IRequest $request, IMailer $mailer, IL10N $l10n, $urlgenerator, - $pendingreg, IUserManager $usermanager, IConfig $config, IGroupManager $groupmanager, Defaults $defaults, - ISecureRandom $random, IUserSession $us){ - $this->mailer = $mailer; + + public function __construct( + $appName, + IRequest $request, + IL10N $l10n, + IURLGenerator $urlgenerator, + RegistrationService $registrationService + ){ + parent::__construct($appName, $request); + $this->request = $request; $this->l10n = $l10n; $this->urlgenerator = $urlgenerator; - $this->pendingreg = $pendingreg; - $this->usermanager = $usermanager; - $this->config = $config; - $this->groupmanager = $groupmanager; - $this->defaults = $defaults; - $this->appName = $appName; - $this->random = $random; - $this->usersession = $us; - parent::__construct($appName, $request); + $this->registrationService = $registrationService; } /** * @NoCSRFRequired * @PublicPage + * + * @param $errormsg + * @param $entered + * @return TemplateResponse */ public function askEmail($errormsg, $entered) { $params = array( @@ -75,83 +61,19 @@ class RegisterController extends Controller { /** * @PublicPage + * @return TemplateResponse */ public function validateEmail() { $email = $this->request->getParam('email'); - if ( !$this->mailer->validateMailAddress($email) ) { - return new TemplateResponse('', 'error', array( - 'errors' => array(array( - 'error' => $this->l10n->t('The email address you entered is not valid'), - 'hint' => '' - )) - ), 'error'); - } - - if ( $this->pendingreg->find($email) ) { - $this->pendingreg->delete($email); - $token = $this->pendingreg->save($email); - - try { - $this->sendValidationEmail($token, $email); - } catch (\Exception $e) { - return new TemplateResponse('', 'error', array( - 'errors' => array(array( - 'error' => $this->l10n->t('A problem occurred sending email, please contact your administrator.'), - 'hint' => '' - )) - ), 'error'); - } - return new TemplateResponse('', 'error', array( - 'errors' => array(array( - 'error' => $this->l10n->t('There is already a pending registration with this email, a new verification email has been sent to the address.'), - 'hint' => '' - )) - ), 'error'); - } - - if ( $this->config->getUsersForUserValue('settings', 'email', $email) ) { - return new TemplateResponse('', 'error', array( - 'errors' => array(array( - 'error' => $this->l10n->t('A user has already taken this email, maybe you already have an account?'), - 'hint' => str_replace( - '{login}', $this->urlgenerator->getAbsoluteURL('/'), - $this->l10n->t('You can log in now.')) - )) - ), 'error'); - } - - - // allow only from specific email domain - $allowed_domains = $this->config->getAppValue($this->appName, 'allowed_domains', ''); - if ( $allowed_domains !== '' ) { - $allowed_domains = explode(';', $allowed_domains); - $allowed = false; - foreach ( $allowed_domains as $domain ) { - $maildomain=explode("@",$email)[1]; - // valid domain, everythings fine - if ($maildomain === $domain) { - $allowed=true; - break; - } - } - if ( $allowed === false ) { - return new TemplateResponse('registration', 'domains', ['domains' => - $allowed_domains - ], 'guest'); - } - } - - $token = $this->pendingreg->save($email); try { - $this->sendValidationEmail($token, $email); - } catch (\Exception $e) { - return new TemplateResponse('', 'error', array( - 'errors' => array(array( - 'error' => $this->l10n->t('A problem occurred sending email, please contact your administrator.'), - 'hint' => '' - )) - ), 'error'); + $validation = $this->registrationService->validateEmail($email); + if($validation instanceof TemplateResponse) { + return $validation; + } + } catch (RegistrationException $e) { + return $this->renderError($e->getMessage(), $e->getHint()); } + return new TemplateResponse('registration', 'message', array('msg' => $this->l10n->t('Verification email successfully sent.') ), 'guest'); @@ -160,207 +82,60 @@ class RegisterController extends Controller { /** * @NoCSRFRequired * @PublicPage + * + * @param $token + * @return TemplateResponse */ public function verifyToken($token) { - $email = $this->pendingreg->findEmailByToken($token); - if ( $email === false ) { - return new TemplateResponse('', 'error', array( - 'errors' => array(array( - 'error' => $this->l10n->t('Invalid verification URL. No registration request with this verification URL is found.'), - 'hint' => '' - )) - ), 'error'); - } elseif ( $email ) { - return new TemplateResponse('registration', 'form', array('email' => $email, 'token' => $token), 'guest'); + try { + $registration = $this->registrationService->verifyToken($token); + return new TemplateResponse('registration', 'form', ['email' => $registration->getEmail(), 'token' => $registration->getToken()], 'guest'); + } catch (RegistrationException $exception) { + return $this->renderError($exception->getMessage(), $exception->getHint()); } + } /** * @PublicPage * @UseSession + * + * @param $token + * @return RedirectResponse|TemplateResponse */ public function createAccount($token) { - $email = $this->pendingreg->findEmailByToken($token); - if ( $email === false ) { - return new TemplateResponse('', 'error', array( - 'errors' => array(array( - 'error' => $this->l10n->t('Invalid verification URL. No registration request with this verification URL is found.'), - 'hint' => '' - )) - ), 'error'); - } elseif ( $email ) { - $username = $this->request->getParam('username'); - $password = $this->request->getParam('password'); - try { - $user = $this->usermanager->createUser($username, $password); - } catch (\Exception $e) { - return new TemplateResponse('registration', 'form', - array('email' => $email, - 'entered_data' => array('username' => $username), - 'errormsgs' => array($e->getMessage()), - 'token' => $token), 'guest'); - } - if ( $user === false ) { - return new TemplateResponse('', 'error', array( - 'errors' => array(array( - 'error' => $this->l10n->t('Unable to create user, there are problems with the user backend.'), - 'hint' => '' - )) - ), 'error'); - } else { - $userId = $user->getUID(); - // Set user email - try { - $this->config->setUserValue($userId, 'settings', 'email', $email); - } catch (\Exception $e) { - return new TemplateResponse('', 'error', array( - 'errors' => array(array( - 'error' => $this->l10n->t('Unable to set user email: '.$e->getMessage()), - 'hint' => '' - )) - ), 'error'); - } + $username = $this->request->getParam('username'); + $password = $this->request->getParam('password'); + $registration = $this->registrationService->getRegistrationForToken($token); - // Add user to group - $registered_user_group = $this->config->getAppValue($this->appName, 'registered_user_group', 'none'); - if ( $registered_user_group !== 'none' ) { - try { - $group = $this->groupmanager->get($registered_user_group); - $group->addUser($user); - } catch (\Exception $e) { - return new TemplateResponse('', 'error', array( - 'errors' => array(array( - 'error' => $e->message, - )) - ), 'error'); - } - } - - // Delete pending reg request - $res = $this->pendingreg->delete($email); - if ( $res === false ) { - return new TemplateResponse('', 'error', array( - 'errors' => array(array( - 'error' => $this->l10n->t('Failed to delete pending registration request'), - 'hint' => '' - )) - ), 'error'); - } - - // Notify admin - $admin_users = $this->groupmanager->get('admin')->getUsers(); - $to_arr = array(); - foreach ( $admin_users as $au ) { - $au_email = $this->config->getUserValue($au->getUID(), 'settings', 'email'); - if ( $au_email !== '' ) { - $to_arr[$au_email] = $au->getDisplayName(); - } - } - try { - $this->sendNewUserNotifEmail($to_arr, $userId); - } catch (\Exception $e) { - \OCP\Util::writeLog('registration', 'Sending admin notification email failed: '. $e->getMessage, \OCP\Util::ERROR); - } - - // Try to log user in - if ( method_exists($this->usersession, 'createSessionToken') ) { - $this->usersession->login($username, $password); - $this->usersession->createSessionToken($this->request, $userId, $username, $password); - return new RedirectResponse($this->urlgenerator->linkToRoute('files.view.index')); - } 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', array('msg' => - str_replace('{link}', - $this->urlgenerator->getAbsoluteURL('/'), - $this->l10n->t('Your account has been successfully created, you can log in now.') - )), 'guest'); - } - } + try { + $this->registrationService->createAccount($token, $username, $password); + } catch (RegistrationException $exception) { + return $this->renderError($exception->getMessage(), $exception->getHint()); + } catch (\InvalidArgumentException $exception) { + // Render form with previously sent values + return new TemplateResponse('registration', 'form', + [ + 'email' => $registration->getEmail(), + 'entered_data' => array('user' => $username), + 'errormsgs' => array($exception->getMessage()), + 'token' => $token + ], 'guest'); } + + return new TemplateResponse('registration', 'message', + ['msg' => $this->l10n->t('Your account has been successfully created, you can log in now.', [$this->urlgenerator->getAbsoluteURL('/')])], + 'guest' + ); } - /** - * Sends validation email - * @param string $token - * @param string $to - * @return null - * @throws \Exception - */ - private function sendValidationEmail($token, $to) { - $link = $this->urlgenerator->linkToRoute('registration.register.verifyToken', array('token' => $token)); - $link = $this->urlgenerator->getAbsoluteURL($link); - $template_var = [ - 'link' => $link, - 'sitename' => $this->defaults->getName() - ]; - $html_template = new TemplateResponse('registration', 'email.validate_html', $template_var, 'blank'); - $html_part = $html_template->render(); - $plaintext_template = new TemplateResponse('registration', 'email.validate_plaintext', $template_var, 'blank'); - $plaintext_part = $plaintext_template->render(); - $subject = $this->l10n->t('Verify your %s registration request', [$this->defaults->getName()]); - - $from = Util::getDefaultEmailAddress('register'); - $message = $this->mailer->createMessage(); - $message->setFrom([$from => $this->defaults->getName()]); - $message->setTo([$to]); - $message->setSubject($subject); - $message->setPlainBody($plaintext_part); - $message->setHtmlBody($html_part); - $failed_recipients = $this->mailer->send($message); - if ( !empty($failed_recipients) ) - throw new \Exception('Failed recipients: '.print_r($failed_recipients, true)); + private function renderError($error, $hint="") { + return new TemplateResponse('', 'error', array( + 'errors' => array(array( + 'error' => $error, + 'hint' => $hint + )) + ), 'error'); } - /** - * Sends new user notification email to admin - * @param array $to - * @param string $username the new user - * @return null - * @throws \Exception - */ - private function sendNewUserNotifEmail(array $to, $username) { - $template_var = [ - 'user' => $username, - 'sitename' => $this->defaults->getName() - ]; - $html_template = new TemplateResponse('registration', 'email.newuser_html', $template_var, 'blank'); - $html_part = $html_template->render(); - $plaintext_template = new TemplateResponse('registration', 'email.newuser_plaintext', $template_var, 'blank'); - $plaintext_part = $plaintext_template->render(); - $subject = $this->l10n->t('A new user "%s" has created an account on %s', [$username, $this->defaults->getName()]); - - $from = Util::getDefaultEmailAddress('register'); - $message = $this->mailer->createMessage(); - $message->setFrom([$from => $this->defaults->getName()]); - $message->setTo($to); - $message->setSubject($subject); - $message->setPlainBody($plaintext_part); - $message->setHtmlBody($html_part); - $failed_recipients = $this->mailer->send($message); - if ( !empty($failed_recipients) ) - throw new \Exception('Failed recipients: '.print_r($failed_recipients, true)); - } - - /** - * Replicates OC::cleanupLoginTokens() since it's protected - * @param string $userId - * @return null - */ - private function cleanupLoginTokens($userId) { - $cutoff = time() - $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15); - $tokens = $this->config->getUserKeys($userId, 'login_token'); - foreach ($tokens as $token) { - $time = $this->config->getUserValue($userId, 'login_token', $token); - if ($time < $cutoff) { - $this->config->deleteUserValue($userId, 'login_token', $token); - } - } - } } diff --git a/controller/settingscontroller.php b/controller/settingscontroller.php index c8b62b1..f01eadc 100644 --- a/controller/settingscontroller.php +++ b/controller/settingscontroller.php @@ -6,6 +6,7 @@ * later. See the COPYING file. * * @author Pellaeon Lin + * @author Julius Härtl * @copyright Pellaeon Lin 2015 */ @@ -19,21 +20,24 @@ use \OCP\AppFramework\Controller; use \OCP\IGroupManager; use \OCP\IL10N; use \OCP\IConfig; -use \OCP\IUser; class SettingsController extends Controller { + /** @var IL10N */ private $l10n; + /** @var IConfig */ private $config; + /** @var IGroupManager */ private $groupmanager; + /** @var string */ protected $appName; public function __construct($appName, IRequest $request, IL10N $l10n, IConfig $config, IGroupManager $groupmanager){ + parent::__construct($appName, $request); $this->l10n = $l10n; $this->config = $config; $this->groupmanager = $groupmanager; $this->appName = $appName; - parent::__construct($appName, $request); } @@ -89,6 +93,7 @@ class SettingsController extends Controller { */ public function displayPanel() { $groups = $this->groupmanager->search(''); + $group_id_list = []; foreach ( $groups as $group ) { $group_id_list[] = $group->getGid(); } diff --git a/db/pendingregist.php b/db/pendingregist.php deleted file mode 100644 index 1cdb7ee..0000000 --- a/db/pendingregist.php +++ /dev/null @@ -1,49 +0,0 @@ -db = $db; - $this->random = $random; - } - - public function save($email) { - $query = $this->db->prepare( 'INSERT INTO `*PREFIX*registration`' - .' ( `email`, `token`, `requested` ) VALUES( ?, ?, NOW() )' ); - - $token = $this->random->generate(6, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS); - - $query->execute(array( $email, $token )); - return $token; - } - public function find($email) { - $query = $this->db->prepare('SELECT `email` FROM `*PREFIX*registration` WHERE `email` = ? '); - $query->execute(array($email)); - return $query->fetchAll(); - } - - public function delete($email) { - $query = $this->db->prepare('DELETE FROM `*PREFIX*registration` WHERE `email` = ? '); - return $query->execute(array($email)); - } - - /** - * @return string|false - */ - public function findEmailByToken($token) { - $query = $this->db->prepare('SELECT `email` FROM `*PREFIX*registration` WHERE `token` = ? '); - $query->execute(array($token)); - return $query->fetch()['email']; - } - -} diff --git a/db/registration.php b/db/registration.php new file mode 100644 index 0000000..6798d5f --- /dev/null +++ b/db/registration.php @@ -0,0 +1,39 @@ + + * + * @author Julius Härtl + * + * @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\Db; + +use OCP\AppFramework\Db\Entity; + +class Registration extends Entity { + + public $id; + protected $email; + protected $token; + protected $requested; + protected $confirmed; + + public function __construct() { + $this->addType('confirmed', 'boolean'); + } +} \ No newline at end of file diff --git a/db/registrationmapper.php b/db/registrationmapper.php new file mode 100644 index 0000000..320cf65 --- /dev/null +++ b/db/registrationmapper.php @@ -0,0 +1,69 @@ + + * + * @author Julius Härtl + * + * @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\Db; + +use OCP\AppFramework\Db\Mapper; +use OCP\IDBConnection; +use OCP\Security\ISecureRandom; + +class RegistrationMapper extends Mapper { + + /** @var \OCP\Security\ISecureRandom */ + protected $random; + + public function __construct(IDBConnection $db, ISecureRandom $random) { + parent::__construct($db, 'registration', Registration::class); + $this->random = $random; + } + + public function findByToken($token) { + return $this->findEntity('SELECT * FROM `*PREFIX*registration` WHERE `token` = ? ', [$token]); + } + + public function findEmailByToken($token) { + $entity = $this->findByToken($token); + return $entity->getEmail(); + } + + public function find($email) { + $sql = 'SELECT `email` FROM `*PREFIX*registration` WHERE `email` = ? '; + return $this->findEntity($sql, [$email]); + } + + public function deleteByEmail($email) { + $entity = $this->findEntity('SELECT * FROM `*PREFIX*registration` WHERE `email` = ?', [$email]); + return $this->delete($entity); + } + + public function save($email) { + $token = $this->random->generate(6, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS); + $registration = new Registration(); + $registration->setEmail($email); + $registration->setToken($token); + $registration->setRequested(date('Y-m-d H:i:s')); + return $this->insert($registration); + + } + +} \ No newline at end of file diff --git a/service/registrationexception.php b/service/registrationexception.php new file mode 100644 index 0000000..48d6e2c --- /dev/null +++ b/service/registrationexception.php @@ -0,0 +1,42 @@ + + * + * @author Julius Härtl + * + * @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; + +class RegistrationException extends \Exception { + + protected $hint; + + public function __construct($message, $hint = "") { + parent::__construct($message); + $this->setHint($hint); + } + + public function setHint($hint) { + $this->hint = $hint; + } + + public function getHint() { + return $this->hint; + } +} \ No newline at end of file diff --git a/service/registrationservice.php b/service/registrationservice.php new file mode 100644 index 0000000..1c90ae2 --- /dev/null +++ b/service/registrationservice.php @@ -0,0 +1,342 @@ + + * + * @author Julius Härtl + * + * @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 OCA\Registration\Db\RegistrationMapper; +use OCP\AppFramework\Db\DoesNotExistException; +use \OCP\AppFramework\Http\TemplateResponse; +use \OCP\AppFramework\Http\RedirectResponse; +use \OCP\Defaults; +use OCP\ILogger; +use OCP\IRequest; +use OCP\IURLGenerator; +use \OCP\Util; +use \OCP\IUserManager; +use \OCP\IUserSession; +use \OCP\IGroupManager; +use \OCP\IL10N; +use \OCP\IConfig; +use \OCP\Mail\IMailer; +use \OCP\Security\ISecureRandom; +use \OC_User; +use \OC_Util; + +class RegistrationService { + + /** @var IMailer */ + private $mailer; + /** @var IL10N */ + private $l10n; + /** @var IURLGenerator */ + private $urlGenerator; + /** @var RegistrationMapper */ + private $registrationMapper; + /** @var IUserManager */ + private $userManager; + /** @var IConfig */ + private $config; + /** @var IGroupManager */ + private $groupManager; + /** @var \OCP\Defaults */ + private $defaults; + /** @var ISecureRandom */ + private $random; + /** @var IUserSession */ + private $usersession; + /** @var string */ + private $appName; + /** @var IRequest */ + private $request; + /** @var ILogger */ + private $logger; + + public function __construct($appName, IMailer $mailer, IL10N $l10n, IURLGenerator $urlGenerator, + RegistrationMapper $registrationMapper, IUserManager $userManager, IConfig $config, IGroupManager $groupManager, Defaults $defaults, + ISecureRandom $random, IUserSession $us, IRequest $request, ILogger $logger){ + $this->mailer = $mailer; + $this->l10n = $l10n; + $this->urlGenerator = $urlGenerator; + $this->registrationMapper = $registrationMapper; + $this->userManager = $userManager; + $this->config = $config; + $this->groupManager = $groupManager; + $this->defaults = $defaults; + $this->random = $random; + $this->usersession = $us; + $this->appName = $appName; + $this->request = $request; + $this->logger = $logger; + } + + + public function validateEmail($email) { + + if ( !$this->mailer->validateMailAddress($email) ) { + throw new RegistrationException($this->l10n->t('The email address you entered is not valid')); + } + + try { + $registration = $this->registrationMapper->find($email); + } catch (\Exception $e) { + $registration = null; + } + // check if email already tried to register + if ( $registration !== null) { + $this->registrationMapper->delete($registration); + $this->generateToken($email); + throw new RegistrationException($this->l10n->t('There is already a pending registration with this email, a new verification email has been sent to the address.')); + } + + if ( $this->config->getUsersForUserValue('settings', 'email', $email) ) { + throw new RegistrationException( + $this->l10n->t('A user has already taken this email, maybe you already have an account?'), + $this->l10n->t('You can log in now.', [$this->urlGenerator->getAbsoluteURL('/')]) + ); + } + + // allow only from specific email domain} + if (!$this->checkAllowedDomains($email)) { + $allowed_domains = $this->config->getAppValue($this->appName, 'allowed_domains', ''); + $allowed_domains = explode(';', $allowed_domains); + return new TemplateResponse('registration', 'domains', [ + 'domains' => $allowed_domains + ], 'guest'); + } + + $this->generateToken($email); + + return null; + + } + + public function generateToken($email) { + try { + $registration = $this->registrationMapper->find($email); + $this->registrationMapper->delete($registration); + } catch (\Exception $exception) {} + $registration = $this->registrationMapper->save($email); + + try { + $this->sendValidationEmail($registration->getToken(), $email); + } catch (\Exception $e) { + throw new RegistrationException($this->l10n->t('A problem occurred sending email, please contact your administrator.')); + } + } + + /** + * check if email domain is allowed + * + * @param $email + * @return bool + */ + public function checkAllowedDomains($email) { + $allowed_domains = $this->config->getAppValue($this->appName, 'allowed_domains', ''); + if ( $allowed_domains !== '' ) { + $allowed_domains = explode(';', $allowed_domains); + $allowed = false; + foreach ($allowed_domains as $domain) { + $maildomain = explode("@", $email)[1]; + // valid domain, everythings fine + if ($maildomain === $domain) { + $allowed = true; + break; + } + } + return $allowed; + } + return true; + } + + /** + * @param $token + * @return string + * @throws RegistrationException + */ + public function verifyToken($token) { + try { + return $this->registrationMapper->findByToken($token); + } catch (DoesNotExistException $exception) { + throw new RegistrationException($this->l10n->t('Invalid verification URL. No registration request with this verification URL is found.')); + } + } + + + public function createAccount($token, $username, $password) { + $email = $this->registrationMapper->findEmailByToken($token); + if ( $email === false ) { + throw new RegistrationException($this->l10n->t('Invalid verification URL. No registration request with this verification URL is found.')); + } + + $user = $this->userManager->createUser($username, $password); + if ($user === false) { + throw new RegistrationException($this->l10n->t('Unable to create user, there are problems with the user backend.')); + } + $userId = $user->getUID(); + // Set user email + try { + $this->config->setUserValue($userId, 'settings', 'email', $email); + } catch (\Exception $e) { + throw new RegistrationException($this->l10n->t('Unable to set user email: ' . $e->getMessage())); + } + + // Add user to group + $registered_user_group = $this->config->getAppValue($this->appName, 'registered_user_group', 'none'); + if ( $registered_user_group !== 'none' ) { + try { + $group = $this->groupManager->get($registered_user_group); + $group->addUser($user); + } catch (\Exception $e) { + throw new RegistrationException($e->getMessage()); + } + } + + // Delete pending reg request + $res = $this->registrationMapper->deleteByEmail($email); + if ($res === false) { + throw new RegistrationException($this->l10n->t('Failed to delete pending registration request')); + } + + $this->notifyAdmins($userId); + + $this->loginUser($userId, $username, $password); + + } + + public function loginUser($userId, $username, $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->linkToRoute('files.view.index')); + } 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' + ); + + } + + public function notifyAdmins($userId) { + // Notify admin + $admin_users = $this->groupManager->get('admin')->getUsers(); + $to_arr = array(); + foreach ( $admin_users as $au ) { + $au_email = $this->config->getUserValue($au->getUID(), 'settings', 'email'); + if ( $au_email !== '' ) { + $to_arr[$au_email] = $au->getDisplayName(); + } + } + try { + $this->sendNewUserNotifEmail($to_arr, $userId); + } catch (\Exception $e) { + $this->logger->error('Sending admin notification email failed: '. $e->getMessage()); + } + } + + /** + * Sends validation email + * @param string $token + * @param string $to + * @throws \Exception + */ + private function sendValidationEmail($token, $to) { + $link = $this->urlGenerator->linkToRoute('registration.register.verifyToken', array('token' => $token)); + $link = $this->urlGenerator->getAbsoluteURL($link); + $template_var = [ + 'link' => $link, + 'sitename' => $this->defaults->getName() + ]; + $html_template = new TemplateResponse('registration', 'email.validate_html', $template_var, 'blank'); + $html_part = $html_template->render(); + $plaintext_template = new TemplateResponse('registration', 'email.validate_plaintext', $template_var, 'blank'); + $plaintext_part = $plaintext_template->render(); + $subject = $this->l10n->t('Verify your %s registration request', [$this->defaults->getName()]); + + $from = Util::getDefaultEmailAddress('register'); + $message = $this->mailer->createMessage(); + $message->setFrom([$from => $this->defaults->getName()]); + $message->setTo([$to]); + $message->setSubject($subject); + $message->setPlainBody($plaintext_part); + $message->setHtmlBody($html_part); + $failed_recipients = $this->mailer->send($message); + if ( !empty($failed_recipients) ) + throw new RegistrationException('Failed recipients: '.print_r($failed_recipients, true)); + } + + /** + * Sends new user notification email to admin + * @param array $to + * @param string $username the new user + * @throws \Exception + */ + private function sendNewUserNotifEmail(array $to, $username) { + $template_var = [ + 'user' => $username, + 'sitename' => $this->defaults->getName() + ]; + $html_template = new TemplateResponse('registration', 'email.newuser_html', $template_var, 'blank'); + $html_part = $html_template->render(); + $plaintext_template = new TemplateResponse('registration', 'email.newuser_plaintext', $template_var, 'blank'); + $plaintext_part = $plaintext_template->render(); + $subject = $this->l10n->t('A new user "%s" has created an account on %s', [$username, $this->defaults->getName()]); + + $from = Util::getDefaultEmailAddress('register'); + $message = $this->mailer->createMessage(); + $message->setFrom([$from => $this->defaults->getName()]); + $message->setTo($to); + $message->setSubject($subject); + $message->setPlainBody($plaintext_part); + $message->setHtmlBody($html_part); + $failed_recipients = $this->mailer->send($message); + if ( !empty($failed_recipients) ) + throw new RegistrationException('Failed recipients: '.print_r($failed_recipients, true)); + } + + /** + * Replicates OC::cleanupLoginTokens() since it's protected + * @param string $userId + */ + public function cleanupLoginTokens($userId) { + $cutoff = time() - $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15); + $tokens = $this->config->getUserKeys($userId, 'login_token'); + foreach ($tokens as $token) { + $time = $this->config->getUserValue($userId, 'login_token', $token); + if ($time < $cutoff) { + $this->config->deleteUserValue($userId, 'login_token', $token); + } + } + } + + public function getRegistrationForToken($token) { + return $this->registrationMapper->findByToken($token); + } +} From 8f192c49fe7a667e39f604030552262c7b7ecd99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Tue, 27 Jun 2017 11:37:47 +0200 Subject: [PATCH 2/7] Add registration API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- appinfo/app.php | 5 +- appinfo/database.xml | 22 ++ appinfo/info.xml | 2 +- appinfo/routes.php | 22 +- capabilities.php | 50 +++++ controller/apicontroller.php | 174 +++++++++++++++ controller/registercontroller.php | 40 +++- db/registration.php | 12 +- db/registrationmapper.php | 42 ++-- service/mailservice.php | 143 +++++++++++++ service/registrationservice.php | 344 ++++++++++++++++++------------ 11 files changed, 687 insertions(+), 169 deletions(-) create mode 100644 capabilities.php create mode 100644 controller/apicontroller.php create mode 100644 service/mailservice.php diff --git a/appinfo/app.php b/appinfo/app.php index feadb9d..33a0147 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -16,4 +16,7 @@ namespace OCA\Registration\AppInfo; 'href' => \OC::$server->getURLGenerator()->linkToRoute('registration.register.askEmail') ]); -\OCP\App::registerAdmin('registration', 'admin'); \ No newline at end of file +\OCP\App::registerAdmin('registration', 'admin'); + +$app = new \OCP\AppFramework\App('registration'); +$app->getContainer()->registerCapability(\OCA\Registration\Capabilities::class); diff --git a/appinfo/database.xml b/appinfo/database.xml index 3a8039c..ff4d751 100644 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -20,11 +20,33 @@ text true + + username + text + true + + + password + text + + + displayname + text + + + email_confirmed + boolean + false + token text true + + client_secret + text + requested timestamp diff --git a/appinfo/info.xml b/appinfo/info.xml index ead2573..9bbf8fc 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -6,7 +6,7 @@ User registration agpl Pellaeon Lin - 0.2.3 + 0.2.3-3 https://github.com/pellaeon/registration https://github.com/pellaeon/registration/issues https://github.com/pellaeon/registration diff --git a/appinfo/routes.php b/appinfo/routes.php index 0ad091e..7db7778 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -6,13 +6,21 @@ * later. See the COPYING file. * * @author Pellaeon Lin + * @author Julius Härtl * @copyright Pellaeon Lin 2014 */ -return ['routes' => [ - array('name' => 'settings#admin', 'url' => '/settings', 'verb' => 'POST'), - array('name' => 'register#askEmail', 'url' => '/', 'verb' => 'GET'), - array('name' => 'register#validateEmail', 'url' => '/', 'verb' => 'POST'), - array('name' => 'register#verifyToken', 'url' => '/verify/{token}', 'verb' => 'GET'), - array('name' => 'register#createAccount', 'url' => '/verify/{token}', 'verb' => 'POST') -]]; +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'] + ], + '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'] + ] +]; diff --git a/capabilities.php b/capabilities.php new file mode 100644 index 0000000..035ad97 --- /dev/null +++ b/capabilities.php @@ -0,0 +1,50 @@ + + * + * @author Julius Härtl + * + * @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; + +use OCP\Capabilities\ICapability; +use OCP\IURLGenerator; + +class Capabilities implements ICapability { + + /** @var IURLGenerator */ + private $urlGenerator; + + public function __construct(IURLGenerator $urlGenerator) { + $this->urlGenerator = $urlGenerator; + } + + public function getCapabilities() { + return [ + 'registration' => + [ + 'enabled' => true, + 'apiRoot' => $this->urlGenerator->linkTo( + '', 'ocs/v1.php/apps/registration/api/v1/'), + 'apiLevel' => 'v1' + ] + ]; + } + +} \ No newline at end of file diff --git a/controller/apicontroller.php b/controller/apicontroller.php new file mode 100644 index 0000000..32bc2d0 --- /dev/null +++ b/controller/apicontroller.php @@ -0,0 +1,174 @@ + + * + * @author Julius Härtl + * + * @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\Controller; + +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; +use OCP\AppFramework\OCS\OCSBadRequestException; +use OCP\AppFramework\OCS\OCSNotFoundException; +use OCP\AppFramework\OCSController; +use OCP\AppFramework\OCS\OCSException; +use OCP\AppFramework\Http\DataResponse; +use OCP\Defaults; +use OCP\IL10N; +use OCP\IRequest; + +class ApiController extends OCSController { + + /** @var RegistrationService */ + private $registrationService; + /** @var MailService */ + private $mailService; + /** @var IL10N */ + private $l10n; + /** @var Defaults */ + private $defaults; + + public function __construct($appName, + IRequest $request, + $corsMethods = 'PUT, POST, GET, DELETE, PATCH', + $corsAllowedHeaders = 'Authorization, Content-Type, Accept', + $corsMaxAge = 1728000, + RegistrationService $registrationService, + MailService $mailService, + IL10N $l10n, + Defaults $defaults) { + parent::__construct($appName, $request, $corsMethods, $corsAllowedHeaders, $corsMaxAge); + $this->registrationService = $registrationService; + $this->mailService = $mailService; + $this->l10n = $l10n; + $this->defaults = $defaults; + } + + /** + * @PublicPage + * @AnonRateThrottle(limit=5, period=1) + * + * @param $username + * @param $displayname + * @param $email + * @throws OCSException + * @return DataResponse + */ + public function validate($username, $displayname, $email) { + try { + $this->registrationService->validateEmail($email); + $this->registrationService->validateDisplayname($displayname); + $this->registrationService->validateUsername($username); + } catch (RegistrationException $e) { + throw new OCSBadRequestException($e->getMessage()); + } + $data = [ + 'username' => $username, + 'displayname' => $displayname, + 'email' => $email + ]; + return new DataResponse($data, Http::STATUS_OK); + } + + /** + * @PublicPage + * + * @param $registrationToken + * @param $clientSecret + * @throws OCSException + * @return DataResponse + */ + public function status($registrationToken, $clientSecret=null) { + $data = []; + try { + /** @var Registration $registration */ + $registration = $this->registrationService->getRegistrationForToken($registrationToken); + if(!$registration->getEmailConfirmed()) { + $data = [ + 'status' => Registration::STATUS_PENDING, + 'message' => $this->l10n->t('Your registration is pending. Please confirm your email address.') + ]; + } else { + // create account if email confirmed and not already created + $user = $this->registrationService->getUserAccount($registration); + if($user === null) { + $user = $this->registrationService->createAccount($registration); + } + $this->registrationService->loginUser($user->getUID(), $registration->getUsername(), $registration->getPassword(), true); + $appPassword = $this->registrationService->generateAppPassword($user->getUID()); + if ($clientSecret === $registration->getClientSecret()) { + $data = [ + 'status' => Registration::STATUS_FINISHED, + 'appPassword' => $appPassword, + 'cloudUrl' => $this->defaults->getBaseUrl() + ]; + $this->registrationService->deleteRegistration($registration); + } + } + return new DataResponse($data, Http::STATUS_OK); + } catch (DoesNotExistException $e) { + throw new OCSNotFoundException('No pending registration.'); + } + } + + /** + * @PublicPage + * + * @param $username + * @param $displayname + * @param $email + * @param $password + * @throws OCSException + * @return DataResponse + */ + public function register($username, $displayname, $email, $password) { + $data = []; + try { + $secret = null; + $registration = $this->registrationService->validateEmail($email); + if($registration === null) { + $this->registrationService->validateDisplayname($displayname); + $this->registrationService->validateUsername($username); + $registration = $this->registrationService->createRegistration($email, $username, $password, $displayname); + $this->mailService->sendTokenByMail($registration); + $secret = $registration->getClientSecret(); + } else { + $this->registrationService->generateNewToken($registration); + $this->mailService->sendTokenByMail($registration); + throw new RegistrationException($this->l10n->t('There is already a pending registration with this email, a new verification email has been sent to the address.')); + } + + $data['message'] = $this->l10n->t('Your registration is pending. Please confirm your email address.'); + $data['token'] = $registration->getToken(); + $data['status'] = Registration::STATUS_PENDING; + if($secret !== null) { + $data['secret'] = $secret; + } + return new DataResponse($data, Http::STATUS_OK); + } catch (RegistrationException $exception) { + throw new OCSException($exception->getMessage()); + } + } + +} \ No newline at end of file diff --git a/controller/registercontroller.php b/controller/registercontroller.php index 1770cfc..e69e1c1 100644 --- a/controller/registercontroller.php +++ b/controller/registercontroller.php @@ -12,6 +12,8 @@ namespace OCA\Registration\Controller; +use OCA\Registration\Db\Registration; +use OCA\Registration\Service\MailService; use OCA\Registration\Service\RegistrationException; use OCA\Registration\Service\RegistrationService; use \OCP\IRequest; @@ -23,10 +25,14 @@ use \OCP\IL10N; class RegisterController extends Controller { + /** @var IL10N */ private $l10n; + /** @var IURLGenerator */ private $urlgenerator; /** @var RegistrationService */ private $registrationService; + /** @var MailService */ + private $mailService; public function __construct( @@ -34,13 +40,14 @@ class RegisterController extends Controller { IRequest $request, IL10N $l10n, IURLGenerator $urlgenerator, - RegistrationService $registrationService + RegistrationService $registrationService, + MailService $mailService ){ parent::__construct($appName, $request); - $this->request = $request; $this->l10n = $l10n; $this->urlgenerator = $urlgenerator; $this->registrationService = $registrationService; + $this->mailService = $mailService; } /** @@ -61,19 +68,26 @@ class RegisterController extends Controller { /** * @PublicPage + * * @return TemplateResponse */ public function validateEmail() { $email = $this->request->getParam('email'); + + if (!$this->registrationService->checkAllowedDomains($email)) { + return new TemplateResponse('registration', 'domains', [ + 'domains' => $this->registrationService->getAllowedDomains() + ], 'guest'); + } try { - $validation = $this->registrationService->validateEmail($email); - if($validation instanceof TemplateResponse) { - return $validation; - } + $this->registrationService->validateEmail($email); + $registration = $this->registrationService->createRegistration($email); + $this->mailService->sendTokenByMail($registration); } catch (RegistrationException $e) { return $this->renderError($e->getMessage(), $e->getHint()); } + return new TemplateResponse('registration', 'message', array('msg' => $this->l10n->t('Verification email successfully sent.') ), 'guest'); @@ -88,7 +102,19 @@ class RegisterController extends Controller { */ public function verifyToken($token) { 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', ['email' => $registration->getEmail(), 'token' => $registration->getToken()], 'guest'); } catch (RegistrationException $exception) { return $this->renderError($exception->getMessage(), $exception->getHint()); @@ -109,7 +135,7 @@ class RegisterController extends Controller { $registration = $this->registrationService->getRegistrationForToken($token); try { - $this->registrationService->createAccount($token, $username, $password); + $this->registrationService->createAccount($registration, $username, $password); } catch (RegistrationException $exception) { return $this->renderError($exception->getMessage(), $exception->getHint()); } catch (\InvalidArgumentException $exception) { diff --git a/db/registration.php b/db/registration.php index 6798d5f..475a600 100644 --- a/db/registration.php +++ b/db/registration.php @@ -27,13 +27,21 @@ use OCP\AppFramework\Db\Entity; class Registration extends Entity { + const STATUS_FINISHED = 0; + const STATUS_PENDING = 1; + public $id; protected $email; + protected $username; + protected $displayname; + protected $password; protected $token; protected $requested; - protected $confirmed; + protected $emailConfirmed; + protected $clientSecret; public function __construct() { - $this->addType('confirmed', 'boolean'); + $this->addType('emailConfirmed', 'boolean'); } + } \ No newline at end of file diff --git a/db/registrationmapper.php b/db/registrationmapper.php index 320cf65..21e670a 100644 --- a/db/registrationmapper.php +++ b/db/registrationmapper.php @@ -23,6 +23,7 @@ namespace OCA\Registration\Db; +use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\Mapper; use OCP\IDBConnection; use OCP\Security\ISecureRandom; @@ -37,33 +38,46 @@ class RegistrationMapper extends Mapper { $this->random = $random; } + /** + * @param $token + * @return Registration|Entity + */ public function findByToken($token) { return $this->findEntity('SELECT * FROM `*PREFIX*registration` WHERE `token` = ? ', [$token]); } - public function findEmailByToken($token) { - $entity = $this->findByToken($token); - return $entity->getEmail(); - } - + /** + * @param $email + * @return Registration|Entity + */ public function find($email) { - $sql = 'SELECT `email` FROM `*PREFIX*registration` WHERE `email` = ? '; + $sql = 'SELECT * FROM `*PREFIX*registration` WHERE `email` = ? '; return $this->findEntity($sql, [$email]); } - public function deleteByEmail($email) { - $entity = $this->findEntity('SELECT * FROM `*PREFIX*registration` WHERE `email` = ?', [$email]); - return $this->delete($entity); + /** + * @param Entity $entity + * @return Entity + */ + public function insert(Entity $entity) { + $entity->setRequested(date('Y-m-d H:i:s')); + return parent::insert($entity); } - public function save($email) { + /** + * @param Registration $registration + */ + public function generateNewToken(Registration &$registration) { $token = $this->random->generate(6, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS); - $registration = new Registration(); - $registration->setEmail($email); $registration->setToken($token); - $registration->setRequested(date('Y-m-d H:i:s')); - return $this->insert($registration); + } + /** + * @param Registration $registration + */ + public function generateClientSecret(Registration &$registration) { + $token = $this->random->generate(32, ISecureRandom::CHAR_HUMAN_READABLE); + $registration->setClientSecret($token); } } \ No newline at end of file diff --git a/service/mailservice.php b/service/mailservice.php new file mode 100644 index 0000000..0b23008 --- /dev/null +++ b/service/mailservice.php @@ -0,0 +1,143 @@ + + * + * @author Julius Härtl + * + * @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 OCA\Registration\Db\Registration; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\Defaults; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IL10N; +use OCP\ILogger; +use OCP\IURLGenerator; +use OCP\Mail\IMailer; +use OCP\Util; + +class MailService { + + /** @var IURLGenerator */ + private $urlGenerator; + /** @var IMailer */ + private $mailer; + /** @var Defaults */ + private $defaults; + /** @var IL10N */ + private $l10n; + /** @var IConfig */ + private $config; + /** @var IGroupManager */ + private $groupManager; + /** @var ILogger */ + private $logger; + + public function __construct(IURLGenerator $urlGenerator, IMailer $mailer, Defaults $defaults, IL10N $l10n, IConfig $config, IGroupManager $groupManager, ILogger $logger) { + $this->urlGenerator = $urlGenerator; + $this->mailer = $mailer; + $this->defaults = $defaults; + $this->l10n = $l10n; + $this->config = $config; + $this->groupManager = $groupManager; + $this->logger = $logger; + } + + public function validateEmail($email) { + if ( !$this->mailer->validateMailAddress($email) ) { + throw new RegistrationException($this->l10n->t('The email address you entered is not valid')); + } + } + + public function sendTokenByMail(Registration $registration) { + return true; + $link = $this->urlGenerator->linkToRoute('registration.register.verifyToken', array('token' => $registration->getToken())); + $link = $this->urlGenerator->getAbsoluteURL($link); + $template_var = [ + 'link' => $link, + 'sitename' => $this->defaults->getName() + ]; + $html_template = new TemplateResponse('registration', 'email.validate_html', $template_var, 'blank'); + $html_part = $html_template->render(); + $plaintext_template = new TemplateResponse('registration', 'email.validate_plaintext', $template_var, 'blank'); + $plaintext_part = $plaintext_template->render(); + $subject = $this->l10n->t('Verify your %s registration request', [$this->defaults->getName()]); + + $from = Util::getDefaultEmailAddress('register'); + $message = $this->mailer->createMessage(); + $message->setFrom([$from => $this->defaults->getName()]); + $message->setTo([$registration->getEmail()]); + $message->setSubject($subject); + $message->setPlainBody($plaintext_part); + $message->setHtmlBody($html_part); + $failed_recipients = $this->mailer->send($message); + if ( !empty($failed_recipients) ) { + throw new RegistrationException($this->l10n->t('A problem occurred sending email, please contact your administrator.')); + } + } + + public function notifyAdmins($userId) { + // Notify admin + $admin_users = $this->groupManager->get('admin')->getUsers(); + $to_arr = array(); + foreach ( $admin_users as $au ) { + $au_email = $this->config->getUserValue($au->getUID(), 'settings', 'email'); + if ( $au_email !== '' ) { + $to_arr[$au_email] = $au->getDisplayName(); + } + } + try { + $this->sendNewUserNotifEmail($to_arr, $userId); + } catch (\Exception $e) { + $this->logger->error('Sending admin notification email failed: '. $e->getMessage()); + } + } + + /** + * Sends new user notification email to admin + * @param array $to + * @param string $username the new user + * @throws \Exception + */ + private function sendNewUserNotifEmail(array $to, $username) { + $template_var = [ + 'user' => $username, + 'sitename' => $this->defaults->getName() + ]; + $html_template = new TemplateResponse('registration', 'email.newuser_html', $template_var, 'blank'); + $html_part = $html_template->render(); + $plaintext_template = new TemplateResponse('registration', 'email.newuser_plaintext', $template_var, 'blank'); + $plaintext_part = $plaintext_template->render(); + $subject = $this->l10n->t('A new user "%s" has created an account on %s', [$username, $this->defaults->getName()]); + + $from = Util::getDefaultEmailAddress('register'); + $message = $this->mailer->createMessage(); + $message->setFrom([$from => $this->defaults->getName()]); + $message->setTo($to); + $message->setSubject($subject); + $message->setPlainBody($plaintext_part); + $message->setHtmlBody($html_part); + $failed_recipients = $this->mailer->send($message); + if ( !empty($failed_recipients) ) + throw new RegistrationException('Failed recipients: '.print_r($failed_recipients, true)); + } + +} \ No newline at end of file diff --git a/service/registrationservice.php b/service/registrationservice.php index 1c90ae2..1dba483 100644 --- a/service/registrationservice.php +++ b/service/registrationservice.php @@ -23,6 +23,11 @@ namespace OCA\Registration\Service; +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Exceptions\PasswordlessTokenException; +use OC\Authentication\Token\IProvider; +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; @@ -30,22 +35,25 @@ use \OCP\AppFramework\Http\RedirectResponse; use \OCP\Defaults; use OCP\ILogger; use OCP\IRequest; +use OCP\ISession; use OCP\IURLGenerator; -use \OCP\Util; +use OCP\Security\ICrypto; +use OCP\Session\Exceptions\SessionNotAvailableException; use \OCP\IUserManager; use \OCP\IUserSession; use \OCP\IGroupManager; use \OCP\IL10N; use \OCP\IConfig; -use \OCP\Mail\IMailer; use \OCP\Security\ISecureRandom; use \OC_User; use \OC_Util; class RegistrationService { - /** @var IMailer */ - private $mailer; + /** @var string */ + private $appName; + /** @var MailService */ + private $mailService; /** @var IL10N */ private $l10n; /** @var IURLGenerator */ @@ -64,17 +72,22 @@ class RegistrationService { private $random; /** @var IUserSession */ private $usersession; - /** @var string */ - private $appName; /** @var IRequest */ private $request; /** @var ILogger */ private $logger; + /** @var ISession */ + private $session; + /** @var IProvider */ + private $tokenProvider; + /** @var ICrypto */ + private $crypto; - public function __construct($appName, IMailer $mailer, IL10N $l10n, IURLGenerator $urlGenerator, + public function __construct($appName, MailService $mailService, IL10N $l10n, IURLGenerator $urlGenerator, RegistrationMapper $registrationMapper, IUserManager $userManager, IConfig $config, IGroupManager $groupManager, Defaults $defaults, - ISecureRandom $random, IUserSession $us, IRequest $request, ILogger $logger){ - $this->mailer = $mailer; + ISecureRandom $random, IUserSession $us, IRequest $request, ILogger $logger, ISession $session, IProvider $tokenProvider, ICrypto $crypto){ + $this->appName = $appName; + $this->mailService = $mailService; $this->l10n = $l10n; $this->urlGenerator = $urlGenerator; $this->registrationMapper = $registrationMapper; @@ -84,29 +97,60 @@ class RegistrationService { $this->defaults = $defaults; $this->random = $random; $this->usersession = $us; - $this->appName = $appName; $this->request = $request; $this->logger = $logger; + $this->session = $session; + $this->tokenProvider = $tokenProvider; + $this->crypto = $crypto; } + public function confirmEmail(Registration &$registration) { + $registration->setEmailConfirmed(true); + $this->registrationMapper->update($registration); + } + /** + * @param Registration $registration + */ + public function generateNewToken(Registration &$registration) { + $this->registrationMapper->generateNewToken($registration); + $this->registrationMapper->update($registration); + } + /** + * @param $email + * @param string $username + * @param string $password + * @param string $displayname + * @return Registration + */ + public function createRegistration($email, $username="", $password="", $displayname="") { + $registration = new Registration(); + $registration->setEmail($email); + $registration->setUsername($username); + $registration->setDisplayname(); + if($password !== "") { + $password = $this->crypto->encrypt($password); + $registration->setPassword($password); + } + $this->registrationMapper->generateNewToken($registration); + $this->registrationMapper->generateClientSecret($registration); + $this->registrationMapper->insert($registration); + return $registration; + } + + /** + * @param $email + * @return Registration + * @throws RegistrationException + */ public function validateEmail($email) { - if ( !$this->mailer->validateMailAddress($email) ) { - throw new RegistrationException($this->l10n->t('The email address you entered is not valid')); - } + $this->mailService->validateEmail($email); + // check for pending registrations try { - $registration = $this->registrationMapper->find($email); - } catch (\Exception $e) { - $registration = null; - } - // check if email already tried to register - if ( $registration !== null) { - $this->registrationMapper->delete($registration); - $this->generateToken($email); - throw new RegistrationException($this->l10n->t('There is already a pending registration with this email, a new verification email has been sent to the address.')); - } + return $this->registrationMapper->find($email); + } catch (\Exception $e) {} if ( $this->config->getUsersForUserValue('settings', 'email', $email) ) { throw new RegistrationException( @@ -115,32 +159,34 @@ class RegistrationService { ); } - // allow only from specific email domain} if (!$this->checkAllowedDomains($email)) { - $allowed_domains = $this->config->getAppValue($this->appName, 'allowed_domains', ''); - $allowed_domains = explode(';', $allowed_domains); - return new TemplateResponse('registration', 'domains', [ - 'domains' => $allowed_domains - ], 'guest'); + throw new RegistrationException( + $this->l10n->t( + 'Registration is only allowed for the following domains: ' . + $this->config->getAppValue($this->appName, 'allowed_domains', '') + ) + ); } - - $this->generateToken($email); - return null; - } - public function generateToken($email) { - try { - $registration = $this->registrationMapper->find($email); - $this->registrationMapper->delete($registration); - } catch (\Exception $exception) {} - $registration = $this->registrationMapper->save($email); + /** + * @param $displayname + * @throws RegistrationException + */ + public function validateDisplayname($displayname) { + if($displayname === "") { + throw new RegistrationException($this->l10n->t('Please provide a valid display name.')); + } + } - try { - $this->sendValidationEmail($registration->getToken(), $email); - } catch (\Exception $e) { - throw new RegistrationException($this->l10n->t('A problem occurred sending email, please contact your administrator.')); + /** + * @param $username + * @throws RegistrationException + */ + public function validateUsername($username) { + if($username === "" || $this->userManager->get($username) !== null) { + throw new RegistrationException($this->l10n->t('Please provide a valid user name.')); } } @@ -169,6 +215,17 @@ class RegistrationService { } /** + * @return array + */ + public function getAllowedDomains() { + $allowed_domains = $this->config->getAppValue($this->appName, 'allowed_domains', ''); + $allowed_domains = explode(';', $allowed_domains); + return $allowed_domains; + } + + /** + * Find registration entity for token + * * @param $token * @return string * @throws RegistrationException @@ -181,11 +238,25 @@ class RegistrationService { } } + /** + * @param $registration + * @param null $username + * @param null $password + * @return \OCP\IUser + * @throws RegistrationException + */ + public function createAccount(Registration &$registration, $username = null, $password = null) { + if($password === null && $registration->getPassword() === null) { + $generatedPassword = $this->generateRandomDeviceToken(); + $registration->setPassword($this->crypto->encrypt($generatedPassword)); + } - public function createAccount($token, $username, $password) { - $email = $this->registrationMapper->findEmailByToken($token); - if ( $email === false ) { - throw new RegistrationException($this->l10n->t('Invalid verification URL. No registration request with this verification URL is found.')); + if ($username === null) { + $username = $registration->getUsername(); + } + + if($registration->getPassword() !== null) { + $password = $this->crypto->decrypt($registration->getPassword()); } $user = $this->userManager->createUser($username, $password); @@ -195,7 +266,7 @@ class RegistrationService { $userId = $user->getUID(); // Set user email try { - $this->config->setUserValue($userId, 'settings', 'email', $email); + $this->config->setUserValue($userId, 'settings', 'email', $registration->getEmail()); } catch (\Exception $e) { throw new RegistrationException($this->l10n->t('Unable to set user email: ' . $e->getMessage())); } @@ -211,19 +282,98 @@ class RegistrationService { } } - // Delete pending reg request - $res = $this->registrationMapper->deleteByEmail($email); - if ($res === false) { - throw new RegistrationException($this->l10n->t('Failed to delete pending registration request')); + // Delete pending registration if no client secret is stored + 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->notifyAdmins($userId); - - $this->loginUser($userId, $username, $password); - + $this->mailService->notifyAdmins($userId); + return $user; } - public function loginUser($userId, $username, $password) { + /** + * @param $token + * @return Registration + */ + public function getRegistrationForToken($token) { + return $this->registrationMapper->findByToken($token); + } + + /** + * @param Registration $registation + * @return null|\OCP\IUser + */ + public function getUserAccount(Registration $registation) { + $user = $this->userManager->get($registation->getUsername()); + return $user; + } + + /** + * @param Registration $registration + */ + public function deleteRegistration(Registration $registration) { + $this->registrationMapper->delete($registration); + } + + /** + * Return a 25 digit device password + * + * Example: AbCdE-fGhIj-KlMnO-pQrSt-12345 + * + * @return string + */ + private function generateRandomDeviceToken() { + $groups = []; + for ($i = 0; $i < 5; $i++) { + $groups[] = $this->random->generate(5, ISecureRandom::CHAR_HUMAN_READABLE); + } + return implode('-', $groups); + } + + /** + * @param $uid + * @return string + * @throws RegistrationException + */ + public function generateAppPassword($uid) { + $name = $this->l10n->t('Registration app auto setup'); + try { + $sessionId = $this->session->getId(); + } catch (SessionNotAvailableException $ex) { + throw new RegistrationException('Failed to generate an app token.'); + } + + try { + $sessionToken = $this->tokenProvider->getToken($sessionId); + $loginName = $sessionToken->getLoginName(); + try { + $password = $this->tokenProvider->getPassword($sessionToken, $sessionId); + } catch (PasswordlessTokenException $ex) { + $password = null; + } + } catch (InvalidTokenException $ex) { + throw new RegistrationException('Failed to generate an app token.'); + } + + $token = $this->generateRandomDeviceToken(); + $this->tokenProvider->generateToken($token, $uid, $loginName, $password, $name, IToken::PERMANENT_TOKEN); + return $token; + } + + /** + * @param $userId + * @param $username + * @param $password + * @param $decrypt + * @return RedirectResponse|TemplateResponse + */ + public function loginUser($userId, $username, $password, $decrypt = false) { + 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); @@ -244,83 +394,6 @@ class RegistrationService { } - public function notifyAdmins($userId) { - // Notify admin - $admin_users = $this->groupManager->get('admin')->getUsers(); - $to_arr = array(); - foreach ( $admin_users as $au ) { - $au_email = $this->config->getUserValue($au->getUID(), 'settings', 'email'); - if ( $au_email !== '' ) { - $to_arr[$au_email] = $au->getDisplayName(); - } - } - try { - $this->sendNewUserNotifEmail($to_arr, $userId); - } catch (\Exception $e) { - $this->logger->error('Sending admin notification email failed: '. $e->getMessage()); - } - } - - /** - * Sends validation email - * @param string $token - * @param string $to - * @throws \Exception - */ - private function sendValidationEmail($token, $to) { - $link = $this->urlGenerator->linkToRoute('registration.register.verifyToken', array('token' => $token)); - $link = $this->urlGenerator->getAbsoluteURL($link); - $template_var = [ - 'link' => $link, - 'sitename' => $this->defaults->getName() - ]; - $html_template = new TemplateResponse('registration', 'email.validate_html', $template_var, 'blank'); - $html_part = $html_template->render(); - $plaintext_template = new TemplateResponse('registration', 'email.validate_plaintext', $template_var, 'blank'); - $plaintext_part = $plaintext_template->render(); - $subject = $this->l10n->t('Verify your %s registration request', [$this->defaults->getName()]); - - $from = Util::getDefaultEmailAddress('register'); - $message = $this->mailer->createMessage(); - $message->setFrom([$from => $this->defaults->getName()]); - $message->setTo([$to]); - $message->setSubject($subject); - $message->setPlainBody($plaintext_part); - $message->setHtmlBody($html_part); - $failed_recipients = $this->mailer->send($message); - if ( !empty($failed_recipients) ) - throw new RegistrationException('Failed recipients: '.print_r($failed_recipients, true)); - } - - /** - * Sends new user notification email to admin - * @param array $to - * @param string $username the new user - * @throws \Exception - */ - private function sendNewUserNotifEmail(array $to, $username) { - $template_var = [ - 'user' => $username, - 'sitename' => $this->defaults->getName() - ]; - $html_template = new TemplateResponse('registration', 'email.newuser_html', $template_var, 'blank'); - $html_part = $html_template->render(); - $plaintext_template = new TemplateResponse('registration', 'email.newuser_plaintext', $template_var, 'blank'); - $plaintext_part = $plaintext_template->render(); - $subject = $this->l10n->t('A new user "%s" has created an account on %s', [$username, $this->defaults->getName()]); - - $from = Util::getDefaultEmailAddress('register'); - $message = $this->mailer->createMessage(); - $message->setFrom([$from => $this->defaults->getName()]); - $message->setTo($to); - $message->setSubject($subject); - $message->setPlainBody($plaintext_part); - $message->setHtmlBody($html_part); - $failed_recipients = $this->mailer->send($message); - if ( !empty($failed_recipients) ) - throw new RegistrationException('Failed recipients: '.print_r($failed_recipients, true)); - } - /** * Replicates OC::cleanupLoginTokens() since it's protected * @param string $userId @@ -336,7 +409,4 @@ class RegistrationService { } } - public function getRegistrationForToken($token) { - return $this->registrationMapper->findByToken($token); - } } From c17398deded12d72ce0ebd9a71a129f13b1164e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Fri, 30 Jun 2017 09:40:33 +0200 Subject: [PATCH 3/7] Fix copyright, comments, phpdoc and OCS status codes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- capabilities.php | 2 +- controller/apicontroller.php | 34 +++++++++++++++---------------- service/mailservice.php | 14 +++++++++++++ service/registrationexception.php | 14 +++++++++---- service/registrationservice.php | 25 +++++++++++++++-------- 5 files changed, 57 insertions(+), 32 deletions(-) diff --git a/capabilities.php b/capabilities.php index 035ad97..58021e5 100644 --- a/capabilities.php +++ b/capabilities.php @@ -41,7 +41,7 @@ class Capabilities implements ICapability { [ 'enabled' => true, 'apiRoot' => $this->urlGenerator->linkTo( - '', 'ocs/v1.php/apps/registration/api/v1/'), + '', 'ocs/v2.php/apps/registration/api/v1/'), 'apiLevel' => 'v1' ] ]; diff --git a/controller/apicontroller.php b/controller/apicontroller.php index 32bc2d0..bd04ca1 100644 --- a/controller/apicontroller.php +++ b/controller/apicontroller.php @@ -49,16 +49,16 @@ class ApiController extends OCSController { /** @var Defaults */ private $defaults; + const OCS_STATUS_PENDING = 101; + const OCS_STATUS_RESENT = 102; + public function __construct($appName, IRequest $request, - $corsMethods = 'PUT, POST, GET, DELETE, PATCH', - $corsAllowedHeaders = 'Authorization, Content-Type, Accept', - $corsMaxAge = 1728000, RegistrationService $registrationService, MailService $mailService, IL10N $l10n, Defaults $defaults) { - parent::__construct($appName, $request, $corsMethods, $corsAllowedHeaders, $corsMaxAge); + parent::__construct($appName, $request); $this->registrationService = $registrationService; $this->mailService = $mailService; $this->l10n = $l10n; @@ -69,9 +69,9 @@ class ApiController extends OCSController { * @PublicPage * @AnonRateThrottle(limit=5, period=1) * - * @param $username - * @param $displayname - * @param $email + * @param string $username + * @param string $displayname + * @param string $email * @throws OCSException * @return DataResponse */ @@ -93,9 +93,10 @@ class ApiController extends OCSController { /** * @PublicPage + * @AnonRateThrottle(limit=10, period=1) * - * @param $registrationToken - * @param $clientSecret + * @param string $registrationToken + * @param string $clientSecret * @throws OCSException * @return DataResponse */ @@ -105,10 +106,7 @@ class ApiController extends OCSController { /** @var Registration $registration */ $registration = $this->registrationService->getRegistrationForToken($registrationToken); if(!$registration->getEmailConfirmed()) { - $data = [ - 'status' => Registration::STATUS_PENDING, - 'message' => $this->l10n->t('Your registration is pending. Please confirm your email address.') - ]; + throw new OCSException($this->l10n->t('Your registration is pending. Please confirm your email address.'), self::OCS_STATUS_PENDING); } else { // create account if email confirmed and not already created $user = $this->registrationService->getUserAccount($registration); @@ -135,10 +133,10 @@ class ApiController extends OCSController { /** * @PublicPage * - * @param $username - * @param $displayname - * @param $email - * @param $password + * @param string $username + * @param string $displayname + * @param string $email + * @param string $password * @throws OCSException * @return DataResponse */ @@ -156,7 +154,7 @@ class ApiController extends OCSController { } else { $this->registrationService->generateNewToken($registration); $this->mailService->sendTokenByMail($registration); - throw new RegistrationException($this->l10n->t('There is already a pending registration with this email, a new verification email has been sent to the address.')); + throw new OCSException($this->l10n->t('There is already a pending registration with this email, a new verification email has been sent to the address.'), self::OCS_STATUS_RESENT); } $data['message'] = $this->l10n->t('Your registration is pending. Please confirm your email address.'); diff --git a/service/mailservice.php b/service/mailservice.php index 0b23008..7122b21 100644 --- a/service/mailservice.php +++ b/service/mailservice.php @@ -1,8 +1,10 @@ + * @copyright Copyright (c) 2017 Pellaeon Lin * * @author Julius Härtl + * @author Pellaeon Lin * * @license GNU AGPL version 3 or any later version * @@ -61,12 +63,21 @@ class MailService { $this->logger = $logger; } + /** + * @param string $email + * @throws RegistrationException + */ public function validateEmail($email) { if ( !$this->mailer->validateMailAddress($email) ) { throw new RegistrationException($this->l10n->t('The email address you entered is not valid')); } } + /** + * @param Registration $registration + * @return bool + * @throws RegistrationException + */ public function sendTokenByMail(Registration $registration) { return true; $link = $this->urlGenerator->linkToRoute('registration.register.verifyToken', array('token' => $registration->getToken())); @@ -94,6 +105,9 @@ class MailService { } } + /** + * @param string $userId + */ public function notifyAdmins($userId) { // Notify admin $admin_users = $this->groupManager->get('admin')->getUsers(); diff --git a/service/registrationexception.php b/service/registrationexception.php index 48d6e2c..1035811 100644 --- a/service/registrationexception.php +++ b/service/registrationexception.php @@ -27,16 +27,22 @@ class RegistrationException extends \Exception { protected $hint; + /** + * RegistrationException constructor. + * + * @param string $message + * @param string $hint + */ public function __construct($message, $hint = "") { parent::__construct($message); - $this->setHint($hint); - } - - public function setHint($hint) { $this->hint = $hint; } + /** + * @return string + */ public function getHint() { return $this->hint; } + } \ No newline at end of file diff --git a/service/registrationservice.php b/service/registrationservice.php index 1dba483..d9e2398 100644 --- a/service/registrationservice.php +++ b/service/registrationservice.php @@ -1,8 +1,12 @@ + * @copyright Copyright (c) 2017 Pellaeon Lin + * @copyright Copyright (c) 2017 Lukas Reschke * * @author Julius Härtl + * @author Pellaeon Lin + * @author Lukas Reschke * * @license GNU AGPL version 3 or any later version * @@ -104,6 +108,9 @@ class RegistrationService { $this->crypto = $crypto; } + /** + * @param Registration $registration + */ public function confirmEmail(Registration &$registration) { $registration->setEmailConfirmed(true); $this->registrationMapper->update($registration); @@ -117,7 +124,7 @@ class RegistrationService { $this->registrationMapper->update($registration); } /** - * @param $email + * @param string $email * @param string $username * @param string $password * @param string $displayname @@ -139,7 +146,7 @@ class RegistrationService { } /** - * @param $email + * @param string $email * @return Registration * @throws RegistrationException */ @@ -171,7 +178,7 @@ class RegistrationService { } /** - * @param $displayname + * @param string $displayname * @throws RegistrationException */ public function validateDisplayname($displayname) { @@ -181,7 +188,7 @@ class RegistrationService { } /** - * @param $username + * @param string $username * @throws RegistrationException */ public function validateUsername($username) { @@ -193,7 +200,7 @@ class RegistrationService { /** * check if email domain is allowed * - * @param $email + * @param string $email * @return bool */ public function checkAllowedDomains($email) { @@ -226,7 +233,7 @@ class RegistrationService { /** * Find registration entity for token * - * @param $token + * @param string $token * @return string * @throws RegistrationException */ @@ -240,8 +247,8 @@ class RegistrationService { /** * @param $registration - * @param null $username - * @param null $password + * @param string $username + * @param string $password * @return \OCP\IUser * @throws RegistrationException */ @@ -334,7 +341,7 @@ class RegistrationService { } /** - * @param $uid + * @param string $uid * @return string * @throws RegistrationException */ From f4521a9d29e23225b86c6ca53f2fecd4fbfae4c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Fri, 30 Jun 2017 15:54:29 +0200 Subject: [PATCH 4/7] Add basic unit testing and tests for API controller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- phpunit.xml | 13 +- tests/autoloader.php | 42 +---- tests/unit/controller/ApiControllerTest.php | 174 +++++++++++++++++++ tests/unit/controller/PageControllerTest.php | 53 ------ 4 files changed, 187 insertions(+), 95 deletions(-) create mode 100644 tests/unit/controller/ApiControllerTest.php delete mode 100644 tests/unit/controller/PageControllerTest.php diff --git a/phpunit.xml b/phpunit.xml index 7e5a983..aff4ae0 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1 +1,12 @@ - \ No newline at end of file + + + + ./tests/unit + + + + + ./ + + + diff --git a/tests/autoloader.php b/tests/autoloader.php index f62e632..ffaf089 100644 --- a/tests/autoloader.php +++ b/tests/autoloader.php @@ -9,44 +9,4 @@ * @copyright Pellaeon Lin 2014 */ -require_once __DIR__ . '/../../../3rdparty/Pimple/Pimple.php'; - - -class OC { - public static $server; - public static $session; -} - -// to execute without owncloud, we need to create our own classloader -spl_autoload_register(function ($className){ - if (strpos($className, 'OCA\\') === 0) { - - $path = strtolower(str_replace('\\', '/', substr($className, 3)) . '.php'); - $relPath = __DIR__ . '/../..' . $path; - - if(file_exists($relPath)){ - require_once $relPath; - } - } else if(strpos($className, 'OCP\\') === 0) { - $path = strtolower(str_replace('\\', '/', substr($className, 3)) . '.php'); - $relPath = __DIR__ . '/../../../lib/public' . $path; - - if(file_exists($relPath)){ - require_once $relPath; - } - } else if(strpos($className, 'OC_') === 0) { - $path = strtolower(str_replace('\\', '/', substr($className, 3)) . '.php'); - $relPath = __DIR__ . '/../../../lib/private/' . $path; - - if(file_exists($relPath)){ - require_once $relPath; - } - } else if(strpos($className, 'OC\\') === 0) { - $path = strtolower(str_replace('\\', '/', substr($className, 2)) . '.php'); - $relPath = __DIR__ . '/../../../lib/private' . $path; - - if(file_exists($relPath)){ - require_once $relPath; - } - } -}); \ No newline at end of file +require_once __DIR__ . '/../../../tests/bootstrap.php'; diff --git a/tests/unit/controller/ApiControllerTest.php b/tests/unit/controller/ApiControllerTest.php new file mode 100644 index 0000000..d16a43b --- /dev/null +++ b/tests/unit/controller/ApiControllerTest.php @@ -0,0 +1,174 @@ + + * @copyright Pellaeon Lin 2014 + */ + +namespace OCA\Registration\Controller; + +use OCA\Registration\Db\Registration; +use OCA\Registration\Service\MailService; +use OCA\Registration\Service\RegistrationService; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCS\OCSException; +use OCP\AppFramework\OCS\OCSNotFoundException; +use OCP\Defaults; +use OCP\IL10N; +use OCP\IRequest; +use OCP\IUser; +use \Test\TestCase; + +class ApiControllerTest extends TestCase { + + /** @var IRequest */ + private $request; + /** @var RegistrationService|\PHPUnit_Framework_MockObject_MockObject */ + private $registrationService; + /** @var MailService */ + private $mailService; + /** @var IL10N */ + private $l10n; + /** @var Defaults */ + private $defaults; + /** @var ApiController */ + private $controller; + + public function setUp () { + parent::setUp(); + $this->request = $this->createMock(IRequest::class); + $this->registrationService = $this->createMock(RegistrationService::class); + $this->mailService = $this->createMock(MailService::class); + $this->l10n = $this->createMock(IL10N::class); + $this->defaults = $this->createMock(Defaults::class); + $this->controller = new ApiController( + "registration", + $this->request, + $this->registrationService, + $this->mailService, + $this->l10n, + $this->defaults + ); + } + + public function testValidate() { + $this->registrationService + ->expects($this->once()) + ->method('validateEmail') + ->with('test@example.com'); + $this->registrationService + ->expects($this->once()) + ->method('validateDisplayname') + ->with('user test'); + $this->registrationService + ->expects($this->once()) + ->method('validateUsername') + ->with('user1'); + + $expected = new DataResponse([ + 'username' => 'user1', + 'displayname' => 'user test', + 'email' => 'test@example.com' + ], Http::STATUS_OK); + $actual = $this->controller->validate('user1', 'user test', 'test@example.com'); + $this->assertEquals($expected, $actual); + } + + /** + * @expectedException \OCP\AppFramework\OCS\OCSException + * @expectedExceptionCode 999 + */ + public function testValidateFailEmail() { + $this->registrationService + ->expects($this->once()) + ->method('validateEmail') + ->willThrowException(new OCSException('', 999)); + $this->controller->validate('user1', 'user test', 'test@example.com'); + } + + /** + * @expectedException \OCP\AppFramework\OCS\OCSException + * @expectedExceptionCode 999 + */ + public function testValidateFailDisplayname() { + $this->registrationService + ->expects($this->once()) + ->method('validateDisplayname') + ->willThrowException(new OCSException('', 999)); + $this->controller->validate('user1', 'user test', 'test@example.com'); + } + + /** + * @expectedException \OCP\AppFramework\OCS\OCSException + * @expectedExceptionCode 999 + */ + public function testValidateFailUsername() { + $this->registrationService + ->expects($this->once()) + ->method('validateUsername') + ->willThrowException(new OCSException('', 999)); + $this->controller->validate('user1', 'user test', 'test@example.com'); + } + + /** + * @expectedException \OCP\AppFramework\OCS\OCSNotFoundException + * @expectedExceptionCode 404 + */ + public function testStatusNoRegistration() { + $this->registrationService + ->method('getRegistrationForToken') + ->with('ABCDEF') + ->willThrowException(new DoesNotExistException('')); + $this->controller->status('ABCDEF'); + } + + /** + * @expectedException \OCP\AppFramework\OCS\OCSException + * @expectedExceptionCode 403 + */ + public function testStatusPendingRegistration() { + $registration = new Registration(); + $registration->setEmailConfirmed(false); + $this->registrationService + ->method('getRegistrationForToken') + ->with('ABCDEF') + ->willReturn($registration); + $actual = $this->controller->status('ABCDEF'); + } + + public function testStatusConfirmedRegistration() { + $registration = new Registration(); + $registration->setEmailConfirmed(true); + $registration->setClientSecret('mysecret'); + $user = $this->createMock(IUser::class); + $this->registrationService + ->method('getRegistrationForToken') + ->with('ABCDEF') + ->willReturn($registration); + $this->registrationService + ->expects($this->once()) + ->method('getUserAccount') + ->with($registration) + ->willReturn($user); + $this->registrationService + ->expects($this->once()) + ->method('loginUser'); + $this->registrationService + ->expects($this->once()) + ->method('generateAppPassword'); + $actual = $this->controller->status('ABCDEF'); + $expected = new DataResponse([]); + $this->assertEquals($expected, $actual); + } + + public function testStatusConfirmedRegistrationWithSecret() { + + } + +} \ No newline at end of file diff --git a/tests/unit/controller/PageControllerTest.php b/tests/unit/controller/PageControllerTest.php deleted file mode 100644 index 0e084ed..0000000 --- a/tests/unit/controller/PageControllerTest.php +++ /dev/null @@ -1,53 +0,0 @@ - - * @copyright Pellaeon Lin 2014 - */ - -namespace OCA\Registration\Controller; - - -use \OCP\IRequest; -use \OCP\AppFramework\Http\TemplateResponse; -use \OCP\AppFramework\Http\JSONResponse; - -use \OCA\Registration\AppInfo\Application; - - -class PageControllerTest extends \PHPUnit_Framework_TestCase { - - private $container; - - public function setUp () { - $app = new Application(); - $this->container = $app->getContainer(); - } - - - public function testIndex () { - // swap out request - $this->container['Request'] = $this->getMockBuilder('\OCP\IRequest') - ->getMock(); - $this->container['UserId'] = 'john'; - - $result = $this->container['PageController']->index(); - - $this->assertEquals(array('user' => 'john'), $result->getParams()); - $this->assertEquals('main', $result->getTemplateName()); - $this->assertTrue($result instanceof TemplateResponse); - } - - - public function testEcho () { - $result = $this->container['PageController']->doEcho('hi'); - - $this->assertEquals(array('echo' => 'hi'), $result); - } - - -} \ No newline at end of file From 74b542819370d7790d1db230281f90804ab00788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Fri, 30 Jun 2017 15:55:21 +0200 Subject: [PATCH 5/7] Use client secret as identifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- appinfo/app.php | 6 ++++-- appinfo/database.xml | 2 ++ capabilities.php | 3 ++- controller/apicontroller.php | 31 ++++++++++++++----------------- db/registrationmapper.php | 5 +++++ service/registrationservice.php | 8 ++++++++ 6 files changed, 35 insertions(+), 20 deletions(-) diff --git a/appinfo/app.php b/appinfo/app.php index 33a0147..172e0a4 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -18,5 +18,7 @@ namespace OCA\Registration\AppInfo; \OCP\App::registerAdmin('registration', 'admin'); -$app = new \OCP\AppFramework\App('registration'); -$app->getContainer()->registerCapability(\OCA\Registration\Capabilities::class); +if(interface_exists('\OCP\Capabilities\IPublicCapability')) { + $app = new \OCP\AppFramework\App('registration'); + $app->getContainer()->registerCapability(\OCA\Registration\Capabilities::class); +} \ No newline at end of file diff --git a/appinfo/database.xml b/appinfo/database.xml index ff4d751..572c9aa 100644 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -42,10 +42,12 @@ token text true + true client_secret text + true requested diff --git a/capabilities.php b/capabilities.php index 58021e5..a97f56e 100644 --- a/capabilities.php +++ b/capabilities.php @@ -24,9 +24,10 @@ namespace OCA\Registration; use OCP\Capabilities\ICapability; +use OCP\Capabilities\IPublicCapability; use OCP\IURLGenerator; -class Capabilities implements ICapability { +class Capabilities implements IPublicCapability { /** @var IURLGenerator */ private $urlGenerator; diff --git a/controller/apicontroller.php b/controller/apicontroller.php index bd04ca1..4ddba0c 100644 --- a/controller/apicontroller.php +++ b/controller/apicontroller.php @@ -49,8 +49,8 @@ class ApiController extends OCSController { /** @var Defaults */ private $defaults; - const OCS_STATUS_PENDING = 101; - const OCS_STATUS_RESENT = 102; + const OCS_STATUS_PENDING = 403; + const OCS_STATUS_RESENT = 403; public function __construct($appName, IRequest $request, @@ -95,18 +95,19 @@ class ApiController extends OCSController { * @PublicPage * @AnonRateThrottle(limit=10, period=1) * - * @param string $registrationToken * @param string $clientSecret * @throws OCSException * @return DataResponse */ - public function status($registrationToken, $clientSecret=null) { - $data = []; + public function status($clientSecret) { try { /** @var Registration $registration */ - $registration = $this->registrationService->getRegistrationForToken($registrationToken); + $registration = $this->registrationService->getRegistrationForSecret($clientSecret); if(!$registration->getEmailConfirmed()) { - throw new OCSException($this->l10n->t('Your registration is pending. Please confirm your email address.'), self::OCS_STATUS_PENDING); + throw new OCSException( + $this->l10n->t('Your registration is pending. Please confirm your email address.'), + self::OCS_STATUS_PENDING + ); } else { // create account if email confirmed and not already created $user = $this->registrationService->getUserAccount($registration); @@ -115,16 +116,13 @@ class ApiController extends OCSController { } $this->registrationService->loginUser($user->getUID(), $registration->getUsername(), $registration->getPassword(), true); $appPassword = $this->registrationService->generateAppPassword($user->getUID()); - if ($clientSecret === $registration->getClientSecret()) { - $data = [ - 'status' => Registration::STATUS_FINISHED, - 'appPassword' => $appPassword, - 'cloudUrl' => $this->defaults->getBaseUrl() - ]; - $this->registrationService->deleteRegistration($registration); - } + $data = [ + 'appPassword' => $appPassword, + 'cloudUrl' => $this->defaults->getBaseUrl() + ]; + $this->registrationService->deleteRegistration($registration); + return new DataResponse($data, Http::STATUS_OK); } - return new DataResponse($data, Http::STATUS_OK); } catch (DoesNotExistException $e) { throw new OCSNotFoundException('No pending registration.'); } @@ -158,7 +156,6 @@ class ApiController extends OCSController { } $data['message'] = $this->l10n->t('Your registration is pending. Please confirm your email address.'); - $data['token'] = $registration->getToken(); $data['status'] = Registration::STATUS_PENDING; if($secret !== null) { $data['secret'] = $secret; diff --git a/db/registrationmapper.php b/db/registrationmapper.php index 21e670a..8ebd54c 100644 --- a/db/registrationmapper.php +++ b/db/registrationmapper.php @@ -46,6 +46,11 @@ class RegistrationMapper extends Mapper { return $this->findEntity('SELECT * FROM `*PREFIX*registration` WHERE `token` = ? ', [$token]); } + public function findBySecret($secret) { + return $this->findEntity('SELECT * FROM `*PREFIX*registration` WHERE `client_secret` = ? ', [$secret]); + + } + /** * @param $email * @return Registration|Entity diff --git a/service/registrationservice.php b/service/registrationservice.php index d9e2398..4131f7e 100644 --- a/service/registrationservice.php +++ b/service/registrationservice.php @@ -309,6 +309,14 @@ class RegistrationService { return $this->registrationMapper->findByToken($token); } + /** + * @param $secret + * @return Registration + */ + public function getRegistrationForSecret($secret) { + return $this->registrationMapper->findBySecret($secret); + } + /** * @param Registration $registation * @return null|\OCP\IUser From fffdb77ff6a76a36e3ec52e7651b618cbe8e0c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Sat, 8 Jul 2017 14:21:19 +0200 Subject: [PATCH 6/7] Cleanup status codes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- appinfo/database.xml | 2 - controller/apicontroller.php | 63 +++++++++++++++++++------------ db/registration.php | 3 -- service/mailservice.php | 1 - service/registrationexception.php | 5 ++- service/registrationservice.php | 2 +- 6 files changed, 42 insertions(+), 34 deletions(-) diff --git a/appinfo/database.xml b/appinfo/database.xml index 572c9aa..ff4d751 100644 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -42,12 +42,10 @@ token text true - true client_secret text - true requested diff --git a/controller/apicontroller.php b/controller/apicontroller.php index 4ddba0c..a00a85b 100644 --- a/controller/apicontroller.php +++ b/controller/apicontroller.php @@ -49,8 +49,9 @@ class ApiController extends OCSController { /** @var Defaults */ private $defaults; - const OCS_STATUS_PENDING = 403; - const OCS_STATUS_RESENT = 403; + const REGISTRATION_STATUS_COMPLETE = 0; + const REGISTRATION_STATUS_PENDING = 1; + const REGISTRATION_STATUS_EXISTING = 2; public function __construct($appName, IRequest $request, @@ -103,33 +104,39 @@ class ApiController extends OCSController { try { /** @var Registration $registration */ $registration = $this->registrationService->getRegistrationForSecret($clientSecret); - if(!$registration->getEmailConfirmed()) { - throw new OCSException( - $this->l10n->t('Your registration is pending. Please confirm your email address.'), - self::OCS_STATUS_PENDING - ); - } else { - // create account if email confirmed and not already created - $user = $this->registrationService->getUserAccount($registration); - if($user === null) { - $user = $this->registrationService->createAccount($registration); - } - $this->registrationService->loginUser($user->getUID(), $registration->getUsername(), $registration->getPassword(), true); - $appPassword = $this->registrationService->generateAppPassword($user->getUID()); - $data = [ - 'appPassword' => $appPassword, - 'cloudUrl' => $this->defaults->getBaseUrl() - ]; - $this->registrationService->deleteRegistration($registration); - return new DataResponse($data, Http::STATUS_OK); - } } catch (DoesNotExistException $e) { throw new OCSNotFoundException('No pending registration.'); } + + if (!$registration->getEmailConfirmed()) { + return new DataResponse( + [ + 'registrationStatus' => self::REGISTRATION_STATUS_PENDING, + 'message' => $this->l10n->t('Your registration is pending. Please confirm your email address.') + ], + Http::STATUS_OK + ); + } else { + // create account if email confirmed and not already created + $user = $this->registrationService->getUserAccount($registration); + if ($user === null) { + $user = $this->registrationService->createAccount($registration); + } + $this->registrationService->loginUser($user->getUID(), $registration->getUsername(), $registration->getPassword(), true); + $appPassword = $this->registrationService->generateAppPassword($user->getUID()); + $data = [ + 'appPassword' => $appPassword, + 'cloudUrl' => $this->defaults->getBaseUrl(), + 'registrationStatus' => self::REGISTRATION_STATUS_COMPLETE + ]; + $this->registrationService->deleteRegistration($registration); + return new DataResponse($data, Http::STATUS_OK); + } } /** * @PublicPage + * @AnonRateThrottle(limit=5, period=1) * * @param string $username * @param string $displayname @@ -152,17 +159,23 @@ class ApiController extends OCSController { } else { $this->registrationService->generateNewToken($registration); $this->mailService->sendTokenByMail($registration); - throw new OCSException($this->l10n->t('There is already a pending registration with this email, a new verification email has been sent to the address.'), self::OCS_STATUS_RESENT); + return new DataResponse( + [ + 'registrationStatus' => self::REGISTRATION_STATUS_EXISTING, + 'message' => $this->l10n->t('There is already a pending registration with this email, a new verification email has been sent to the address.') + ], + Http::STATUS_OK + ); } $data['message'] = $this->l10n->t('Your registration is pending. Please confirm your email address.'); - $data['status'] = Registration::STATUS_PENDING; + $data['registrationStatus'] = self::REGISTRATION_STATUS_PENDING; if($secret !== null) { $data['secret'] = $secret; } return new DataResponse($data, Http::STATUS_OK); } catch (RegistrationException $exception) { - throw new OCSException($exception->getMessage()); + throw new OCSException($exception->getMessage(), $exception->getCode()); } } diff --git a/db/registration.php b/db/registration.php index 475a600..9d8473f 100644 --- a/db/registration.php +++ b/db/registration.php @@ -27,9 +27,6 @@ use OCP\AppFramework\Db\Entity; class Registration extends Entity { - const STATUS_FINISHED = 0; - const STATUS_PENDING = 1; - public $id; protected $email; protected $username; diff --git a/service/mailservice.php b/service/mailservice.php index 7122b21..bed408c 100644 --- a/service/mailservice.php +++ b/service/mailservice.php @@ -79,7 +79,6 @@ class MailService { * @throws RegistrationException */ public function sendTokenByMail(Registration $registration) { - return true; $link = $this->urlGenerator->linkToRoute('registration.register.verifyToken', array('token' => $registration->getToken())); $link = $this->urlGenerator->getAbsoluteURL($link); $template_var = [ diff --git a/service/registrationexception.php b/service/registrationexception.php index 1035811..b3070d1 100644 --- a/service/registrationexception.php +++ b/service/registrationexception.php @@ -32,9 +32,10 @@ class RegistrationException extends \Exception { * * @param string $message * @param string $hint + * @param int $code */ - public function __construct($message, $hint = "") { - parent::__construct($message); + public function __construct($message, $hint = "", $code = 400) { + parent::__construct($message, $code); $this->hint = $hint; } diff --git a/service/registrationservice.php b/service/registrationservice.php index 4131f7e..9da62bb 100644 --- a/service/registrationservice.php +++ b/service/registrationservice.php @@ -241,7 +241,7 @@ class RegistrationService { try { return $this->registrationMapper->findByToken($token); } catch (DoesNotExistException $exception) { - throw new RegistrationException($this->l10n->t('Invalid verification URL. No registration request with this verification URL is found.')); + throw new RegistrationException($this->l10n->t('Invalid verification URL. No registration request with this verification URL is found.', 404)); } } From ffcc23957e78002609c0e787ebcefdf0d2ed0efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Sat, 8 Jul 2017 14:27:51 +0200 Subject: [PATCH 7/7] Check if username is already used for a pending registration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- db/registrationmapper.php | 12 ++++++++++++ service/registrationservice.php | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/db/registrationmapper.php b/db/registrationmapper.php index 8ebd54c..0559af9 100644 --- a/db/registrationmapper.php +++ b/db/registrationmapper.php @@ -23,6 +23,7 @@ namespace OCA\Registration\Db; +use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\Mapper; use OCP\IDBConnection; @@ -48,7 +49,18 @@ class RegistrationMapper extends Mapper { public function findBySecret($secret) { return $this->findEntity('SELECT * FROM `*PREFIX*registration` WHERE `client_secret` = ? ', [$secret]); + } + public function usernameIsPending($username) { + try { + $entity = $this->findEntity( + 'SELECT id FROM `*PREFIX*registration` WHERE `username` = ? ', + [$username] + ); + } catch (DoesNotExistException $e) { + return false; + } + return true; } /** diff --git a/service/registrationservice.php b/service/registrationservice.php index 9da62bb..f9337c7 100644 --- a/service/registrationservice.php +++ b/service/registrationservice.php @@ -192,9 +192,13 @@ class RegistrationService { * @throws RegistrationException */ public function validateUsername($username) { - if($username === "" || $this->userManager->get($username) !== null) { + if($username === "") { throw new RegistrationException($this->l10n->t('Please provide a valid user name.')); } + + if($this->registrationMapper->usernameIsPending($username) || $this->userManager->get($username) !== null) { + throw new RegistrationException($this->l10n->t('The username you have chosen already exists.')); + } } /**