From 04bdaa308a9b8500ebe8eac10d7719175aefbd68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 31 Aug 2018 17:04:45 +0200 Subject: [PATCH] script to send batch password recover code --- .../ChillUserSendRenewPasswordCodeCommand.php | 198 ++++++++++++++++++ Resources/config/services/command.yml | 5 + Resources/config/services/security.yml | 1 + Resources/config/validation.yml | 2 +- .../PasswordRecover/RecoverPasswordHelper.php | 45 +++- 5 files changed, 242 insertions(+), 9 deletions(-) create mode 100644 Command/ChillUserSendRenewPasswordCodeCommand.php diff --git a/Command/ChillUserSendRenewPasswordCodeCommand.php b/Command/ChillUserSendRenewPasswordCodeCommand.php new file mode 100644 index 000000000..69089ecf4 --- /dev/null +++ b/Command/ChillUserSendRenewPasswordCodeCommand.php @@ -0,0 +1,198 @@ +logger = $logger; + $this->em = $em; + $this->recoverPasswordHelper = $recoverPasswordHelper; + $this->eventDispatcher = $eventDispatcher; + + parent::__construct(); + } + + + + protected function configure() + { + $this + ->setName('chill:user:send-password-recover-code') + ->setDescription('Send a message with code to recover password') + ->addArgument('csvfile', InputArgument::REQUIRED, 'CSV file with the list of users') + ->addOption('template', null, InputOption::VALUE_REQUIRED, 'Template for email') + ->addOption('expiration', null, InputOption::VALUE_REQUIRED, 'Expiration of the link, as an unix timestamp') + ->addOption('subject', null, InputOption::VALUE_REQUIRED, 'Subject of the email', 'Recover your password') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->output = $output; + + $reader = $this->getReader(); + + foreach($reader->getRecords() as $offset => $r) { + $user = $this->getUser($r); + + if ($user === null) { + $this->onUserNotFound($r, $offset); + continue; + } + + $this->sendRecoverCode($user); + } + } + + protected function sendRecoverCode(User $user) + { + if (empty($user->getEmail())) { + $this->logger->alert("User without email", [ + 'user_id' => $user->getId(), + 'username' => $user->getUsername() + ]); + + return; + } + + $template = $this->input->getOption('template'); + $expiration = \DateTime::createFromFormat('U', + $this->input->getOption('expiration')); + + $this->recoverPasswordHelper + ->sendRecoverEmail( + $user, + $expiration, + $template, + [ 'expiration' => $expiration], + false, + [ '_locale' => 'fr' ], + $this->input->getOption('subject') + ); + } + + protected function onUserNotFound($row, $offset) + { + $this->logger->alert('User not found', \array_merge([ + 'offset' => $offset + ], $row)); + } + + protected function getUser($row) + { + /* @var $userRepository \Chill\MainBundle\Repository\UserRepository */ + $userRepository = $this->em->getRepository(User::class); + + try { + if (\array_key_exists('email', $row)) { + return $userRepository->findOneByUsernameOrEmail(\trim($row['email'])); + } + } catch (\Doctrine\ORM\NoResultException $e) { + // continue, we will try username + } + + try { + if (\array_key_exists('username', $row)) { + return $userRepository->findOneByUsernameOrEmail(\trim($row['username'])); + } + } catch (\Doctrine\ORM\NoResultException $e) { + return null; + } + } + + /** + * + * @return Reader + * @throws \Exception + */ + protected function getReader() + { + try { + $reader = Reader::createFromPath($this->input->getArgument('csvfile')); + } catch (\Exception $e) { + $this->logger->error("The csv file could not be read", [ + 'path' => $this->input->getArgument('csvfile') + ]); + + throw $e; + } + + $reader->setHeaderOffset(0); + + $headers = $reader->getHeader(); + + if (FALSE === \in_array('username', $headers) + && FALSE === \in_array('email', $headers)) { + throw new \InvalidArgumentException("The csv file does not have an " + . "username or email header"); + } + + return $reader; + } + +} diff --git a/Resources/config/services/command.yml b/Resources/config/services/command.yml index 51065922f..d06ce2cc4 100644 --- a/Resources/config/services/command.yml +++ b/Resources/config/services/command.yml @@ -8,3 +8,8 @@ services: tags: - { name: console.command } + + Chill\MainBundle\Command\ChillUserSendRenewPasswordCodeCommand: + autowire: true + tags: + - { name: console.command } diff --git a/Resources/config/services/security.yml b/Resources/config/services/security.yml index df35c0bf9..e341eb196 100644 --- a/Resources/config/services/security.yml +++ b/Resources/config/services/security.yml @@ -31,6 +31,7 @@ services: $tokenManager: '@Chill\MainBundle\Security\PasswordRecover\TokenManager' $urlGenerator: '@Symfony\Component\Routing\Generator\UrlGeneratorInterface' $mailer: '@Chill\MainBundle\Notification\Mailer' + $routeParameters: "%chill_main.notifications%" Chill\MainBundle\Security\PasswordRecover\PasswordRecoverEventSubscriber: arguments: diff --git a/Resources/config/validation.yml b/Resources/config/validation.yml index 141d489fc..5843e0427 100644 --- a/Resources/config/validation.yml +++ b/Resources/config/validation.yml @@ -33,7 +33,7 @@ Chill\MainBundle\Entity\Center: - NotBlank: ~ - Length: max: 50 - min: 3 + min: 2 Chill\MainBundle\Entity\Address: properties: diff --git a/Security/PasswordRecover/RecoverPasswordHelper.php b/Security/PasswordRecover/RecoverPasswordHelper.php index 7df4cf7e5..969f774a6 100644 --- a/Security/PasswordRecover/RecoverPasswordHelper.php +++ b/Security/PasswordRecover/RecoverPasswordHelper.php @@ -47,26 +47,53 @@ class RecoverPasswordHelper */ protected $mailer; + protected $routeParameters; + const RECOVER_PASSWORD_ROUTE = 'password_recover'; public function __construct( TokenManager $tokenManager, UrlGeneratorInterface $urlGenerator, - Mailer $mailer + Mailer $mailer, + array $routeParameters ) { $this->tokenManager = $tokenManager; $this->urlGenerator = $urlGenerator; $this->mailer = $mailer; + $this->routeParameters = $routeParameters; } - - public function generateUrl(User $user, \DateTimeInterface $expiration, $absolute = true) + /** + * + * @param User $user + * @param \DateTimeInterface $expiration + * @param bool $absolute + * @param array $parameters additional parameters to url + * @return string + */ + public function generateUrl(User $user, \DateTimeInterface $expiration, $absolute = true, array $parameters = []) { - return $this->urlGenerator->generate( + + $context = $this->urlGenerator->getContext(); + $previousHost = $context->getHost(); + $previousScheme = $context->getScheme(); + + $context->setHost($this->routeParameters['host']); + $context->setScheme($this->routeParameters['scheme']); + + $url = $this->urlGenerator->generate( self::RECOVER_PASSWORD_ROUTE, - $this->tokenManager->generate($user, $expiration), + \array_merge( + $this->tokenManager->generate($user, $expiration), + $parameters), $absolute ? UrlGeneratorInterface::ABSOLUTE_URL : UrlGeneratorInterface::ABSOLUTE_PATH ); + + // reset the host + $context->setHost($previousHost); + $context->setScheme($previousScheme); + + return $url; } public function sendRecoverEmail( @@ -74,21 +101,23 @@ class RecoverPasswordHelper \DateTimeInterface $expiration, $template = '@ChillMain/Password/recover_email.txt.twig', array $templateParameters = [], - $force = false + $force = false, + array $additionalUrlParameters = [], + $emailSubject = 'Recover your password' ) { $content = $this->mailer->renderContentToUser( $user, $template, \array_merge([ 'user' => $user, - 'url' => $this->generateUrl($user, $expiration, true) + 'url' => $this->generateUrl($user, $expiration, true, $additionalUrlParameters) ], $templateParameters )); $this->mailer->sendNotification( $user, - [ 'Recover your password' ], + [ $emailSubject ], [ 'text/plain' => $content, ],