mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
341 lines
12 KiB
PHP
341 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* Chill is a software for social workers
|
|
*
|
|
* For the full copyright and license information, please view
|
|
* the LICENSE file that was distributed with this source code.
|
|
*/
|
|
|
|
namespace Chill\MainBundle\Controller;
|
|
|
|
use Chill\MainBundle\Entity\User;
|
|
use Chill\MainBundle\Form\UserPasswordType;
|
|
use Chill\MainBundle\Security\PasswordRecover\PasswordRecoverEvent;
|
|
use Chill\MainBundle\Security\PasswordRecover\PasswordRecoverVoter;
|
|
use Chill\MainBundle\Security\PasswordRecover\RecoverPasswordHelper;
|
|
use Chill\MainBundle\Security\PasswordRecover\TokenManager;
|
|
use DateInterval;
|
|
use DateTimeImmutable;
|
|
use Psr\Log\LoggerInterface;
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\Routing\Annotation\Route;
|
|
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
|
use Symfony\Component\Validator\Constraints\Callback;
|
|
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
|
|
|
/**
|
|
* Class PasswordController.
|
|
*/
|
|
class PasswordController extends AbstractController
|
|
{
|
|
/**
|
|
* @var LoggerInterface
|
|
*/
|
|
protected $chillLogger;
|
|
|
|
/**
|
|
* @var EventDispatcherInterface
|
|
*/
|
|
protected $eventDispatcher;
|
|
|
|
/**
|
|
* @var UserPasswordEncoderInterface
|
|
*/
|
|
protected $passwordEncoder;
|
|
|
|
/**
|
|
* @var RecoverPasswordHelper
|
|
*/
|
|
protected $recoverPasswordHelper;
|
|
|
|
/**
|
|
* @var TokenManager
|
|
*/
|
|
protected $tokenManager;
|
|
|
|
/**
|
|
* @var TranslatorInterface
|
|
*/
|
|
protected $translator;
|
|
|
|
/**
|
|
* PasswordController constructor.
|
|
*/
|
|
public function __construct(
|
|
LoggerInterface $chillLogger,
|
|
UserPasswordEncoderInterface $passwordEncoder,
|
|
RecoverPasswordHelper $recoverPasswordHelper,
|
|
TokenManager $tokenManager,
|
|
TranslatorInterface $translator,
|
|
EventDispatcherInterface $eventDispatcher
|
|
) {
|
|
$this->chillLogger = $chillLogger;
|
|
$this->passwordEncoder = $passwordEncoder;
|
|
$this->translator = $translator;
|
|
$this->tokenManager = $tokenManager;
|
|
$this->recoverPasswordHelper = $recoverPasswordHelper;
|
|
$this->eventDispatcher = $eventDispatcher;
|
|
}
|
|
|
|
/**
|
|
* @return Response
|
|
* @\Symfony\Component\Routing\Annotation\Route(path="/public/{_locale}/password/request-changed", name="password_request_recover_changed")
|
|
*/
|
|
public function changeConfirmedAction()
|
|
{
|
|
return $this->render('@ChillMain/Password/recover_password_changed.html.twig');
|
|
}
|
|
|
|
/**
|
|
* @return Response|\Symfony\Component\HttpFoundation\RedirectResponse
|
|
* @\Symfony\Component\Routing\Annotation\Route(path="/public/{_locale}/password/recover", name="password_recover")
|
|
*/
|
|
public function recoverAction(Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
|
{
|
|
if (false === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN)) {
|
|
return new Response($this->translator->trans('You are not allowed '
|
|
. 'to try to recover password, due to mitigating possible '
|
|
. 'attack. Try to contact your system administrator'), Response::HTTP_FORBIDDEN);
|
|
}
|
|
|
|
$query = $request->query;
|
|
$username = $query->get(TokenManager::USERNAME_CANONICAL);
|
|
$hash = $query->getAlnum(TokenManager::HASH);
|
|
$token = $query->getAlnum(TokenManager::TOKEN);
|
|
$timestamp = $query->getAlnum(TokenManager::TIMESTAMP);
|
|
$user = $this->getDoctrine()->getRepository(User::class)
|
|
->findOneByUsernameCanonical($username);
|
|
|
|
if (null === $user) {
|
|
$this->eventDispatcher->dispatch(
|
|
new PasswordRecoverEvent($token, null, $request->getClientIp()),
|
|
PasswordRecoverEvent::INVALID_TOKEN
|
|
);
|
|
|
|
throw $this->createNotFoundException(sprintf('User %s not found', $username));
|
|
}
|
|
|
|
if (true !== $this->tokenManager->verify($hash, $token, $user, $timestamp)) {
|
|
$this->eventDispatcher->dispatch(
|
|
new PasswordRecoverEvent($token, $user, $request->getClientIp()),
|
|
PasswordRecoverEvent::INVALID_TOKEN
|
|
);
|
|
|
|
return new Response('Invalid token', Response::HTTP_FORBIDDEN);
|
|
}
|
|
|
|
$form = $this->passwordForm($user);
|
|
$form->remove('actual_password');
|
|
|
|
$form->handleRequest($request);
|
|
|
|
if ($form->isSubmitted() && $form->isValid()) {
|
|
$password = $form->get('new_password')->getData();
|
|
$user->setPassword($this->passwordEncoder->encodePassword($user, $password));
|
|
// logging for prod
|
|
$this
|
|
->chillLogger
|
|
->notice(
|
|
'setting new password for user',
|
|
[
|
|
'user' => $user->getUsername(),
|
|
]
|
|
);
|
|
|
|
$this->getDoctrine()->getManager()->flush();
|
|
|
|
return $this->redirectToRoute('password_request_recover_changed');
|
|
}
|
|
|
|
return $this->render('@ChillMain/Password/recover_password_form.html.twig', [
|
|
'form' => $form->createView(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @throws \Doctrine\ORM\NoResultException
|
|
* @throws \Doctrine\ORM\NonUniqueResultException
|
|
*
|
|
* @return Response|\Symfony\Component\HttpFoundation\RedirectResponse
|
|
* @\Symfony\Component\Routing\Annotation\Route(path="/public/{_locale}/password/request-recover", name="password_request_recover")
|
|
*/
|
|
public function requestRecoverAction(Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
|
{
|
|
if (false === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN)) {
|
|
return new Response($this->translator->trans('You are not allowed '
|
|
. 'to try to recover password, due to mitigating possible '
|
|
. 'attack. Try to contact your system administrator'), Response::HTTP_FORBIDDEN);
|
|
}
|
|
|
|
$form = $this->requestRecoverForm();
|
|
|
|
$form->handleRequest($request);
|
|
|
|
if ($form->isSubmitted() && $form->isValid()) {
|
|
/** @var \Doctrine\ORM\QueryBuilder $qb */
|
|
$qb = $this->getDoctrine()->getManager()
|
|
->createQueryBuilder();
|
|
$qb->select('u')
|
|
->from(User::class, 'u')
|
|
->where($qb->expr()->eq('u.usernameCanonical', 'UNACCENT(LOWER(:pattern))'))
|
|
->orWhere($qb->expr()->eq('u.emailCanonical', 'UNACCENT(LOWER(:pattern))'))
|
|
->setParameter('pattern', $form->get('username_or_email')->getData());
|
|
|
|
$user = $qb->getQuery()->getSingleResult();
|
|
|
|
if (empty($user->getEmail())) {
|
|
$this->addFlash('error', $this->translator->trans('This account does not have an email address. '
|
|
. 'Please ask your administrator to renew your password.'));
|
|
} else {
|
|
if (false === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN, $user)) {
|
|
return new Response($this->translator->trans('You are not allowed '
|
|
. 'to try to recover password, due to mitigating possible '
|
|
. 'attack. Try to contact your system administrator'), Response::HTTP_FORBIDDEN);
|
|
}
|
|
|
|
$this->recoverPasswordHelper->sendRecoverEmail(
|
|
$user,
|
|
(new DateTimeImmutable('now'))->add(new DateInterval('PT30M'))
|
|
);
|
|
|
|
// logging for prod
|
|
$this
|
|
->chillLogger
|
|
->notice(
|
|
'Sending an email for password recovering',
|
|
[
|
|
'user' => $user->getUsername(),
|
|
]
|
|
);
|
|
|
|
$this->eventDispatcher->dispatch(
|
|
new PasswordRecoverEvent(null, $user, $request->getClientIp()),
|
|
PasswordRecoverEvent::ASK_TOKEN_SUCCESS
|
|
);
|
|
|
|
return $this->redirectToRoute('password_request_recover_confirm');
|
|
}
|
|
} elseif ($form->isSubmitted() && false === $form->isValid()) {
|
|
$this->eventDispatcher->dispatch(
|
|
new PasswordRecoverEvent(null, null, $request->getClientIp()),
|
|
PasswordRecoverEvent::ASK_TOKEN_INVALID_FORM
|
|
);
|
|
}
|
|
|
|
return $this->render('@ChillMain/Password/request_recover_password.html.twig', [
|
|
'form' => $form->createView(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @return Response
|
|
* @\Symfony\Component\Routing\Annotation\Route(path="/public/{_locale}/password/request-confirm", name="password_request_recover_confirm")
|
|
*/
|
|
public function requestRecoverConfirmAction()
|
|
{
|
|
return $this->render('@ChillMain/Password/request_recover_password_confirm.html.twig');
|
|
}
|
|
|
|
/**
|
|
* @return Response
|
|
* @Route("/{_locale}/my/password", name="change_my_password")
|
|
*/
|
|
public function UserPasswordAction(Request $request)
|
|
{
|
|
// get authentified user
|
|
$user = $this->getUser();
|
|
|
|
// create a form for password_encoder
|
|
$form = $this->passwordForm($user);
|
|
|
|
// process the form
|
|
$form->handleRequest($request);
|
|
|
|
if ($form->isSubmitted() && $form->isValid()) {
|
|
$password = $form->get('new_password')->getData();
|
|
|
|
// logging for prod
|
|
$this
|
|
->chillLogger
|
|
->notice(
|
|
'update password for an user',
|
|
[
|
|
'method' => $request->getMethod(),
|
|
'user' => $user->getUsername(),
|
|
]
|
|
);
|
|
|
|
$user->setPassword($this->passwordEncoder->encodePassword($user, $password));
|
|
|
|
$em = $this->getDoctrine()->getManager();
|
|
$em->flush();
|
|
|
|
$this->addFlash('success', $this->translator->trans('Password successfully updated!'));
|
|
|
|
return $this->redirectToRoute('change_my_password');
|
|
}
|
|
|
|
// render into a template
|
|
return $this->render('@ChillMain/Password/password.html.twig', [
|
|
'form' => $form->createView(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @return \Symfony\Component\Form\FormInterface
|
|
*/
|
|
protected function requestRecoverForm()
|
|
{
|
|
$builder = $this->createFormBuilder();
|
|
$builder
|
|
->add('username_or_email', TextType::class, [
|
|
'label' => 'Username or email',
|
|
'constraints' => [
|
|
new Callback([
|
|
'callback' => function ($pattern, ExecutionContextInterface $context, $payload) {
|
|
$qb = $this->getDoctrine()->getManager()
|
|
->createQueryBuilder();
|
|
$qb->select('COUNT(u)')
|
|
->from(User::class, 'u')
|
|
->where($qb->expr()->eq('u.usernameCanonical', 'UNACCENT(LOWER(:pattern))'))
|
|
->orWhere($qb->expr()->eq('u.emailCanonical', 'UNACCENT(LOWER(:pattern))'))
|
|
->setParameter('pattern', $pattern);
|
|
|
|
if ((int) $qb->getQuery()->getSingleScalarResult() !== 1) {
|
|
$context->addViolation('This username or email does not exists');
|
|
}
|
|
},
|
|
]),
|
|
],
|
|
])
|
|
->add('submit', SubmitType::class, [
|
|
'label' => 'Request recover',
|
|
]);
|
|
|
|
return $builder->getForm();
|
|
}
|
|
|
|
/**
|
|
* @return \Symfony\Component\Form\Form
|
|
*/
|
|
private function passwordForm(User $user)
|
|
{
|
|
return $this
|
|
->createForm(
|
|
UserPasswordType::class,
|
|
[],
|
|
['user' => $user]
|
|
)
|
|
->add('submit', SubmitType::class, ['label' => 'Change password']);
|
|
}
|
|
}
|