-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[tests] test bundle against actual symfony app
- Loading branch information
Showing
18 changed files
with
672 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
APP_ENV=dev | ||
APP_SECRET=7e6cd3398232b047dc249e51729039fa | ||
DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" | ||
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0 | ||
MAILER_DSN=null://null |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
symfonycasts_reset_password: | ||
request_password_repository: App\Repository\ResetPasswordRequestRepository |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
controllers: | ||
resource: | ||
path: ../src/Controller/ | ||
namespace: App\Controller | ||
type: attribute | ||
|
||
app_home: | ||
path: / | ||
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController | ||
defaults: | ||
template: 'base.html.twig' | ||
statusCode: 200 |
185 changes: 185 additions & 0 deletions
185
tests/Fixtures/App/src/Controller/ResetPasswordController.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the SymfonyCasts ResetPasswordBundle package. | ||
* Copyright (c) SymfonyCasts <https://symfonycasts.com/> | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace App\Controller; | ||
|
||
use App\Entity\User; | ||
use App\Form\ChangePasswordFormType; | ||
use App\Form\ResetPasswordRequestFormType; | ||
use Doctrine\ORM\EntityManagerInterface; | ||
use Symfony\Bridge\Twig\Mime\TemplatedEmail; | ||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | ||
use Symfony\Component\HttpFoundation\RedirectResponse; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\Mailer\MailerInterface; | ||
use Symfony\Component\Mime\Address; | ||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; | ||
use Symfony\Component\Routing\Attribute\Route; | ||
use Symfony\Contracts\Translation\TranslatorInterface; | ||
use SymfonyCasts\Bundle\ResetPassword\Controller\ResetPasswordControllerTrait; | ||
use SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface; | ||
use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelperInterface; | ||
|
||
#[Route('/reset-password')] | ||
class ResetPasswordController extends AbstractController | ||
{ | ||
use ResetPasswordControllerTrait; | ||
|
||
public function __construct( | ||
private ResetPasswordHelperInterface $resetPasswordHelper, | ||
private EntityManagerInterface $entityManager | ||
) { | ||
} | ||
|
||
/** | ||
* Display & process form to request a password reset. | ||
*/ | ||
#[Route('', name: 'app_forgot_password_request')] | ||
public function request(Request $request, MailerInterface $mailer, TranslatorInterface $translator): Response | ||
{ | ||
$form = $this->createForm(ResetPasswordRequestFormType::class); | ||
$form->handleRequest($request); | ||
|
||
if ($form->isSubmitted() && $form->isValid()) { | ||
return $this->processSendingPasswordResetEmail( | ||
$form->get('email')->getData(), | ||
$mailer, | ||
$translator | ||
); | ||
} | ||
|
||
return $this->render('reset_password/request.html.twig', [ | ||
'requestForm' => $form, | ||
]); | ||
} | ||
|
||
/** | ||
* Confirmation page after a user has requested a password reset. | ||
*/ | ||
#[Route('/check-email', name: 'app_check_email')] | ||
public function checkEmail(): Response | ||
{ | ||
// Generate a fake token if the user does not exist or someone hit this page directly. | ||
// This prevents exposing whether or not a user was found with the given email address or not | ||
if (null === ($resetToken = $this->getTokenObjectFromSession())) { | ||
$resetToken = $this->resetPasswordHelper->generateFakeResetToken(); | ||
} | ||
|
||
return $this->render('reset_password/check_email.html.twig', [ | ||
'resetToken' => $resetToken, | ||
]); | ||
} | ||
|
||
/** | ||
* Validates and process the reset URL that the user clicked in their email. | ||
*/ | ||
#[Route('/reset/{token}', name: 'app_reset_password')] | ||
public function reset(Request $request, UserPasswordHasherInterface $passwordHasher, TranslatorInterface $translator, ?string $token = null): Response | ||
{ | ||
if ($token) { | ||
// We store the token in session and remove it from the URL, to avoid the URL being | ||
// loaded in a browser and potentially leaking the token to 3rd party JavaScript. | ||
$this->storeTokenInSession($token); | ||
|
||
return $this->redirectToRoute('app_reset_password'); | ||
} | ||
|
||
$token = $this->getTokenFromSession(); | ||
|
||
if (null === $token) { | ||
throw $this->createNotFoundException('No reset password token found in the URL or in the session.'); | ||
} | ||
|
||
try { | ||
/** @var User $user */ | ||
$user = $this->resetPasswordHelper->validateTokenAndFetchUser($token); | ||
} catch (ResetPasswordExceptionInterface $e) { | ||
$this->addFlash('reset_password_error', sprintf( | ||
'%s - %s', | ||
$translator->trans(ResetPasswordExceptionInterface::MESSAGE_PROBLEM_VALIDATE, [], 'ResetPasswordBundle'), | ||
$translator->trans($e->getReason(), [], 'ResetPasswordBundle') | ||
)); | ||
|
||
return $this->redirectToRoute('app_forgot_password_request'); | ||
} | ||
|
||
// The token is valid; allow the user to change their password. | ||
$form = $this->createForm(ChangePasswordFormType::class); | ||
$form->handleRequest($request); | ||
|
||
if ($form->isSubmitted() && $form->isValid()) { | ||
// A password reset token should be used only once, remove it. | ||
$this->resetPasswordHelper->removeResetRequest($token); | ||
|
||
// Encode(hash) the plain password, and set it. | ||
$encodedPassword = $passwordHasher->hashPassword( | ||
$user, | ||
$form->get('plainPassword')->getData() | ||
); | ||
|
||
$user->setPassword($encodedPassword); | ||
$this->entityManager->flush(); | ||
|
||
// The session is cleaned up after the password has been changed. | ||
$this->cleanSessionAfterReset(); | ||
|
||
return $this->redirectToRoute('app_home'); | ||
} | ||
|
||
return $this->render('reset_password/reset.html.twig', [ | ||
'resetForm' => $form, | ||
]); | ||
} | ||
|
||
private function processSendingPasswordResetEmail(string $emailFormData, MailerInterface $mailer, TranslatorInterface $translator): RedirectResponse | ||
{ | ||
$user = $this->entityManager->getRepository(User::class)->findOneBy([ | ||
'email' => $emailFormData, | ||
]); | ||
|
||
// Do not reveal whether a user account was found or not. | ||
if (!$user) { | ||
return $this->redirectToRoute('app_check_email'); | ||
} | ||
|
||
try { | ||
$resetToken = $this->resetPasswordHelper->generateResetToken($user); | ||
} catch (ResetPasswordExceptionInterface $e) { | ||
// If you want to tell the user why a reset email was not sent, uncomment | ||
// the lines below and change the redirect to 'app_forgot_password_request'. | ||
// Caution: This may reveal if a user is registered or not. | ||
// | ||
// $this->addFlash('reset_password_error', sprintf( | ||
// '%s - %s', | ||
// $translator->trans(ResetPasswordExceptionInterface::MESSAGE_PROBLEM_HANDLE, [], 'ResetPasswordBundle'), | ||
// $translator->trans($e->getReason(), [], 'ResetPasswordBundle') | ||
// )); | ||
|
||
return $this->redirectToRoute('app_check_email'); | ||
} | ||
|
||
$email = (new TemplatedEmail()) | ||
->from(new Address('bot@example.com', 'SymfonyCasts')) | ||
->to($user->getEmail()) | ||
->subject('Your password reset request') | ||
->htmlTemplate('reset_password/email.html.twig') | ||
->context([ | ||
'resetToken' => $resetToken, | ||
]) | ||
; | ||
|
||
$mailer->send($email); | ||
|
||
// Store the token object in session for retrieval in check-email route. | ||
$this->setTokenObjectInSession($resetToken); | ||
|
||
return $this->redirectToRoute('app_check_email'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the SymfonyCasts ResetPasswordBundle package. | ||
* Copyright (c) SymfonyCasts <https://symfonycasts.com/> | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace App\Entity; | ||
|
||
use App\Repository\ResetPasswordRequestRepository; | ||
use Doctrine\ORM\Mapping as ORM; | ||
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestInterface; | ||
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestTrait; | ||
|
||
#[ORM\Entity(repositoryClass: ResetPasswordRequestRepository::class)] | ||
class ResetPasswordRequest implements ResetPasswordRequestInterface | ||
{ | ||
use ResetPasswordRequestTrait; | ||
|
||
#[ORM\Id] | ||
#[ORM\GeneratedValue] | ||
#[ORM\Column] | ||
private ?int $id = null; | ||
|
||
#[ORM\ManyToOne] | ||
#[ORM\JoinColumn(nullable: false)] | ||
private ?User $user = null; | ||
|
||
public function __construct(User $user, \DateTimeInterface $expiresAt, string $selector, string $hashedToken) | ||
{ | ||
$this->user = $user; | ||
$this->initialize($expiresAt, $selector, $hashedToken); | ||
} | ||
|
||
public function getId(): ?int | ||
{ | ||
return $this->id; | ||
} | ||
|
||
public function getUser(): User | ||
{ | ||
return $this->user; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the SymfonyCasts ResetPasswordBundle package. | ||
* Copyright (c) SymfonyCasts <https://symfonycasts.com/> | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace App\Form; | ||
|
||
use Symfony\Component\Form\AbstractType; | ||
use Symfony\Component\Form\Extension\Core\Type\PasswordType; | ||
use Symfony\Component\Form\Extension\Core\Type\RepeatedType; | ||
use Symfony\Component\Form\FormBuilderInterface; | ||
use Symfony\Component\OptionsResolver\OptionsResolver; | ||
use Symfony\Component\Validator\Constraints\Length; | ||
use Symfony\Component\Validator\Constraints\NotBlank; | ||
use Symfony\Component\Validator\Constraints\NotCompromisedPassword; | ||
use Symfony\Component\Validator\Constraints\PasswordStrength; | ||
|
||
class ChangePasswordFormType extends AbstractType | ||
{ | ||
public function buildForm(FormBuilderInterface $builder, array $options): void | ||
{ | ||
$builder | ||
->add('plainPassword', RepeatedType::class, [ | ||
'type' => PasswordType::class, | ||
'options' => [ | ||
'attr' => [ | ||
'autocomplete' => 'new-password', | ||
], | ||
], | ||
'first_options' => [ | ||
'constraints' => [ | ||
new NotBlank([ | ||
'message' => 'Please enter a password', | ||
]), | ||
new Length([ | ||
'min' => 12, | ||
'minMessage' => 'Your password should be at least {{ limit }} characters', | ||
// max length allowed by Symfony for security reasons | ||
'max' => 4096, | ||
]), | ||
new PasswordStrength(), | ||
new NotCompromisedPassword(), | ||
], | ||
'label' => 'New password', | ||
], | ||
'second_options' => [ | ||
'label' => 'Repeat Password', | ||
], | ||
'invalid_message' => 'The password fields must match.', | ||
// Instead of being set onto the object directly, | ||
// this is read and encoded in the controller | ||
'mapped' => false, | ||
]) | ||
; | ||
} | ||
|
||
public function configureOptions(OptionsResolver $resolver): void | ||
{ | ||
$resolver->setDefaults([]); | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
tests/Fixtures/App/src/Form/ResetPasswordRequestFormType.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the SymfonyCasts ResetPasswordBundle package. | ||
* Copyright (c) SymfonyCasts <https://symfonycasts.com/> | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace App\Form; | ||
|
||
use Symfony\Component\Form\AbstractType; | ||
use Symfony\Component\Form\Extension\Core\Type\EmailType; | ||
use Symfony\Component\Form\FormBuilderInterface; | ||
use Symfony\Component\OptionsResolver\OptionsResolver; | ||
use Symfony\Component\Validator\Constraints\NotBlank; | ||
|
||
class ResetPasswordRequestFormType extends AbstractType | ||
{ | ||
public function buildForm(FormBuilderInterface $builder, array $options): void | ||
{ | ||
$builder | ||
->add('email', EmailType::class, [ | ||
'attr' => ['autocomplete' => 'email'], | ||
'constraints' => [ | ||
new NotBlank([ | ||
'message' => 'Please enter your email', | ||
]), | ||
], | ||
]) | ||
; | ||
} | ||
|
||
public function configureOptions(OptionsResolver $resolver): void | ||
{ | ||
$resolver->setDefaults([]); | ||
} | ||
} |
Oops, something went wrong.