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