chill-bundles/Controller/PasswordController.php

312 lines
11 KiB
PHP

<?php
namespace Chill\MainBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Chill\MainBundle\Form\UserPasswordType;
use Chill\MainBundle\Entity\User;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Chill\MainBundle\Security\PasswordRecover\RecoverPasswordHelper;
use Symfony\Component\HttpFoundation\Response;
use Chill\MainBundle\Security\PasswordRecover\TokenManager;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Chill\MainBundle\Security\PasswordRecover\PasswordRecoverEvent;
use Chill\MainBundle\Security\PasswordRecover\PasswordRecoverVoter;
class PasswordController extends Controller
{
/**
*
* @var UserPasswordEncoderInterface
*/
protected $passwordEncoder;
/**
*
* @var TranslatorInterface
*/
protected $translator;
/**
*
* @var LoggerInterface
*/
protected $chillLogger;
/**
*
* @var RecoverPasswordHelper
*/
protected $recoverPasswordHelper;
/**
*
* @var TokenManager
*/
protected $tokenManager;
/**
*
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
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;
}
/**
*
* @param Request $request
* @return Response
*/
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',
array(
'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', array(
'form' => $form->createView()
));
}
/**
*
*
* @param User $user
* @return \Symfony\Component\Form\Form
*/
private function passwordForm(User $user)
{
return $this
->createForm(
UserPasswordType::class,
[],
[ 'user' => $user ]
)
->add('submit', SubmitType::class, array('label' => 'Change password'))
;
}
public function recoverAction(Request $request)
{
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->getInt(TokenManager::TIMESTAMP);
$user = $this->getDoctrine()->getRepository(User::class)
->findOneByUsernameCanonical($username);
if (NULL === $user) {
$this->eventDispatcher->dispatch(PasswordRecoverEvent::INVALID_TOKEN,
new PasswordRecoverEvent($token, null, $request->getClientIp()));
throw $this->createNotFoundException(sprintf('User %s not found', $username));
}
if (TRUE !== $this->tokenManager->verify($hash, $token, $user, $timestamp)) {
$this->eventDispatcher->dispatch(PasswordRecoverEvent::INVALID_TOKEN,
new PasswordRecoverEvent($token, $user, $request->getClientIp()));
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',
array(
'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()
]);
}
public function changeConfirmedAction()
{
return $this->render('@ChillMain/Password/recover_password_changed.html.twig');
}
public function requestRecoverAction(Request $request)
{
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 $qb \Doctrine\ORM\QueryBuilder */
$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',
array(
'user' => $user->getUsername()
)
);
$this->eventDispatcher->dispatch(
PasswordRecoverEvent::ASK_TOKEN_SUCCESS,
new PasswordRecoverEvent(null, $user, $request->getClientIp())
);
return $this->redirectToRoute('password_request_recover_confirm');
}
} elseif ($form->isSubmitted() && FALSE === $form->isValid()) {
$this->eventDispatcher->dispatch(
PasswordRecoverEvent::ASK_TOKEN_INVALID_FORM,
new PasswordRecoverEvent(null, null, $request->getClientIp())
);
}
return $this->render('@ChillMain/Password/request_recover_password.html.twig', [
'form' => $form->createView()
]);
}
public function requestRecoverConfirmAction()
{
return $this->render('@ChillMain/Password/request_recover_password_confirm.html.twig');
}
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();
}
}