]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Controllers/AuthController.php
Fixes, run CS on PHP 7.4
[galette.git] / galette / lib / Galette / Controllers / AuthController.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Galette authentication controller
7 *
8 * PHP version 5
9 *
10 * Copyright © 2019-2020 The Galette Team
11 *
12 * This file is part of Galette (http://galette.tuxfamily.org).
13 *
14 * Galette is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
18 *
19 * Galette is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
26 *
27 * @category Entity
28 * @package Galette
29 *
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2019-2020 The Galette Team
32 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
33 * @version SVN: $Id$
34 * @link http://galette.tuxfamily.org
35 * @since Available since 0.9.4dev - 2019-12-02
36 */
37
38 namespace Galette\Controllers;
39
40 use Slim\Http\Request;
41 use Slim\Http\Response;
42 use Galette\Core\SysInfos;
43 use Galette\Core\Login;
44 use Galette\Core\Password;
45 use Galette\Core\GaletteMail;
46 use Galette\Entity\Adherent;
47 use Galette\Entity\Texts;
48 use Analog\Analog;
49
50 /**
51 * Galette authentication controller
52 *
53 * @category Controllers
54 * @name AuthController
55 * @package Galette
56 * @author Johan Cwiklinski <johan@x-tnd.be>
57 * @copyright 2019-2020 The Galette Team
58 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
59 * @link http://galette.tuxfamily.org
60 * @since Available since 0.9.4dev - 2019-12-02
61 */
62
63 class AuthController extends AbstractController
64 {
65 /**
66 * Log in
67 *
68 * @param Request $request PSR Request
69 * @param Response $response PSR Response
70 * @param array $args Request arguments ['r']
71 *
72 * @return void
73 */
74 public function login(Request $request, Response $response, array $args = [])
75 {
76 //store redirect path if any
77 if (isset($args['r'])
78 && $args['r'] != '/logout'
79 && $args['r'] != '/login'
80 ) {
81 $this->session->urlRedirect = $args['r'];
82 }
83
84 if (!$this->login->isLogged()) {
85 // display page
86 $this->view->render(
87 $response,
88 'index.tpl',
89 array(
90 'page_title' => _T("Login"),
91 )
92 );
93 return $response;
94 } else {
95 return $this->galetteRedirect($request, $response, $args);
96 }
97 }
98
99 /**
100 * Do login
101 *
102 * @param Request $request PSR Request
103 * @param Response $response PSR Response
104 *
105 * @return void
106 */
107 public function doLogin(Request $request, Response $response)
108 {
109 $nick = $request->getParsedBody()['login'];
110 $password = $request->getParsedBody()['password'];
111 $checkpass = new \Galette\Util\Password($this->preferences);
112
113 if (trim($nick) == '' || trim($password) == '') {
114 $this->flash->addMessage(
115 'loginfault',
116 _T("You must provide both login and password.")
117 );
118 return $response
119 ->withStatus(301)
120 ->withHeader('Location', $this->router->pathFor('login'));
121 }
122
123 if ($nick === $this->preferences->pref_admin_login) {
124 $pw_superadmin = password_verify(
125 $password,
126 $this->preferences->pref_admin_pass
127 );
128 if (!$pw_superadmin) {
129 $pw_superadmin = (
130 md5($password) === $this->preferences->pref_admin_pass
131 );
132 }
133 if ($pw_superadmin) {
134 $this->login->logAdmin($nick, $this->preferences);
135 }
136 } else {
137 $this->login->logIn($nick, $password);
138 }
139
140 if ($this->login->isLogged()) {
141 if (!$checkpass->isValid($password)) {
142 //password is no longer valid with current rules, must be changed
143 $this->flash->addMessage(
144 'warning_detected',
145 _T("Your password is too weak! Please consider updating it.") .
146 '<br/> -' . implode('<br/>', $checkpass->getErrors())
147 );
148 }
149 $this->session->login = $this->login;
150 $this->history->add(_T("Login"));
151 return $this->galetteRedirect($request, $response, []);
152 } else {
153 $this->flash->addMessage('error_detected', _T("Login failed."));
154 $this->history->add(_T("Authentication failed"), $nick);
155 return $response->withStatus(301)->withHeader('Location', $this->router->pathFor('login'));
156 }
157 }
158
159 /**
160 * Log out
161 *
162 * @param Request $request PSR Request
163 * @param Response $response PSR Response
164 *
165 * @return void
166 */
167 public function logout(Request $request, Response $response)
168 {
169 $this->login->logOut();
170 $this->history->add(_T("Log off"));
171 \RKA\Session::destroy();
172 return $response
173 ->withStatus(301)
174 ->withHeader('Location', $this->router->pathFor('slash'));
175 }
176
177 /**
178 * Impersonate
179 *
180 * @param Request $request PSR Request
181 * @param Response $response PSR Response
182 * @param array $args Request arguments ['id']
183 *
184 * @return void
185 */
186 public function impersonate(Request $request, Response $response, array $args)
187 {
188 $success = $this->login->impersonate((int)$args['id']);
189
190 if ($success === true) {
191 $this->session->login = $this->login;
192 $msg = str_replace(
193 '%login',
194 $this->login->login,
195 _T("Impersonating as %login")
196 );
197
198 $this->history->add($msg);
199 $this->flash->addMessage(
200 'success_detected',
201 $msg
202 );
203 } else {
204 $msg = str_replace(
205 '%id',
206 $args['id'],
207 _T("Unable to impersonate as %id")
208 );
209 $this->flash->addMessage(
210 'error_detected',
211 $msg
212 );
213 $this->history->add($msg);
214 }
215
216 return $response
217 ->withStatus(301)
218 ->withHeader('Location', $this->router->pathFor('slash'));
219 }
220
221 /**
222 * End impersonate
223 *
224 * @param Request $request PSR Request
225 * @param Response $response PSR Response
226 *
227 * @return void
228 */
229 public function unimpersonate(Request $request, Response $response)
230 {
231 $login = new Login($this->zdb, $this->i18n, $this->session);
232 $login->logAdmin($this->preferences->pref_admin_login, $this->preferences);
233 $this->history->add(_T("Impersonating ended"));
234 $this->session->login = $login;
235 $this->login = $login;
236 $this->flash->addMessage(
237 'success_detected',
238 _T("Impersonating ended")
239 );
240 return $response
241 ->withStatus(301)
242 ->withHeader('Location', $this->router->pathFor('slash'));
243 }
244
245 /**
246 * Lost password page
247 *
248 * @param Request $request PSR Request
249 * @param Response $response PSR Response
250 *
251 * @return Response
252 */
253 public function lostPassword(Request $request, Response $response) :Response
254 {
255 if ($this->preferences->pref_mail_method === GaletteMail::METHOD_DISABLED) {
256 throw new \RuntimeException('Mailing disabled.');
257 }
258 // display page
259 $this->view->render(
260 $response,
261 'lostpasswd.tpl',
262 array(
263 'page_title' => _T("Password recovery")
264 )
265 );
266 return $response;
267 }
268
269 /**
270 * Retrieve password procedure
271 *
272 * @param Request $request PSR Request
273 * @param Response $response PSR Response
274 * @param array $args Request arguments ['id']
275 *
276 * @return Response
277 */
278 public function retrievePassword(Request $request, Response $response, array $args) :Response
279 {
280 $from_admin = false;
281 $redirect_url = $this->router->pathFor('slash');
282 if ((($this->login->isAdmin() || $this->login->isStaff()) && isset($args[Adherent::PK]))) {
283 $from_admin = true;
284 $redirect_url = $this->router->pathFor('member', ['id' => $args[Adherent::PK]]);
285 }
286
287 if (($this->login->isLogged()
288 || $this->preferences->pref_mail_method == GaletteMail::METHOD_DISABLED)
289 && !$from_admin
290 ) {
291 if ($this->preferences->pref_mail_method == GaletteMail::METHOD_DISABLED) {
292 $this->flash->addMessage(
293 'error_detected',
294 _T("Email sent is disabled in the preferences. Ask galette admin")
295 );
296 }
297 return $response
298 ->withStatus(301)
299 ->withHeader('Location', $redirect_url);
300 }
301
302 $adh = null;
303 $login_adh = null;
304 if (($this->login->isAdmin() || $this->login->isStaff()) && isset($args[Adherent::PK])) {
305 $adh = new Adherent($this->zdb, (int)$args[Adherent::PK]);
306 $login_adh = $adh->login;
307 } else {
308 $post = $request->getParsedBody();
309 $login_adh = $post['login'];
310 $adh = new Adherent($this->zdb, $login_adh);
311 }
312
313 if ($adh->id != '') {
314 //account has been found, proceed
315 if (GaletteMail::isValidEmail($adh->email)) {
316 $password = new Password($this->zdb);
317 $res = $password->generateNewPassword($adh->id);
318 if ($res == true) {
319 $link_validity = new \DateTime();
320 $link_validity->add(new \DateInterval('PT24H'));
321
322 $df = _T("Y-m-d H:i:s");
323
324 $texts = new Texts(
325 $this->preferences,
326 $this->router,
327 array(
328 'change_pass_uri' => $this->preferences->getURL() .
329 $this->router->pathFor(
330 'password-recovery',
331 ['hash' => base64_encode($password->getHash())]
332 ),
333 'link_validity' => $link_validity->format(_T("Y-m-d H:i:s")),
334 'login_adh' => custom_html_entity_decode($adh->login, ENT_QUOTES)
335 )
336 );
337 $mtxt = $texts->getTexts('pwd', $adh->language);
338
339 $mail = new GaletteMail($this->preferences);
340 $mail->setSubject($texts->getSubject());
341 $mail->setRecipients(
342 array(
343 $adh->email => $adh->sname
344 )
345 );
346
347 $mail->setMessage($texts->getBody());
348 $sent = $mail->send();
349
350 if ($sent == GaletteMail::MAIL_SENT) {
351 $this->history->add(
352 str_replace(
353 '%s',
354 $login_adh,
355 _T("Email sent to '%s' for password recovery.")
356 )
357 );
358 if ($from_admin === false) {
359 $message = _T("An email has been sent to your address.<br/>Please check your inbox and follow the instructions.");
360 $done = true;
361 } else {
362 $message = _T("An email has been sent to the member.");
363 }
364
365 $this->flash->addMessage(
366 'success_detected',
367 $message
368 );
369 } else {
370 $str = str_replace(
371 '%s',
372 $login_adh,
373 _T("A problem happened while sending password for account '%s'")
374 );
375 $this->history->add($str);
376 $this->flash->addMessage(
377 'error_detected',
378 $str
379 );
380
381 $error_detected[] = $str;
382 }
383 } else {
384 $str = str_replace(
385 '%s',
386 $login_adh,
387 _T("An error occurred storing temporary password for %s. Please inform an admin.")
388 );
389 $this->history->add($str);
390 $this->flash->addMessage(
391 'error_detected',
392 $str
393 );
394 }
395 } else {
396 $str = str_replace(
397 '%s',
398 $login_adh,
399 _T("Your account (%s) do not contain any valid email address")
400 );
401 $this->history->add($str);
402 $this->flash->addMessage(
403 'error_detected',
404 $str
405 );
406 }
407 } else {
408 //account has not been found
409 if (GaletteMail::isValidEmail($login_adh)) {
410 $str = str_replace(
411 '%s',
412 $login_adh,
413 _T("Mails address %s does not exist")
414 );
415 } else {
416 $str = str_replace(
417 '%s',
418 $login_adh,
419 _T("Login %s does not exist")
420 );
421 }
422
423 $this->history->add($str);
424 $this->flash->addMessage(
425 'error_detected',
426 $str
427 );
428 }
429
430 return $response
431 ->withStatus(301)
432 ->withHeader('Location', $redirect_url);
433 }
434
435 /**
436 * Password recovery page
437 *
438 * @param Request $request PSR Request
439 * @param Response $response PSR Response
440 * @param array $args Request arguments
441 *
442 * @return Response
443 */
444 public function recoverPassword(Request $request, Response $response, array $args) :Response
445 {
446 $password = new Password($this->zdb);
447 if (!$id_adh = $password->isHashValid(base64_decode($args['hash']))) {
448 $this->flash->addMessage(
449 'warning_detected',
450 _T("This link is no longer valid. You should ask to retrieve your password again.")
451 );
452 return $response
453 ->withStatus(301)
454 ->withHeader(
455 'Location',
456 $this->router->pathFor('password-lost')
457 );
458 }
459
460 // display page
461 $this->view->render(
462 $response,
463 'change_passwd.tpl',
464 array(
465 'hash' => $args['hash'],
466 'page_title' => _T("Password recovery")
467 )
468 );
469 return $response;
470 }
471
472 /**
473 * Password recovery
474 *
475 * @param Request $request PSR Request
476 * @param Response $response PSR Response
477 *
478 * @return Response
479 */
480 public function doRecoverPassword(Request $request, Response $response) :Response
481 {
482 $post = $request->getParsedBody();
483 $password = new Password($this->zdb);
484 $hash_ok = true;
485
486 if (!$id_adh = $password->isHashValid(base64_decode($post['hash']))) {
487 return $response
488 ->withStatus(301)
489 ->withHeader(
490 'Location',
491 $this->router->pathFor('password-recovery', ['hash' => $post['hash']])
492 );
493 }
494
495 $error = null;
496 if ($post['mdp_adh'] == '') {
497 $error = _T("No password");
498 } elseif (isset($post['mdp_adh2'])) {
499 if (strcmp($post['mdp_adh'], $post['mdp_adh2'])) {
500 $error = _T("- The passwords don't match!");
501 } else {
502 $checkpass = new \Galette\Util\Password($this->preferences);
503
504 if (!$checkpass->isValid($post['mdp_adh'])) {
505 //password is not valid with current rules
506 $error = _T("Your password is too weak!") .
507 '<br/> -' . implode('<br/>', $checkpass->getErrors());
508 } else {
509 $res = Adherent::updatePassword(
510 $this->zdb,
511 $id_adh,
512 $post['mdp_adh']
513 );
514 if ($res !== true) {
515 $error = _T("An error occurred while updating your password.");
516 } else {
517 $this->history->add(
518 str_replace(
519 '%s',
520 $id_adh,
521 _T("Password changed for member '%s'.")
522 )
523 );
524 //once password has been changed, we can remove the
525 //temporary password entry
526 $password->removeHash(base64_decode($post['hash']));
527 $password_updated = true;
528 $this->flash->addMessage(
529 'success_detected',
530 _T("Your password has been changed!")
531 );
532 return $response
533 ->withStatus(301)
534 ->withHeader(
535 'Location',
536 $this->router->pathFor('slash')
537 );
538 }
539 }
540 }
541 }
542
543 if ($error !== null) {
544 $this->flash->addMessage(
545 'error_detected',
546 $error
547 );
548 }
549
550 return $response
551 ->withStatus(301)
552 ->withHeader(
553 'Location',
554 $this->router->pathFor('password-recovery', ['hash' => $post['hash']])
555 );
556 }
557 }