Skip to content

Commit

Permalink
Forgot password with username or email or another property (#32)
Browse files Browse the repository at this point in the history
* reset password with username

* resolve discussions on reset password with username

* add unit tests on reset password with username

* update configuration

* return  after resetPassword

* resolve discussions

* Fix BC Break

* Fix code review

* Fix tests

* Fix code review

* PHP-CS-Fixer

* Fix phpunit test

* Fix tests

* Fix tests

* Fix Code review

* Fix Conflicts with master

* use strict mode for in_array
  • Loading branch information
daviddlv authored and vincentchalamon committed Nov 28, 2018
1 parent f78e02a commit c0d2208
Show file tree
Hide file tree
Showing 16 changed files with 216 additions and 52 deletions.
7 changes: 4 additions & 3 deletions Controller/ForgotPasswordController.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ public function __construct(
}

/**
* @param string $email
* @param string $propertyName
* @param string $value
*
* @return Response
*/
public function resetPasswordAction($email)
public function resetPasswordAction($propertyName, $value)
{
$this->forgotPasswordManager->resetPassword($email);
$this->forgotPasswordManager->resetPassword($propertyName, $value);

return new Response('', 204);
}
Expand Down
6 changes: 6 additions & 0 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ public function getConfigTreeBuilder()
->children()
->scalarNode('class')->cannotBeEmpty()->isRequired()->info('User class.')->end()
->scalarNode('email_field')->defaultValue('email')->cannotBeEmpty()->info('User email field name to retrieve it (email, username...).')->end()
->arrayNode('authorized_fields')
->defaultValue(['email'])
->requiresAtLeastOneElement()
->info('User fields names to retrieve it (email, username...).')
->prototype('scalar')->end()
->end()
->scalarNode('password_field')->defaultValue('password')->cannotBeEmpty()->info('User password field name.')->end()
->end()
->end()
Expand Down
4 changes: 3 additions & 1 deletion DependencyInjection/CoopTilleulsForgotPasswordExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ public function load(array $configs, ContainerBuilder $container)
$container->setParameter('coop_tilleuls_forgot_password.password_token_serialization_groups', $config['password_token']['serialization_groups']);

$container->setParameter('coop_tilleuls_forgot_password.user_class', $config['user']['class']);
$container->setParameter('coop_tilleuls_forgot_password.user_email_field', $config['user']['email_field']);
$config['user']['authorized_fields'] = array_unique(array_merge($config['user']['authorized_fields'], [$config['user']['email_field']]));
unset($config['user']['email_field']);
$container->setParameter('coop_tilleuls_forgot_password.user_authorized_fields', $config['user']['authorized_fields']);
$container->setParameter('coop_tilleuls_forgot_password.user_password_field', $config['user']['password_field']);

$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
Expand Down
40 changes: 32 additions & 8 deletions EventListener/RequestEventListener.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@

namespace CoopTilleuls\ForgotPasswordBundle\EventListener;

use CoopTilleuls\ForgotPasswordBundle\Exception\InvalidJsonHttpException;
use CoopTilleuls\ForgotPasswordBundle\Exception\MissingFieldHttpException;
use CoopTilleuls\ForgotPasswordBundle\Exception\NoParameterException;
use CoopTilleuls\ForgotPasswordBundle\Exception\UnauthorizedFieldException;
use CoopTilleuls\ForgotPasswordBundle\Manager\PasswordTokenManager;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
Expand All @@ -21,21 +24,21 @@
*/
final class RequestEventListener
{
private $userEmailField;
private $authorizedFields;
private $userPasswordField;
private $passwordTokenManager;

/**
* @param string $userEmailField
* @param array $authorizedFields
* @param string $userPasswordField
* @param PasswordTokenManager $passwordTokenManager
*/
public function __construct(
$userEmailField,
array $authorizedFields,
$userPasswordField,
PasswordTokenManager $passwordTokenManager
) {
$this->userEmailField = $userEmailField;
$this->authorizedFields = $authorizedFields;
$this->userPasswordField = $userPasswordField;
$this->passwordTokenManager = $passwordTokenManager;
}
Expand All @@ -52,12 +55,33 @@ public function decodeRequest(GetResponseEvent $event)
return;
}

$data = json_decode($request->getContent(), true);
$fieldName = 'coop_tilleuls_forgot_password.reset' === $routeName ? $this->userEmailField : $this->userPasswordField;
if (!isset($data[$fieldName]) || empty($data[$fieldName])) {
$content = $request->getContent();
$data = json_decode($content, true);
if (!empty($content) && JSON_ERROR_NONE !== json_last_error()) {
throw new InvalidJsonHttpException();
}
if (!is_array($data) || empty($data)) {
throw new NoParameterException();
}

$fieldName = key($data);
if (empty($data[$fieldName])) {
throw new MissingFieldHttpException($fieldName);
}
$request->attributes->set($fieldName, $data[$fieldName]);

if ('coop_tilleuls_forgot_password.reset' === $routeName) {
if (!in_array($fieldName, $this->authorizedFields, true)) {
throw new UnauthorizedFieldException($fieldName);
}
$request->attributes->set('propertyName', $fieldName);
$request->attributes->set('value', $data[$fieldName]);
} else {
if ($this->userPasswordField !== $fieldName) {
throw new MissingFieldHttpException($this->userPasswordField);
}

$request->attributes->set($fieldName, $data[$fieldName]);
}
}

public function getTokenFromRequest(GetResponseEvent $event)
Expand Down
22 changes: 22 additions & 0 deletions Exception/InvalidJsonHttpException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the CoopTilleulsForgotPasswordBundle package.
*
* (c) Vincent Chalamon <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace CoopTilleuls\ForgotPasswordBundle\Exception;

use Symfony\Component\HttpKernel\Exception\HttpException;

final class InvalidJsonHttpException extends HttpException implements JsonHttpExceptionInterface
{
public function __construct()
{
parent::__construct(400, 'Invalid JSON data.');
}
}
22 changes: 22 additions & 0 deletions Exception/NoParameterException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the CoopTilleulsForgotPasswordBundle package.
*
* (c) Vincent Chalamon <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace CoopTilleuls\ForgotPasswordBundle\Exception;

use Symfony\Component\HttpKernel\Exception\HttpException;

final class NoParameterException extends HttpException implements JsonHttpExceptionInterface
{
public function __construct()
{
parent::__construct(400, 'No parameter sent.');
}
}
22 changes: 22 additions & 0 deletions Exception/UnauthorizedFieldException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the CoopTilleulsForgotPasswordBundle package.
*
* (c) Vincent Chalamon <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace CoopTilleuls\ForgotPasswordBundle\Exception;

use Symfony\Component\HttpKernel\Exception\HttpException;

final class UnauthorizedFieldException extends HttpException implements JsonHttpExceptionInterface
{
public function __construct($propertyName)
{
parent::__construct(400, sprintf('The parameter "%s" is not authorized in your configuration.', $propertyName));
}
}
13 changes: 5 additions & 8 deletions Manager/ForgotPasswordManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,35 +25,32 @@ class ForgotPasswordManager
private $passwordTokenManager;
private $dispatcher;
private $userClass;
private $userEmailField;

/**
* @param PasswordTokenManager $passwordTokenManager
* @param EventDispatcherInterface $dispatcher
* @param ManagerInterface $manager
* @param string $userClass
* @param string $userEmailField
*/
public function __construct(
PasswordTokenManager $passwordTokenManager,
EventDispatcherInterface $dispatcher,
ManagerInterface $manager,
$userClass,
$userEmailField
$userClass
) {
$this->passwordTokenManager = $passwordTokenManager;
$this->dispatcher = $dispatcher;
$this->manager = $manager;
$this->userClass = $userClass;
$this->userEmailField = $userEmailField;
}

/**
* @param string $username
* @param $propertyName
* @param $value
*/
public function resetPassword($username)
public function resetPassword($propertyName, $value)
{
$user = $this->manager->findOneBy($this->userClass, [$this->userEmailField => $username]);
$user = $this->manager->findOneBy($this->userClass, [$propertyName => $value]);
if (null === $user) {
return;
}
Expand Down
4 changes: 1 addition & 3 deletions Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="coop_tilleuls_forgot_password.controller.forgot_password" class="CoopTilleuls\ForgotPasswordBundle\Controller\ForgotPasswordController" public="true">
<argument type="service" id="coop_tilleuls_forgot_password.manager.forgot_password" />
Expand All @@ -16,7 +15,6 @@
<argument type="service" id="event_dispatcher" />
<argument type="service" id="coop_tilleuls_forgot_password.manager" />
<argument>%coop_tilleuls_forgot_password.user_class%</argument>
<argument>%coop_tilleuls_forgot_password.user_email_field%</argument>
</service>

<service id="coop_tilleuls_forgot_password.manager.password_token" class="CoopTilleuls\ForgotPasswordBundle\Manager\PasswordTokenManager" public="true">
Expand All @@ -31,7 +29,7 @@
</service>

<service id="coop_tilleuls_forgot_password.event_listener.request" class="CoopTilleuls\ForgotPasswordBundle\EventListener\RequestEventListener">
<argument>%coop_tilleuls_forgot_password.user_email_field%</argument>
<argument>%coop_tilleuls_forgot_password.user_authorized_fields%</argument>
<argument>%coop_tilleuls_forgot_password.user_password_field%</argument>
<argument type="service" id="coop_tilleuls_forgot_password.manager.password_token" />
<tag name="kernel.event_listener" event="kernel.request" method="decodeRequest" />
Expand Down
3 changes: 3 additions & 0 deletions features/app/AppKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load
$c->loadFromExtension('coop_tilleuls_forgot_password', [
'password_token_class' => PasswordToken::class,
'user_class' => User::class,
'user' => [
'authorized_fields' => ['email', 'username'],
],
'use_jms_serializer' => 'jmsserializer' === $this->getEnvironment(),
]);

Expand Down
31 changes: 23 additions & 8 deletions features/app/TestBundle/Entity/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ final class User implements UserInterface
*/
private $email;

/**
* @var string
*
* @ORM\Column(type="string")
*/
private $username;

/**
* @var string
*
Expand All @@ -52,14 +59,6 @@ public function getId()
return $this->id;
}

/**
* {@inheritdoc}
*/
public function getUsername()
{
return $this->getEmail();
}

/**
* {@inheritdoc}
*/
Expand All @@ -76,6 +75,22 @@ public function setEmail($email)
$this->email = $email;
}

/**
* @return string
*/
public function getUsername()
{
return $this->username;
}

/**
* @param string $username
*/
public function setUsername($username)
{
$this->username = $username;
}

/**
* {@inheritdoc}
*/
Expand Down
16 changes: 11 additions & 5 deletions features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,12 @@ public function iHaveAnExpiredToken()

/**
* @Then I reset my password
* @Then I reset my password with my :propertyName ":value"
*
* @param string $propertyName
* @param string $value
*/
public function iResetMyPassword()
public function IResetMyPassword($propertyName = 'email', $value = '[email protected]')
{
$this->createUser();

Expand All @@ -97,11 +101,12 @@ public function iResetMyPassword()
[],
[],
['CONTENT_TYPE' => 'application/json'],
<<<'JSON'
sprintf(<<<'JSON'
{
"email": "[email protected]"
"%s": "%s"
}
JSON
, $propertyName, $value)
);
}

Expand Down Expand Up @@ -194,9 +199,9 @@ public function iResetMyPasswordUsingInvalidEmailAddress()
}

/**
* @Then I reset my password using no email address
* @Then I reset my password using no parameter
*/
public function iResetMyPasswordUsingNoEmailAddress()
public function iResetMyPasswordUsingNoParameter()
{
$this->client->request('POST', '/forgot_password/');
}
Expand Down Expand Up @@ -314,6 +319,7 @@ private function createUser()
{
$user = new User();
$user->setEmail('[email protected]');
$user->setUsername('JohnDoe');
$user->setPassword('password');
$this->doctrine->getManager()->persist($user);
$this->doctrine->getManager()->flush();
Expand Down
Loading

0 comments on commit c0d2208

Please sign in to comment.