<?php
namespace App\Controller;
use App\Form\UserPasswordType;
use App\Service\EmailService;
use GuzzleHttp\Client;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\FormError;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\User;
use Symfony\Contracts\Translation\TranslatorInterface;
class SecurityController extends AbstractController
{
private $em;
private $parameter;
private $encoder;
private $emailService;
private $translator;
public function __construct(
EntityManagerInterface $em,
ParameterBagInterface $parameter,
UserPasswordEncoderInterface $encoder,
EmailService $emailService,
TranslatorInterface $translator
)
{
$this->em = $em;
$this->parameter = $parameter;
$this->encoder = $encoder;
$this->emailService = $emailService;
$this->translator = $translator;
}
/**
* @Route("/{_locale}/login", name="login")
*/
public function login(Request $request, AuthenticationUtils $authUtils)
{
// get the login error if there is one
$error = $authUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authUtils->getLastUsername();
return $this->render('security/login.html.twig', array(
'last_username' => $lastUsername,
'error' => $error,
'redirect' => $request->getSession()->get('redirect_uri')
));
}
/**
* @Route("/json_login", name="json_login", methods={"POST"})
*/
public function loginJson(Request $request)
{
return $this->json([
'user' => $this->getUser() ? $this->getUser()->getId() : null]
);
}
/**
* Oops, I forgot my password!
* @Route("/{_locale}/forgot_password", name="forgot_password")
*/
public function forgotPassword(Request $request)
{
// make form
$form = $this->createFormBuilder()
->add('email', EmailType::class, array(
'label' => 'label.email',
'label_attr' => [
'class' => 'visually-hidden'
],
'attr' => [
'placeholder' => 'label.email',
'class' => 'form-control',
'autofocus' => true
]
))
->getForm()
;
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// get corresponding user
$email = $form->getData()["email"];
$user = $this->em->getRepository(User::class)->findOneBy(['email' => $email]);
if ($user) {
// if user exists then send mail for resetting password
$this->emailService->sendNouveauMDPMail($user, $request->getLocale());
return $this->render("default/simple.html.twig", array(
"content" => "connect.mail.sent",
));
}
// get back to email form, maybe it's a typo
$form->get('email')->addError(new FormError($this->translator->trans('connect.no.user.found')));
}
// form rendering
return $this->render('security/forgot_password.html.twig', array(
'form' => $form->createView(),
));
}
/**
* Reset a password, given a token and an email. (see forgotPassword function)
* @Route("/{_locale}/adduser_password", name="adduser_password")
*/
public function addUserPassword(Request $request)
{
$pass = $this->randomPassword();
return $this->genPassword($request,$pass, [
'route' => 'adduser_password',
'content_captcha_valide' => $this->translator->trans('connect.your.password.is') . ' : ' . $pass
]);
}
/**
* Reset a password, given a token and an email. (see forgotPassword function)
* @Route("/{_locale}/reset_password", name="reset_password")
*/
public function resetPassword(Request $request)
{
$generatePassOnReset = getenv("GENERATE_PASS_ON_RESET") === "false" ? false : true;
if ($generatePassOnReset) {
// User is given a random password
$pass = $this->randomPassword();
return $this->genPassword($request, $pass, [
'route' => 'reset_password',
'content_captcha_valide' => $this->translator->trans('connect.your.new.password.is') . ' : ' . $pass
]);
} else {
// User can write its own new password
return $this->formChangePassword($request);
}
}
private function genPassword($request, $password, $options){
$email = $request->query->get('email'); // no use urlencode if you need your url is redirect bad
$token = $request->query->get('token');
$captcha_token = $request->request->get("g-recaptcha-response");
if ($captcha_token && $this->isValid($captcha_token)) {
// reset password
// get user
/** @var User $user */
$user = $this->em->getRepository(User::class)->findByTokenAndEmail($token, $email);
// aucun utilisateur correspondant
if (!$user) {
return $this->render("security/client-error.html.twig", ["error_message" => "connect.invalid.link"]);
}
// Changement mdp
$user->setPassword($this->encoder->encodePassword($user, $password));
$user->setResetToken(null);
$user->setResetDate(new \DateTime());
if ($user->getActivatedAt() == null) {
$user->setActivatedAt(new \DateTime());
$user->setIsActive(true);
}
if ($options['route'] === 'adduser_password') {
$user->setIsActive(true);
}
$this->em->flush();
// On redirige vers la page d'accueil
return $this->render("default/simple.html.twig", array(
"content" => $options['content_captcha_valide'],
"redirect_url" => getenv("RESET_PASS_URL_REDIRECT"),
));
}
// show captcha
return $this->render("security/reset_password_captcha.html.twig", array(
"email" => $email,
"token" => $token,
"key" => $this->parameter->get('captcha_key'),
"route" => $options['route']
));
}
private function formChangePassword(Request $request): Response
{
$email = $request->query->get('email');
$token = $request->query->get('token');
// Given a token & mail, check if an user corresponds
/** @var User $user */
$user = $this->em->getRepository(User::class)->findByTokenAndEmail($token, $email);
// No user found, token is invalid
if (!$user) {
return $this->render("security/client-error.html.twig", ["error_message" => "connect.invalid.link"]);
}
// Build form
$form = $this->createForm(UserPasswordType::class, $user, [
"action" => $request->getUri(),
]);
$form->handleRequest($request);
// Form processing
if ($form->isSubmitted() && $form->isValid()) {
$user->setPassword($this->encoder->encodePassword($user, $user->getPlainPassword()));
if ($user->getActivatedAt() == null) {
$user->setActivatedAt(new \DateTime());
$user->setIsActive(true);
}
$this->em->persist($user);
$this->em->flush();
$customRedirectUrl = getenv("RESET_PASS_URL_REDIRECT");
if ($customRedirectUrl) {
// On redirige vers l'url personnalisée
return $this->redirect($customRedirectUrl);
} else {
// On redirige vers le login
return $this->render("security/login.html.twig", [
"success_msg" => "security.label.password_changed_success",
"error" => false,
"last_username" => "",
]);
}
}
return $this->render("security/change_password.html.twig", [
"form" => $form->createView(),
]);
}
/**
* Generates random password.
* @see https://stackoverflow.com/questions/4356289/php-random-string-generator/31107425#31107425
*/
private function randomPassword()
{
$keyspace = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ&*";
$pieces = [];
$max = mb_strlen($keyspace, '8bit') - 1;
for ($i = 0; $i < 10; ++$i) {
$pieces []= $keyspace[random_int(0, $max)];
}
return implode('', $pieces);
}
private function isValid($token)
{
$client = new Client();
$url = "https://www.google.com/recaptcha/api/siteverify?secret=" . $this->parameter->get('captcha_secret') . "&response=" . $token;
$response = $client->get($url);
$json = json_decode($response->getBody(),true);
return $json["success"];
}
}