]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Controllers/AuthController.php
e812ea18662998d3d6713134e282657566ebb869
[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 (!$checkpass->isValid($password)) {
140 //password is no longer valid with current rules, must be changed
141 $this->flash->addMessage(
142 'warning_detected',
143 _T("Your password is too weak! Please consider updating it.") .
144 '<br/> -' . implode('<br/>', $checkpass->getErrors())
145 );
146 }
147 $this->session->login = $this->login;
148 $this->history->add(_T("Login"));
149 return $this->galetteRedirect($request, $response);
150 } else {
151 $this->flash->addMessage('error_detected', _T("Login failed."));
152 $this->history->add(_T("Authentication failed"), $nick);
153 return $response->withStatus(301)->withHeader('Location', $this->routeparser->urlFor('login'));
154 }
155 }
156
157 /**
158 * Log out
159 *
160 * @param Request $request PSR Request
161 * @param Response $response PSR Response
162 *
163 * @return Response
164 */
165 public function logout(Request $request, Response $response)
166 {
167 $this->login->logOut();
168 $this->history->add(_T("Log off"));
169 \RKA\Session::destroy();
170 return $response
171 ->withStatus(301)
172 ->withHeader('Location', $this->routeparser->urlFor('slash'));
173 }
174
175 /**
176 * Impersonate
177 *
178 * @param Request $request PSR Request
179 * @param Response $response PSR Response
180 * @param integer $id Member to impersonate
181 *
182 * @return Response
183 */
184 public function impersonate(Request $request, Response $response, int $id)
185 {
186 $success = $this->login->impersonate($id);
187
188 if ($success === true) {
189 $this->session->login = $this->login;
190 $msg = str_replace(
191 '%login',
192 $this->login->login,
193 _T("Impersonating as %login")
194 );
195
196 $this->history->add($msg);
197 $this->flash->addMessage(
198 'success_detected',
199 $msg
200 );
201 } else {
202 $msg = str_replace(
203 '%id',
204 $id,
205 _T("Unable to impersonate as %id")
206 );
207 $this->flash->addMessage(
208 'error_detected',
209 $msg
210 );
211 $this->history->add($msg);
212 }
213
214 return $response
215 ->withStatus(301)
216 ->withHeader('Location', $this->routeparser->urlFor('slash'));
217 }
218
219 /**
220 * End impersonate
221 *
222 * @param Request $request PSR Request
223 * @param Response $response PSR Response
224 *
225 * @return Response
226 */
227 public function unimpersonate(Request $request, Response $response)
228 {
229 $login = new Login($this->zdb, $this->i18n);
230 $login->logAdmin($this->preferences->pref_admin_login, $this->preferences);
231 $this->history->add(_T("Impersonating ended"));
232 $this->session->login = $login;
233 $this->login = $login;
234 $this->flash->addMessage(
235 'success_detected',
236 _T("Impersonating ended")
237 );
238 return $response
239 ->withStatus(301)
240 ->withHeader('Location', $this->routeparser->urlFor('slash'));
241 }
242
243 /**
244 * Lost password page
245 *
246 * @param Request $request PSR Request
247 * @param Response $response PSR Response
248 *
249 * @return Response
250 */
251 public function lostPassword(Request $request, Response $response): Response
252 {
253 if ($this->preferences->pref_mail_method === GaletteMail::METHOD_DISABLED) {
254 throw new \RuntimeException('Mailing disabled.');
255 }
256 // display page
257 $this->view->render(
258 $response,
259 'pages/password_lost.html.twig',
260 array(
261 'page_title' => _T("Password recovery")
262 )
263 );
264 return $response;
265 }
266
267 /**
268 * Retrieve password procedure
269 *
270 * @param Request $request PSR Request
271 * @param Response $response PSR Response
272 * @param integer $id_adh Member id
273 *
274 * @return Response
275 */
276 public function retrievePassword(Request $request, Response $response, int $id_adh = null): Response
277 {
278 $from_admin = false;
279 $redirect_url = $this->routeparser->urlFor('slash');
280 if ((($this->login->isAdmin() || $this->login->isStaff()) && $id_adh !== null)) {
281 $from_admin = true;
282 $redirect_url = $this->routeparser->urlFor('member', ['id' => $id_adh]);
283 }
284
285 if (
286 ($this->login->isLogged()
287 || $this->preferences->pref_mail_method == GaletteMail::METHOD_DISABLED)
288 && !$from_admin
289 ) {
290 if ($this->preferences->pref_mail_method == GaletteMail::METHOD_DISABLED) {
291 $this->flash->addMessage(
292 'error_detected',
293 _T("Email sent is disabled in the preferences. Ask galette admin")
294 );
295 }
296 return $response
297 ->withStatus(301)
298 ->withHeader('Location', $redirect_url);
299 }
300
301 $adh = null;
302 $login_adh = null;
303 if (($this->login->isAdmin() || $this->login->isStaff()) && $id_adh !== null) {
304 $adh = new Adherent($this->zdb, $id_adh);
305 $login_adh = $adh->login;
306 } else {
307 $post = $request->getParsedBody();
308 $login_adh = htmlspecialchars($post['login'], ENT_QUOTES);
309 $adh = new Adherent($this->zdb, $login_adh);
310 }
311
312 if ($adh->id != '') {
313 //account has been found, proceed
314 if (GaletteMail::isValidEmail($adh->email)) {
315 $texts = new Texts($this->preferences, $this->routeparser);
316 $texts
317 ->setMember($adh)
318 ->setNoContribution();
319
320 //check if account is active
321 if (!$adh->isActive()) { //https://bugs.galette.eu/issues/1529
322 $res = true;
323 $text_id = 'pwddisabled';
324 } else {
325 $password = new Password($this->zdb);
326 $res = $password->generateNewPassword($adh->id);
327 $text_id = 'pwd';
328 $texts
329 ->setLinkValidity()
330 ->setChangePasswordURI($password);
331 }
332
333 if ($res === true) {
334 $texts->getTexts($text_id, $adh->language);
335
336 $mail = new GaletteMail($this->preferences);
337 $mail->setSubject($texts->getSubject());
338 $mail->setRecipients(
339 array(
340 $adh->email => $adh->sname
341 )
342 );
343
344 $mail->setMessage($texts->getBody());
345 $sent = $mail->send();
346
347 if ($sent == GaletteMail::MAIL_SENT) {
348 $this->history->add(
349 str_replace(
350 '%s',
351 $login_adh,
352 _T("Email sent to '%s' for password recovery.")
353 )
354 );
355 if ($from_admin === false) {
356 $message = _T("An email has been sent to your address.<br/>Please check your inbox and follow the instructions.");
357 } else {
358 $message = _T("An email has been sent to the member.");
359 }
360
361 $this->flash->addMessage(
362 'success_detected',
363 $message
364 );
365 } else {
366 $str = str_replace(
367 '%s',
368 $login_adh,
369 _T("A problem happened while sending password for account '%s'")
370 );
371 $this->history->add($str);
372 $this->flash->addMessage(
373 'error_detected',
374 $str
375 );
376
377 $error_detected[] = $str;
378 }
379 } else {
380 $str = str_replace(
381 '%s',
382 $login_adh,
383 _T("An error occurred storing temporary password for %s. Please inform an admin.")
384 );
385 $this->history->add($str);
386 $this->flash->addMessage(
387 'error_detected',
388 $str
389 );
390 }
391 } else {
392 $str = str_replace(
393 '%s',
394 $login_adh,
395 _T("Your account (%s) do not contain any valid email address")
396 );
397 $this->history->add($str);
398 $this->flash->addMessage(
399 'error_detected',
400 $str
401 );
402 }
403 } else {
404 //account has not been found
405 if (GaletteMail::isValidEmail($login_adh)) {
406 $str = str_replace(
407 '%s',
408 $login_adh,
409 _T("Mails address %s does not exist")
410 );
411 } else {
412 $str = str_replace(
413 '%s',
414 $login_adh,
415 _T("Login %s does not exist")
416 );
417 }
418
419 $this->history->add($str);
420 $this->flash->addMessage(
421 'error_detected',
422 $str
423 );
424 }
425
426 return $response
427 ->withStatus(301)
428 ->withHeader('Location', $redirect_url);
429 }
430
431 /**
432 * Password recovery page
433 *
434 * @param Request $request PSR Request
435 * @param Response $response PSR Response
436 * @param string $hash Hash
437 *
438 * @return Response
439 */
440 public function recoverPassword(Request $request, Response $response, string $hash): Response
441 {
442 $password = new Password($this->zdb);
443 if (!$password->isHashValid(base64_decode($hash))) {
444 $this->flash->addMessage(
445 'warning_detected',
446 _T("This link is no longer valid. You should ask to retrieve your password again.")
447 );
448 return $response
449 ->withStatus(301)
450 ->withHeader(
451 'Location',
452 $this->routeparser->urlFor('password-lost')
453 );
454 }
455
456 // display page
457 $this->view->render(
458 $response,
459 'pages/password_recover.html.twig',
460 array(
461 'hash' => $hash,
462 'page_title' => _T("Password recovery")
463 )
464 );
465 return $response;
466 }
467
468 /**
469 * Password recovery
470 *
471 * @param Request $request PSR Request
472 * @param Response $response PSR Response
473 *
474 * @return Response
475 */
476 public function doRecoverPassword(Request $request, Response $response): Response
477 {
478 $post = $request->getParsedBody();
479 $password = new Password($this->zdb);
480
481 if (!$id_adh = $password->isHashValid(base64_decode($post['hash']))) {
482 return $response
483 ->withStatus(301)
484 ->withHeader(
485 'Location',
486 $this->routeparser->urlFor('password-recovery', ['hash' => $post['hash']])
487 );
488 }
489
490 $error = null;
491 if ($post['mdp_adh'] == '') {
492 $error = _T("No password");
493 } elseif (isset($post['mdp_adh2'])) {
494 if (strcmp($post['mdp_adh'], $post['mdp_adh2'])) {
495 $error = _T("- The passwords don't match!");
496 } else {
497 $checkpass = new \Galette\Util\Password($this->preferences);
498
499 if (!$checkpass->isValid($post['mdp_adh'])) {
500 //password is not valid with current rules
501 $error = _T("Your password is too weak!") .
502 '<br/> -' . implode('<br/>', $checkpass->getErrors());
503 } else {
504 $res = Adherent::updatePassword(
505 $this->zdb,
506 $id_adh,
507 $post['mdp_adh']
508 );
509 if ($res !== true) {
510 $error = _T("An error occurred while updating your password.");
511 } else {
512 $this->history->add(
513 str_replace(
514 '%s',
515 $id_adh,
516 _T("Password changed for member '%s'.")
517 )
518 );
519 //once password has been changed, we can remove the
520 //temporary password entry
521 $password->removeHash(base64_decode($post['hash']));
522 $this->flash->addMessage(
523 'success_detected',
524 _T("Your password has been changed!")
525 );
526 return $response
527 ->withStatus(301)
528 ->withHeader(
529 'Location',
530 $this->routeparser->urlFor('slash')
531 );
532 }
533 }
534 }
535 }
536
537 if ($error !== null) {
538 $this->flash->addMessage(
539 'error_detected',
540 $error
541 );
542 }
543
544 return $response
545 ->withStatus(301)
546 ->withHeader(
547 'Location',
548 $this->routeparser->urlFor('password-recovery', ['hash' => $post['hash']])
549 );
550 }
551 }