From bcce2625a8cb0b630d945c6849014049869e10ce Mon Sep 17 00:00:00 2001 From: Igor Scheller Date: Tue, 27 Nov 2018 12:01:36 +0100 Subject: [PATCH] Implemented AuthController for login * Moved /login functionality to AuthController * Refactored password handling logic to use the Authenticator --- config/config.default.php | 9 +- config/routes.php | 2 + .../2018_10_01_000000_create_users_tables.php | 2 +- includes/controller/users_controller.php | 13 +- includes/pages/admin_user.php | 2 +- includes/pages/guest_login.php | 119 +--------------- includes/pages/user_settings.php | 5 +- includes/sys_auth.php | 68 --------- includes/view/AngelTypes_view.php | 2 +- includes/view/User_view.php | 2 +- .../lang/de_DE.UTF-8/LC_MESSAGES/default.po | 19 +-- .../lang/en_US.UTF-8/LC_MESSAGES/default.mo | Bin 0 -> 745 bytes .../lang/en_US.UTF-8/LC_MESSAGES/default.po | 26 ++++ .../lang/pt_BR.UTF.8/LC_MESSAGES/default.mo | Bin 0 -> 41129 bytes .../LC_MESSAGES/{pt_BR.po => default.po} | 14 +- .../lang/pt_BR.UTF.8/LC_MESSAGES/pt_BR.mo | Bin 41256 -> 0 bytes resources/views/errors/405.twig | 5 + resources/views/macros/base.twig | 11 ++ resources/views/pages/login.twig | 104 ++++++++++++++ src/Controllers/AuthController.php | 90 +++++++++++- src/Helpers/Authenticator.php | 93 ++++++++++-- src/Helpers/AuthenticatorServiceProvider.php | 4 + src/Middleware/LegacyMiddleware.php | 5 - tests/Unit/Controllers/AuthControllerTest.php | 132 ++++++++++++++++-- .../Stub/ControllerImplementation.php | 8 -- .../AuthenticatorServiceProviderTest.php | 9 ++ tests/Unit/Helpers/AuthenticatorTest.php | 125 ++++++++++++++++- .../Http/UrlGeneratorServiceProviderTest.php | 5 +- 28 files changed, 610 insertions(+), 264 deletions(-) create mode 100644 resources/lang/en_US.UTF-8/LC_MESSAGES/default.mo create mode 100644 resources/lang/en_US.UTF-8/LC_MESSAGES/default.po create mode 100644 resources/lang/pt_BR.UTF.8/LC_MESSAGES/default.mo rename resources/lang/pt_BR.UTF.8/LC_MESSAGES/{pt_BR.po => default.po} (99%) delete mode 100644 resources/lang/pt_BR.UTF.8/LC_MESSAGES/pt_BR.mo create mode 100644 resources/views/errors/405.twig create mode 100644 resources/views/macros/base.twig create mode 100644 resources/views/pages/login.twig diff --git a/config/config.default.php b/config/config.default.php index 693b0d19..9c9505c6 100644 --- a/config/config.default.php +++ b/config/config.default.php @@ -95,13 +95,10 @@ return [ // Number of hours that an angel has to sign out own shifts 'last_unsubscribe' => 3, - // Define the algorithm to use for `crypt()` of passwords + // Define the algorithm to use for `password_verify()` // If the user uses an old algorithm the password will be converted to the new format - // MD5 '$1' - // Blowfish '$2y$13' - // SHA-256 '$5$rounds=5000' - // SHA-512 '$6$rounds=5000' - 'crypt_alg' => '$6$rounds=5000', + // See https://secure.php.net/manual/en/password.constants.php for a complete list + 'password_algorithm' => PASSWORD_DEFAULT, // The minimum length for passwords 'min_password_length' => 8, diff --git a/config/routes.php b/config/routes.php index e999d026..02fd3abd 100644 --- a/config/routes.php +++ b/config/routes.php @@ -9,6 +9,8 @@ $route->get('/', 'HomeController@index'); $route->get('/credits', 'CreditsController@index'); // Authentication +$route->get('/login', 'AuthController@login'); +$route->post('/login', 'AuthController@postLogin'); $route->get('/logout', 'AuthController@logout'); // Stats diff --git a/db/migrations/2018_10_01_000000_create_users_tables.php b/db/migrations/2018_10_01_000000_create_users_tables.php index d8422ca0..52b3658f 100644 --- a/db/migrations/2018_10_01_000000_create_users_tables.php +++ b/db/migrations/2018_10_01_000000_create_users_tables.php @@ -28,7 +28,7 @@ class CreateUsersTables extends Migration $table->string('name', 24)->unique(); $table->string('email', 254)->unique(); - $table->string('password', 128); + $table->string('password', 255); $table->string('api_key', 32); $table->dateTime('last_login_at')->nullable(); diff --git a/includes/controller/users_controller.php b/includes/controller/users_controller.php index 7c6bde02..214998dc 100644 --- a/includes/controller/users_controller.php +++ b/includes/controller/users_controller.php @@ -47,6 +47,7 @@ function users_controller() function user_delete_controller() { $user = auth()->user(); + $auth = auth(); $request = request(); if ($request->has('user_id')) { @@ -68,14 +69,12 @@ function user_delete_controller() if ($request->hasPostData('submit')) { $valid = true; - if ( - !( + if (!( $request->has('password') - && verify_password($request->postData('password'), $user->password, $user->id) - ) - ) { + && $auth->verifyPassword($user, $request->postData('password')) + )) { $valid = false; - error(__('Your password is incorrect. Please try it again.')); + error(__('Your password is incorrect. Please try it again.')); } if ($valid) { @@ -341,7 +340,7 @@ function user_password_recovery_set_new_controller() } if ($valid) { - set_password($passwordReset->user->id, $request->postData('password')); + auth()->setPassword($passwordReset->user, $request->postData('password')); success(__('Password saved.')); $passwordReset->delete(); redirect(page_link_to('login')); diff --git a/includes/pages/admin_user.php b/includes/pages/admin_user.php index e6f94180..8482dea5 100644 --- a/includes/pages/admin_user.php +++ b/includes/pages/admin_user.php @@ -291,8 +291,8 @@ function admin_user() $request->postData('new_pw') != '' && $request->postData('new_pw') == $request->postData('new_pw2') ) { - set_password($user_id, $request->postData('new_pw')); $user_source = User::find($user_id); + auth()->setPassword($user_source, $request->postData('new_pw')); engelsystem_log('Set new password for ' . User_Nick_render($user_source, true)); $html .= success('Passwort neu gesetzt.', true); } else { diff --git a/includes/pages/guest_login.php b/includes/pages/guest_login.php index d152a092..3bc10fc3 100644 --- a/includes/pages/guest_login.php +++ b/includes/pages/guest_login.php @@ -8,14 +8,6 @@ use Engelsystem\Models\User\Settings; use Engelsystem\Models\User\State; use Engelsystem\Models\User\User; -/** - * @return string - */ -function login_title() -{ - return __('Login'); -} - /** * @return string */ @@ -226,7 +218,7 @@ function guest_register() // Assign user-group and set password DB::insert('INSERT INTO `UserGroups` (`uid`, `group_id`) VALUES (?, -20)', [$user->id]); - set_password($user->id, $request->postData('password')); + auth()->setPassword($user, $request->postData('password')); // Assign angel-types $user_angel_types_info = []; @@ -369,112 +361,3 @@ function entry_required() { return ''; } - -/** - * @return string - */ -function guest_login() -{ - $nick = ''; - $request = request(); - $session = session(); - $valid = true; - - $session->remove('uid'); - - if ($request->hasPostData('submit')) { - if ($request->has('nick') && !empty($request->input('nick'))) { - $nickValidation = User_validate_Nick($request->input('nick')); - $nick = $nickValidation->getValue(); - $login_user = User::whereName($nickValidation->getValue())->first(); - if ($login_user) { - if ($request->has('password')) { - if (!verify_password($request->postData('password'), $login_user->password, $login_user->id)) { - $valid = false; - error(__('Your password is incorrect. Please try it again.')); - } - } else { - $valid = false; - error(__('Please enter a password.')); - } - } else { - $valid = false; - error(__('No user was found with that Nickname. Please try again. If you are still having problems, ask a Dispatcher.')); - } - } else { - $valid = false; - error(__('Please enter a nickname.')); - } - - if ($valid && $login_user) { - $session->set('uid', $login_user->id); - $session->set('locale', $login_user->settings->language); - - redirect(page_link_to(config('home_site'))); - } - } - - return page([ - div('col-md-12', [ - div('row', [ - EventConfig_countdown_page() - ]), - div('row', [ - div('col-sm-6 col-sm-offset-3 col-md-4 col-md-offset-4', [ - div('panel panel-primary first', [ - div('panel-heading', [ - ' ' . __('Login') - ]), - div('panel-body', [ - msg(), - form([ - form_text_placeholder('nick', __('Nick'), $nick), - form_password_placeholder('password', __('Password')), - form_submit('submit', __('Login')), - !$valid ? buttons([ - button(page_link_to('user_password_recovery'), __('I forgot my password')) - ]) : '' - ]) - ]), - div('panel-footer', [ - glyph('info-sign') . __('Please note: You have to activate cookies!') - ]) - ]) - ]) - ]), - div('row', [ - div('col-sm-6 text-center', [ - heading(register_title(), 2), - get_register_hint() - ]), - div('col-sm-6 text-center', [ - heading(__('What can I do?'), 2), - '

' . __('Please read about the jobs you can do to help us.') . '

', - buttons([ - button( - page_link_to('angeltypes', ['action' => 'about']), - __('Teams/Job description') . ' »' - ) - ]) - ]) - ]) - ]) - ]); -} - -/** - * @return string - */ -function get_register_hint() -{ - if (auth()->can('register') && config('registration_enabled')) { - return join('', [ - '

' . __('Please sign up, if you want to help us!') . '

', - buttons([ - button(page_link_to('register'), register_title() . ' »') - ]) - ]); - } - - return error(__('Registration is disabled.'), true); -} diff --git a/includes/pages/user_settings.php b/includes/pages/user_settings.php index ae29e4d8..f6853191 100644 --- a/includes/pages/user_settings.php +++ b/includes/pages/user_settings.php @@ -101,9 +101,10 @@ function user_settings_main($user_source, $enable_tshirt_size, $tshirt_sizes) function user_settings_password($user_source) { $request = request(); + $auth = auth(); if ( !$request->has('password') - || !verify_password($request->postData('password'), $user_source->password, $user_source->id) + || !$auth->verifyPassword($user_source, $request->postData('password')) ) { error(__('-> not OK. Please try again.')); } elseif (strlen($request->postData('new_password')) < config('min_password_length')) { @@ -111,7 +112,7 @@ function user_settings_password($user_source) } elseif ($request->postData('new_password') != $request->postData('new_password2')) { error(__('Your passwords don\'t match.')); } else { - set_password($user_source->id, $request->postData('new_password')); + $auth->setPassword($user_source, $request->postData('new_password')); success(__('Password saved.')); } redirect(page_link_to('user_settings')); diff --git a/includes/sys_auth.php b/includes/sys_auth.php index 520b13eb..f0485495 100644 --- a/includes/sys_auth.php +++ b/includes/sys_auth.php @@ -1,74 +1,6 @@ password = crypt($password, config('crypt_alg') . '$' . generate_salt(16) . '$'); - $user->save(); -} - -/** - * verify a password given a precomputed salt. - * if $uid is given and $salt is an old-style salt (plain md5), we convert it automatically - * - * @param string $password - * @param string $salt - * @param int $uid - * @return bool - */ -function verify_password($password, $salt, $uid = null) -{ - $crypt_alg = config('crypt_alg'); - $correct = false; - if (substr($salt, 0, 1) == '$') { - // new-style crypt() - $correct = crypt($password, $salt) == $salt; - } elseif (substr($salt, 0, 7) == '{crypt}') { - // old-style crypt() with DES and static salt - not used anymore - $correct = crypt($password, '77') == $salt; - } elseif (strlen($salt) == 32) { - // old-style md5 without salt - not used anymore - $correct = md5($password) == $salt; - } - - if ($correct && substr($salt, 0, strlen($crypt_alg)) != $crypt_alg && intval($uid)) { - // this password is stored in another format than we want it to be. - // let's update it! - // we duplicate the query from the above set_password() function to have the extra safety of checking - // the old hash - $user = User::find($uid); - if ($user->password == $salt) { - $user->password = crypt($password, $crypt_alg . '$' . generate_salt() . '$'); - $user->save(); - } - } - return $correct; -} /** * @param int $user_id diff --git a/includes/view/AngelTypes_view.php b/includes/view/AngelTypes_view.php index f5434e8f..9f9bd736 100644 --- a/includes/view/AngelTypes_view.php +++ b/includes/view/AngelTypes_view.php @@ -578,7 +578,7 @@ function AngelTypes_about_view($angeltypes, $user_logged_in) $buttons[] = button(page_link_to('register'), register_title()); } - $buttons[] = button(page_link_to('login'), login_title()); + $buttons[] = button(page_link_to('login'), __('Login')); } $faqUrl = config('faq_url'); diff --git a/includes/view/User_view.php b/includes/view/User_view.php index 949bba87..21be0c9f 100644 --- a/includes/view/User_view.php +++ b/includes/view/User_view.php @@ -126,7 +126,7 @@ function User_registration_success_view($event_welcome_message) div('col-md-4', [ '

' . __('Login') . '

', form([ - form_text('nick', __('Nick'), ''), + form_text('login', __('Nick'), ''), form_password('password', __('Password')), form_submit('submit', __('Login')), buttons([ diff --git a/resources/lang/de_DE.UTF-8/LC_MESSAGES/default.po b/resources/lang/de_DE.UTF-8/LC_MESSAGES/default.po index d5a7b993..27ceb586 100644 --- a/resources/lang/de_DE.UTF-8/LC_MESSAGES/default.po +++ b/resources/lang/de_DE.UTF-8/LC_MESSAGES/default.po @@ -541,7 +541,7 @@ msgstr "Du kannst Dich nicht selber löschen." #: includes/controller/users_controller.php:78 #: includes/pages/guest_login.php:410 -msgid "Your password is incorrect. Please try it again." +msgid "Your password is incorrect. Please try it again." msgstr "Dein Passwort stimmt nicht. Bitte probiere es nochmal." #: includes/controller/users_controller.php:87 @@ -1530,18 +1530,21 @@ msgid "Entry required!" msgstr "Pflichtfeld!" #: includes/pages/guest_login.php:414 -msgid "Please enter a password." +msgid "auth.no-password" msgstr "Gib bitte ein Passwort ein." #: includes/pages/guest_login.php:418 -msgid "" -"No user was found with that Nickname. Please try again. If you are still " -"having problems, ask a Dispatcher." +msgid "auth.not-found" msgstr "" -"Es wurde kein Engel mit diesem Namen gefunden. Probiere es bitte noch " -"einmal. Wenn das Problem weiterhin besteht, frage einen Dispatcher." +"Es wurde kein Engel gefunden. Probiere es bitte noch einmal. Wenn das Problem " +"weiterhin besteht, melde dich im Himmel." #: includes/pages/guest_login.php:451 includes/view/User_view.php:130 +msgid "auth.no-nickname" +msgstr "Gib bitte einen Nick an." + +#: includes/pages/guest_login.php:481 +#: includes/view/User_view.php:122 msgid "I forgot my password" msgstr "Passwort vergessen" @@ -2357,7 +2360,7 @@ msgid "" "I have my own car with me and am willing to use it for the event (You'll get " "reimbursed for fuel)" msgstr "" -"Ich habe mein eigenes Auto dabei und möchte würde es zum Fahren für das " +"Ich habe mein eigenes Auto dabei und möchte es zum Fahren für das " "Event verwenden (Du wirst für Spritkosten entschädigt)" #: includes/view/UserDriverLicenses_view.php:30 diff --git a/resources/lang/en_US.UTF-8/LC_MESSAGES/default.mo b/resources/lang/en_US.UTF-8/LC_MESSAGES/default.mo new file mode 100644 index 0000000000000000000000000000000000000000..e95ae7038db0dddb2327828cd145df1902bccfaf GIT binary patch literal 745 zcmZ8f&2G~`5H?UQIUvM|1Bc-Tf;C=Wnr_IYA%%z(tTsg)kfNzQaZH_Ev%5~ofeWvI z-~o6OUW8|1T-O*e(x-1{=i8m1-QV~2z6Wf3j0cS8jN6P4jK(U)UB;(t{>prZ@s088 zMiBgDzR$>Rw)}o&YtnObK- zV$DZNy_jX<9a&cxtzfEiD&5X`+CUsegXHZ(Oe~@2sCa>%6vUC-7cv&{0muUt$tRJF z8lgC$ZPDD)>xM!~5${73sd(7x=BV=;a}o=}je^1P?0DLzmz;89v?uMwVpnlCPoMp> zs>DK%AYG$%tkAiF;d$W)@5M{fWYHU|ATg8`9%MKSrQ}fS zi`TSkCgbLX^9q)uoP3k8eYwns6xM15Dt>EwpfZIV>eELuC81+jz`cg$B#5T z9D{s3>d&8=+NLyVyHL=!F-OO}Ha(PWse^7r3P>-{PX9SiXw!AV6^N**AxKK%l33=+ z0z&ul;s1f|2ZX^J1GQuEB|5nJ1tDBt;_VJF=Q)QkTfxrMfR@1dqAX#JTb?Ua)|{8L PaZdr=1-v5Mk`9AE#iiL9 literal 0 HcmV?d00001 diff --git a/resources/lang/en_US.UTF-8/LC_MESSAGES/default.po b/resources/lang/en_US.UTF-8/LC_MESSAGES/default.po new file mode 100644 index 00000000..22566e52 --- /dev/null +++ b/resources/lang/en_US.UTF-8/LC_MESSAGES/default.po @@ -0,0 +1,26 @@ +msgid "" +msgstr "" +"Project-Id-Version: Engelsystem 2.0\n" +"POT-Creation-Date: 2017-12-29 19:01+0100\n" +"PO-Revision-Date: 2018-11-27 00:28+0100\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.8.11\n" +"X-Poedit-KeywordsList: _;gettext;gettext_noop\n" +"X-Poedit-Basepath: .\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-SourceCharset: UTF-8\n" +"Last-Translator: \n" +"Language: en_US\n" +"X-Poedit-SearchPath-0: .\n" + +msgid "auth.no-nickname" +msgstr "Please enter a nickname." + +msgid "auth.no-password" +msgstr "Please enter a password." + +msgid "auth.not-found" +msgstr "No user was found. Please try again. If you are still having problems, ask Heaven." diff --git a/resources/lang/pt_BR.UTF.8/LC_MESSAGES/default.mo b/resources/lang/pt_BR.UTF.8/LC_MESSAGES/default.mo new file mode 100644 index 0000000000000000000000000000000000000000..8b8641565705f92b5dbcf78956dbb887cd62b6bb GIT binary patch literal 41129 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 UT)6FvIMERxof*)@;U?k#0R=vo82|tP literal 0 HcmV?d00001 diff --git a/resources/lang/pt_BR.UTF.8/LC_MESSAGES/pt_BR.po b/resources/lang/pt_BR.UTF.8/LC_MESSAGES/default.po similarity index 99% rename from resources/lang/pt_BR.UTF.8/LC_MESSAGES/pt_BR.po rename to resources/lang/pt_BR.UTF.8/LC_MESSAGES/default.po index e7307e5d..b9bf420d 100644 --- a/resources/lang/pt_BR.UTF.8/LC_MESSAGES/pt_BR.po +++ b/resources/lang/pt_BR.UTF.8/LC_MESSAGES/default.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: Engelsystem 2.0\n" "POT-Creation-Date: 2017-04-25 05:23+0200\n" -"PO-Revision-Date: 2018-10-05 15:35+0200\n" +"PO-Revision-Date: 2018-11-27 00:29+0100\n" "Last-Translator: samba \n" "Language-Team: \n" "Language: pt_BR\n" @@ -477,7 +477,7 @@ msgstr "Você não pode se deletar." #: includes/controller/users_controller.php:61 #: includes/pages/guest_login.php:315 -msgid "Your password is incorrect. Please try it again." +msgid "Your password is incorrect. Please try it again." msgstr "Sua senha está incorreta. Por favor, tente novamente." #: includes/controller/users_controller.php:71 @@ -1420,19 +1420,17 @@ msgid "Entry required!" msgstr "Campo necessário!" #: includes/pages/guest_login.php:319 -msgid "Please enter a password." +msgid "auth.no-password" msgstr "Por favor digite uma senha." #: includes/pages/guest_login.php:323 -msgid "" -"No user was found with that Nickname. Please try again. If you are still " -"having problems, ask a Dispatcher." +msgid "auth.not-found" msgstr "" -"Nenhum usuário com esse apelido foi encontrado. Por favor tente novamente. \n" +"Nenhum usuário foi encontrado. Por favor tente novamente. \n" "Se você continuar com problemas, pergunte a um Dispatcher." #: includes/pages/guest_login.php:327 -msgid "Please enter a nickname." +msgid "auth.no-nickname" msgstr "Por favor digite um apelido." #: includes/pages/guest_login.php:358 includes/view/User_view.php:101 diff --git a/resources/lang/pt_BR.UTF.8/LC_MESSAGES/pt_BR.mo b/resources/lang/pt_BR.UTF.8/LC_MESSAGES/pt_BR.mo deleted file mode 100644 index 95251feb85fead203c8e17739e7b2a139d008363..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41256 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 HChq?MsA +{% endmacro %} + +{% macro glyphicon(glyph) %} + +{% endmacro %} + +{% macro alert(message, type) %} +
{{ message }}
+{% endmacro %} diff --git a/resources/views/pages/login.twig b/resources/views/pages/login.twig new file mode 100644 index 00000000..75b98aa1 --- /dev/null +++ b/resources/views/pages/login.twig @@ -0,0 +1,104 @@ +{% extends "layouts/app.twig" %} +{% import 'macros/base.twig' as m %} + +{% block title %}{{ __('Login') }}{% endblock %} + +{% block content %} +
+{% endblock %} diff --git a/src/Controllers/AuthController.php b/src/Controllers/AuthController.php index cdaee167..e5fc40e3 100644 --- a/src/Controllers/AuthController.php +++ b/src/Controllers/AuthController.php @@ -2,8 +2,12 @@ namespace Engelsystem\Controllers; +use Carbon\Carbon; +use Engelsystem\Helpers\Authenticator; +use Engelsystem\Http\Request; use Engelsystem\Http\Response; use Engelsystem\Http\UrlGeneratorInterface; +use Engelsystem\Models\User\User; use Symfony\Component\HttpFoundation\Session\SessionInterface; class AuthController extends BaseController @@ -17,20 +21,100 @@ class AuthController extends BaseController /** @var UrlGeneratorInterface */ protected $url; - public function __construct(Response $response, SessionInterface $session, UrlGeneratorInterface $url) - { + /** @var Authenticator */ + protected $auth; + + /** @var array */ + protected $permissions = [ + 'login' => 'login', + 'postLogin' => 'login', + ]; + + /** + * @param Response $response + * @param SessionInterface $session + * @param UrlGeneratorInterface $url + * @param Authenticator $auth + */ + public function __construct( + Response $response, + SessionInterface $session, + UrlGeneratorInterface $url, + Authenticator $auth + ) { $this->response = $response; $this->session = $session; $this->url = $url; + $this->auth = $auth; } /** * @return Response */ - public function logout() + public function login() + { + return $this->response->withView('pages/login'); + } + + /** + * Posted login form + * + * @param Request $request + * @return Response + */ + public function postLogin(Request $request): Response + { + $return = $this->authenticateUser($request->get('login', ''), $request->get('password', '')); + if (!$return instanceof User) { + return $this->response->withView( + 'pages/login', + ['errors' => [$return], 'show_password_recovery' => true] + ); + } + + $user = $return; + + $this->session->invalidate(); + $this->session->set('user_id', $user->id); + $this->session->set('locale', $user->settings->language); + + $user->last_login_at = new Carbon(); + $user->save(['touch' => false]); + + return $this->response->redirectTo('news'); + } + + /** + * @return Response + */ + public function logout(): Response { $this->session->invalidate(); return $this->response->redirectTo($this->url->to('/')); } + + /** + * Verify the user and password + * + * @param $login + * @param $password + * @return User|string + */ + protected function authenticateUser(string $login, string $password) + { + if (!$login) { + return 'auth.no-nickname'; + } + + if (!$password) { + return 'auth.no-password'; + } + + if (!$user = $this->auth->authenticate($login, $password)) { + return 'auth.not-found'; + } + + return $user; + } } diff --git a/src/Helpers/Authenticator.php b/src/Helpers/Authenticator.php index 61d07980..db33339b 100644 --- a/src/Helpers/Authenticator.php +++ b/src/Helpers/Authenticator.php @@ -25,6 +25,9 @@ class Authenticator /** @var string[] */ protected $permissions; + /** @var int */ + protected $passwordAlgorithm = PASSWORD_DEFAULT; + /** * @param ServerRequestInterface $request * @param Session $session @@ -48,7 +51,7 @@ class Authenticator return $this->user; } - $userId = $this->session->get('uid'); + $userId = $this->session->get('user_id'); if (!$userId) { return null; } @@ -104,17 +107,15 @@ class Authenticator $abilities = (array)$abilities; if (empty($this->permissions)) { - $userId = $this->user ? $this->user->id : $this->session->get('uid'); + $user = $this->user(); - if ($userId) { - if ($user = $this->user()) { - $this->permissions = $this->getPermissionsByUser($user); + if ($user) { + $this->permissions = $this->getPermissionsByUser($user); - $user->last_login_at = new Carbon(); - $user->save(); - } else { - $this->session->remove('uid'); - } + $user->last_login_at = new Carbon(); + $user->save(); + } elseif ($this->session->get('user_id')) { + $this->session->remove('user_id'); } if (empty($this->permissions)) { @@ -131,6 +132,78 @@ class Authenticator return true; } + /** + * @param string $login + * @param string $password + * @return User|null + */ + public function authenticate(string $login, string $password) + { + /** @var User $user */ + $user = $this->userRepository->whereName($login)->first(); + if (!$user) { + $user = $this->userRepository->whereEmail($login)->first(); + } + + if (!$user) { + return null; + } + + if (!$this->verifyPassword($user, $password)) { + return null; + } + + return $user; + } + + /** + * @param User $user + * @param string $password + * @return bool + */ + public function verifyPassword(User $user, string $password) + { + $algorithm = $this->passwordAlgorithm; + + if (!password_verify($password, $user->password)) { + return false; + } + + if (password_needs_rehash($user->password, $algorithm)) { + $this->setPassword($user, $password); + } + + return true; + } + + /** + * @param UserRepository $user + * @param string $password + */ + public function setPassword(User $user, string $password) + { + $algorithm = $this->passwordAlgorithm; + + $user->password = password_hash($password, $algorithm); + $user->save(); + } + + /** + * @return int + */ + public function getPasswordAlgorithm() + { + return $this->passwordAlgorithm; + } + + /** + * @param int $passwordAlgorithm + */ + public function setPasswordAlgorithm(int $passwordAlgorithm) + { + $this->passwordAlgorithm = $passwordAlgorithm; + } + /** * @param User $user * @return array diff --git a/src/Helpers/AuthenticatorServiceProvider.php b/src/Helpers/AuthenticatorServiceProvider.php index 715a592f..f06e635d 100644 --- a/src/Helpers/AuthenticatorServiceProvider.php +++ b/src/Helpers/AuthenticatorServiceProvider.php @@ -2,14 +2,18 @@ namespace Engelsystem\Helpers; +use Engelsystem\Config\Config; use Engelsystem\Container\ServiceProvider; class AuthenticatorServiceProvider extends ServiceProvider { public function register() { + /** @var Config $config */ + $config = $this->app->get('config'); /** @var Authenticator $authenticator */ $authenticator = $this->app->make(Authenticator::class); + $authenticator->setPasswordAlgorithm($config->get('password_algorithm')); $this->app->instance(Authenticator::class, $authenticator); $this->app->instance('authenticator', $authenticator); diff --git a/src/Middleware/LegacyMiddleware.php b/src/Middleware/LegacyMiddleware.php index af2c6a70..7adcc88d 100644 --- a/src/Middleware/LegacyMiddleware.php +++ b/src/Middleware/LegacyMiddleware.php @@ -19,7 +19,6 @@ class LegacyMiddleware implements MiddlewareInterface 'angeltypes', 'atom', 'ical', - 'login', 'public_dashboard', 'rooms', 'shift_entries', @@ -175,10 +174,6 @@ class LegacyMiddleware implements MiddlewareInterface $title = settings_title(); $content = user_settings(); return [$title, $content]; - case 'login': - $title = login_title(); - $content = guest_login(); - return [$title, $content]; case 'register': $title = register_title(); $content = guest_register(); diff --git a/tests/Unit/Controllers/AuthControllerTest.php b/tests/Unit/Controllers/AuthControllerTest.php index c5349cda..0fad3b6d 100644 --- a/tests/Unit/Controllers/AuthControllerTest.php +++ b/tests/Unit/Controllers/AuthControllerTest.php @@ -3,40 +3,154 @@ namespace Engelsystem\Test\Unit\Controllers; use Engelsystem\Controllers\AuthController; +use Engelsystem\Helpers\Authenticator; +use Engelsystem\Http\Request; use Engelsystem\Http\Response; use Engelsystem\Http\UrlGeneratorInterface; +use Engelsystem\Models\User\Settings; +use Engelsystem\Models\User\User; +use Engelsystem\Test\Unit\HasDatabase; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\SessionInterface; class AuthControllerTest extends TestCase { + use HasDatabase; + /** * @covers \Engelsystem\Controllers\AuthController::__construct - * @covers \Engelsystem\Controllers\AuthController::logout + * @covers \Engelsystem\Controllers\AuthController::login */ - public function testLogout() + public function testLogin() { /** @var Response|MockObject $response */ $response = $this->createMock(Response::class); /** @var SessionInterface|MockObject $session */ - $session = $this->getMockForAbstractClass(SessionInterface::class); /** @var UrlGeneratorInterface|MockObject $url */ - $url = $this->getMockForAbstractClass(UrlGeneratorInterface::class); + /** @var Authenticator|MockObject $auth */ + list(, $session, $url, $auth) = $this->getMocks(); + + $response->expects($this->once()) + ->method('withView') + ->with('pages/login') + ->willReturn($response); + + $controller = new AuthController($response, $session, $url, $auth); + $controller->login(); + } + + /** + * @covers \Engelsystem\Controllers\AuthController::postLogin + * @covers \Engelsystem\Controllers\AuthController::authenticateUser + */ + public function testPostLogin() + { + $this->initDatabase(); + + $request = new Request(); + /** @var Response|MockObject $response */ + $response = $this->createMock(Response::class); + /** @var SessionInterface|MockObject $session */ + /** @var UrlGeneratorInterface|MockObject $url */ + /** @var Authenticator|MockObject $auth */ + list(, $session, $url, $auth) = $this->getMocks(); + + $user = new User([ + 'name' => 'foo', + 'password' => '', + 'email' => '', + 'api_key' => '', + 'last_login_at' => null, + ]); + $user->forceFill(['id' => 42,]); + $user->save(); + + $settings = new Settings(['language' => 'de_DE', 'theme' => '']); + $settings->user() + ->associate($user) + ->save(); + + $auth->expects($this->exactly(2)) + ->method('authenticate') + ->with('foo', 'bar') + ->willReturnOnConsecutiveCalls(null, $user); + + $response->expects($this->exactly(3)) + ->method('withView') + ->withConsecutive( + ['pages/login', ['errors' => ['auth.no-nickname'], 'show_password_recovery' => true]], + ['pages/login', ['errors' => ['auth.no-password'], 'show_password_recovery' => true]], + ['pages/login', ['errors' => ['auth.not-found'], 'show_password_recovery' => true]]) + ->willReturn($response); + $response->expects($this->once()) + ->method('redirectTo') + ->with('news') + ->willReturn($response); $session->expects($this->once()) ->method('invalidate'); - $response->expects($this->once()) - ->method('redirectTo') - ->with('https://foo.bar/'); + $session->expects($this->exactly(2)) + ->method('set') + ->withConsecutive( + ['user_id', 42], + ['locale', 'de_DE'] + ); + + $controller = new AuthController($response, $session, $url, $auth); + $controller->postLogin($request); + + $request = new Request(['login' => 'foo']); + $controller->postLogin($request); + + $request = new Request(['login' => 'foo', 'password' => 'bar']); + // No user found + $controller->postLogin($request); + // Authenticated user + $controller->postLogin($request); + + $this->assertNotNull($user->last_login_at); + } + + /** + * @covers \Engelsystem\Controllers\AuthController::logout + */ + public function testLogout() + { + /** @var Response $response */ + /** @var SessionInterface|MockObject $session */ + /** @var UrlGeneratorInterface|MockObject $url */ + /** @var Authenticator|MockObject $auth */ + list($response, $session, $url, $auth) = $this->getMocks(); + + $session->expects($this->once()) + ->method('invalidate'); $url->expects($this->once()) ->method('to') ->with('/') ->willReturn('https://foo.bar/'); - $controller = new AuthController($response, $session, $url); - $controller->logout(); + $controller = new AuthController($response, $session, $url, $auth); + $return = $controller->logout(); + + $this->assertEquals(['https://foo.bar/'], $return->getHeader('location')); + } + + /** + * @return array + */ + protected function getMocks() + { + $response = new Response(); + /** @var SessionInterface|MockObject $session */ + $session = $this->getMockForAbstractClass(SessionInterface::class); + /** @var UrlGeneratorInterface|MockObject $url */ + $url = $this->getMockForAbstractClass(UrlGeneratorInterface::class); + /** @var Authenticator|MockObject $auth */ + $auth = $this->createMock(Authenticator::class); + + return [$response, $session, $url, $auth]; } } diff --git a/tests/Unit/Controllers/Stub/ControllerImplementation.php b/tests/Unit/Controllers/Stub/ControllerImplementation.php index 01d9f250..a8bf538c 100644 --- a/tests/Unit/Controllers/Stub/ControllerImplementation.php +++ b/tests/Unit/Controllers/Stub/ControllerImplementation.php @@ -14,12 +14,4 @@ class ControllerImplementation extends BaseController 'dolor', ], ]; - - /** - * @param array $permissions - */ - public function setPermissions(array $permissions) - { - $this->permissions = $permissions; - } } diff --git a/tests/Unit/Helpers/AuthenticatorServiceProviderTest.php b/tests/Unit/Helpers/AuthenticatorServiceProviderTest.php index b1767ebc..ab9b23ec 100644 --- a/tests/Unit/Helpers/AuthenticatorServiceProviderTest.php +++ b/tests/Unit/Helpers/AuthenticatorServiceProviderTest.php @@ -3,6 +3,7 @@ namespace Engelsystem\Test\Unit\Helpers; use Engelsystem\Application; +use Engelsystem\Config\Config; use Engelsystem\Helpers\Authenticator; use Engelsystem\Helpers\AuthenticatorServiceProvider; use Engelsystem\Http\Request; @@ -19,11 +20,19 @@ class AuthenticatorServiceProviderTest extends ServiceProviderTest $app = new Application(); $app->bind(ServerRequestInterface::class, Request::class); + $config = new Config(); + $config->set('password_algorithm', PASSWORD_DEFAULT); + $app->instance('config', $config); + $serviceProvider = new AuthenticatorServiceProvider($app); $serviceProvider->register(); $this->assertInstanceOf(Authenticator::class, $app->get(Authenticator::class)); $this->assertInstanceOf(Authenticator::class, $app->get('authenticator')); $this->assertInstanceOf(Authenticator::class, $app->get('auth')); + + /** @var Authenticator $auth */ + $auth = $app->get(Authenticator::class); + $this->assertEquals(PASSWORD_DEFAULT, $auth->getPasswordAlgorithm()); } } diff --git a/tests/Unit/Helpers/AuthenticatorTest.php b/tests/Unit/Helpers/AuthenticatorTest.php index 400278f2..83dc72ad 100644 --- a/tests/Unit/Helpers/AuthenticatorTest.php +++ b/tests/Unit/Helpers/AuthenticatorTest.php @@ -4,6 +4,7 @@ namespace Engelsystem\Test\Unit\Helpers; use Engelsystem\Helpers\Authenticator; use Engelsystem\Models\User\User; +use Engelsystem\Test\Unit\HasDatabase; use Engelsystem\Test\Unit\Helpers\Stub\UserModelImplementation; use Engelsystem\Test\Unit\ServiceProviderTest; use PHPUnit\Framework\MockObject\MockObject; @@ -12,6 +13,8 @@ use Symfony\Component\HttpFoundation\Session\Session; class AuthenticatorTest extends ServiceProviderTest { + use HasDatabase; + /** * @covers \Engelsystem\Helpers\Authenticator::__construct( * @covers \Engelsystem\Helpers\Authenticator::user @@ -29,7 +32,7 @@ class AuthenticatorTest extends ServiceProviderTest $session->expects($this->exactly(3)) ->method('get') - ->with('uid') + ->with('user_id') ->willReturnOnConsecutiveCalls( null, 42, @@ -114,16 +117,13 @@ class AuthenticatorTest extends ServiceProviderTest /** @var User|MockObject $user */ $user = $this->createMock(User::class); - $user->expects($this->once()) - ->method('save'); - - $session->expects($this->exactly(2)) + $session->expects($this->once()) ->method('get') - ->with('uid') + ->with('user_id') ->willReturn(42); $session->expects($this->once()) ->method('remove') - ->with('uid'); + ->with('user_id'); /** @var Authenticator|MockObject $auth */ $auth = $this->getMockBuilder(Authenticator::class) @@ -151,4 +151,115 @@ class AuthenticatorTest extends ServiceProviderTest // Permissions cached $this->assertTrue($auth->can('bar')); } + + /** + * @covers \Engelsystem\Helpers\Authenticator::authenticate + */ + public function testAuthenticate() + { + $this->initDatabase(); + + /** @var ServerRequestInterface|MockObject $request */ + $request = $this->getMockForAbstractClass(ServerRequestInterface::class); + /** @var Session|MockObject $session */ + $session = $this->createMock(Session::class); + $userRepository = new User(); + + (new User([ + 'name' => 'lorem', + 'password' => password_hash('testing', PASSWORD_DEFAULT), + 'email' => 'lorem@foo.bar', + 'api_key' => '', + ]))->save(); + (new User([ + 'name' => 'ipsum', + 'password' => '', + 'email' => 'ipsum@foo.bar', + 'api_key' => '', + ]))->save(); + + $auth = new Authenticator($request, $session, $userRepository); + $this->assertNull($auth->authenticate('not-existing', 'foo')); + $this->assertNull($auth->authenticate('ipsum', 'wrong-password')); + $this->assertInstanceOf(User::class, $auth->authenticate('lorem', 'testing')); + $this->assertInstanceOf(User::class, $auth->authenticate('lorem@foo.bar', 'testing')); + } + + /** + * @covers \Engelsystem\Helpers\Authenticator::verifyPassword + */ + public function testVerifyPassword() + { + $this->initDatabase(); + $password = password_hash('testing', PASSWORD_ARGON2I); + $user = new User([ + 'name' => 'lorem', + 'password' => $password, + 'email' => 'lorem@foo.bar', + 'api_key' => '', + ]); + $user->save(); + + /** @var Authenticator|MockObject $auth */ + $auth = $this->getMockBuilder(Authenticator::class) + ->disableOriginalConstructor() + ->setMethods(['setPassword']) + ->getMock(); + + $auth->expects($this->once()) + ->method('setPassword') + ->with($user, 'testing'); + $auth->setPasswordAlgorithm(PASSWORD_BCRYPT); + + $this->assertFalse($auth->verifyPassword($user, 'randomStuff')); + $this->assertTrue($auth->verifyPassword($user, 'testing')); + } + + /** + * @covers \Engelsystem\Helpers\Authenticator::setPassword + */ + public function testSetPassword() + { + $this->initDatabase(); + $user = new User([ + 'name' => 'ipsum', + 'password' => '', + 'email' => 'ipsum@foo.bar', + 'api_key' => '', + ]); + $user->save(); + + $auth = $this->getAuthenticator(); + $auth->setPasswordAlgorithm(PASSWORD_ARGON2I); + + $auth->setPassword($user, 'FooBar'); + $this->assertTrue($user->isClean()); + + $this->assertTrue(password_verify('FooBar', $user->password)); + $this->assertFalse(password_needs_rehash($user->password, PASSWORD_ARGON2I)); + } + + /** + * @covers \Engelsystem\Helpers\Authenticator::setPasswordAlgorithm + * @covers \Engelsystem\Helpers\Authenticator::getPasswordAlgorithm + */ + public function testPasswordAlgorithm() + { + $auth = $this->getAuthenticator(); + + $auth->setPasswordAlgorithm(PASSWORD_ARGON2I); + $this->assertEquals(PASSWORD_ARGON2I, $auth->getPasswordAlgorithm()); + } + + /** + * @return Authenticator + */ + protected function getAuthenticator() + { + return new class extends Authenticator + { + /** @noinspection PhpMissingParentConstructorInspection */ + public function __construct() { } + }; + } } diff --git a/tests/Unit/Http/UrlGeneratorServiceProviderTest.php b/tests/Unit/Http/UrlGeneratorServiceProviderTest.php index 61bf3e7c..6d18f160 100644 --- a/tests/Unit/Http/UrlGeneratorServiceProviderTest.php +++ b/tests/Unit/Http/UrlGeneratorServiceProviderTest.php @@ -19,7 +19,7 @@ class UrlGeneratorServiceProviderTest extends ServiceProviderTest $urlGenerator = $this->getMockBuilder(UrlGenerator::class) ->getMock(); - $app = $this->getApp(); + $app = $this->getApp(['make', 'instance', 'bind']); $this->setExpects($app, 'make', [UrlGenerator::class], $urlGenerator); $app->expects($this->exactly(2)) @@ -29,6 +29,9 @@ class UrlGeneratorServiceProviderTest extends ServiceProviderTest ['http.urlGenerator', $urlGenerator], [UrlGeneratorInterface::class, $urlGenerator] ); + $app->expects($this->once()) + ->method('bind') + ->with(UrlGeneratorInterface::class, UrlGenerator::class); $serviceProvider = new UrlGeneratorServiceProvider($app); $serviceProvider->register();