script to send batch password recover code

This commit is contained in:
Julien Fastré 2018-08-31 17:04:45 +02:00
parent f6b6ec57bb
commit 04bdaa308a
5 changed files with 242 additions and 9 deletions

View File

@ -0,0 +1,198 @@
<?php
namespace Chill\MainBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use League\Csv\Reader;
use Psr\Log\LoggerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Notification\Mailer;
use Chill\MainBundle\Security\PasswordRecover\RecoverPasswordHelper;
use Chill\MainBundle\Security\PasswordRecover\PasswordRecoverEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class ChillUserSendRenewPasswordCodeCommand extends ContainerAwareCommand
{
/**
*
* @var LoggerInterface
*/
protected $logger;
/**
*
* @var EntityManagerInterface
*/
protected $em;
/**
*
* @var Mailer
*/
protected $mailer;
/**
*
* @var RecoverPasswordHelper
*/
protected $recoverPasswordHelper;
/**
*
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* The current input interface
*
* @var InputInterface
*/
private $input;
/**
* The current output interface
*
* @var OutputInterface
*/
private $output;
public function __construct(
LoggerInterface $logger,
EntityManagerInterface $em,
RecoverPasswordHelper $recoverPasswordHelper,
EventDispatcherInterface $eventDispatcher)
{
$this->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;
}
}

View File

@ -8,3 +8,8 @@ services:
tags:
- { name: console.command }
Chill\MainBundle\Command\ChillUserSendRenewPasswordCodeCommand:
autowire: true
tags:
- { name: console.command }

View File

@ -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:

View File

@ -33,7 +33,7 @@ Chill\MainBundle\Entity\Center:
- NotBlank: ~
- Length:
max: 50
min: 3
min: 2
Chill\MainBundle\Entity\Address:
properties:

View File

@ -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,
],