feat: backup previous password, change password via personal data form
This commit is contained in:
1 parent
c3508403c0
commit
e4430ac5c5
9 files changed
+285
-13
No files matched your search
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20260528120000 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add previous password backup to users';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE user ADD previous_password LONGTEXT DEFAULT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE user DROP previous_password');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ use App\BusProNet\Model\Notification;
|
|||||||
use App\BusProNet\Model\PersonalData;
|
use App\BusProNet\Model\PersonalData;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Exception\NewsletterProviderException;
|
use App\Exception\NewsletterProviderException;
|
||||||
use App\Form\PersonalDataType;
|
use App\Form\AccountPersonalDataType;
|
||||||
use App\Htmx\HxTrait;
|
use App\Htmx\HxTrait;
|
||||||
use App\Model\NewsletterSubscriptionRequestResult;
|
use App\Model\NewsletterSubscriptionRequestResult;
|
||||||
use App\Service\BookingEditDataLoader;
|
use App\Service\BookingEditDataLoader;
|
||||||
@@ -20,6 +20,7 @@ use Psr\Log\LoggerInterface;
|
|||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
@@ -43,6 +44,7 @@ class PersonalDataController extends AbstractController
|
|||||||
private readonly ValidatorInterface $validator,
|
private readonly ValidatorInterface $validator,
|
||||||
private readonly EntityManagerInterface $entityManager,
|
private readonly EntityManagerInterface $entityManager,
|
||||||
private readonly NewsletterManager $newsletterManager,
|
private readonly NewsletterManager $newsletterManager,
|
||||||
|
private readonly UserPasswordHasherInterface $passwordHasher,
|
||||||
private readonly LoggerInterface $logger,
|
private readonly LoggerInterface $logger,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
@@ -71,9 +73,8 @@ class PersonalDataController extends AbstractController
|
|||||||
$newsletterSubscribed = $this->newsletterManager->hasConfirmedOptIn($email);
|
$newsletterSubscribed = $this->newsletterManager->hasConfirmedOptIn($email);
|
||||||
$newsletterPendingConfirmation = $this->newsletterManager->hasPendingConfirmation($email);
|
$newsletterPendingConfirmation = $this->newsletterManager->hasPendingConfirmation($email);
|
||||||
|
|
||||||
$personalDataForm = $this->createForm(PersonalDataType::class, $personalData, [
|
$personalDataForm = $this->createForm(AccountPersonalDataType::class, $personalData, [
|
||||||
'attr' => ['novalidate' => 'novalidate'],
|
'attr' => ['novalidate' => 'novalidate'],
|
||||||
'validation_groups' => ['personal_data'],
|
|
||||||
]);
|
]);
|
||||||
$personalDataForm->handleRequest($request);
|
$personalDataForm->handleRequest($request);
|
||||||
|
|
||||||
@@ -81,6 +82,11 @@ class PersonalDataController extends AbstractController
|
|||||||
try {
|
try {
|
||||||
$this->apiClient->updatePersonalData($email, $personalData);
|
$this->apiClient->updatePersonalData($email, $personalData);
|
||||||
|
|
||||||
|
$newPassword = (string) $personalDataForm->get('newPassword')->getData();
|
||||||
|
if ('' !== $newPassword) {
|
||||||
|
$user->setPassword($this->passwordHasher->hashPassword($user, $newPassword));
|
||||||
|
}
|
||||||
|
|
||||||
// Invalidate cached bookings to ensure edit mode shows updated applicant data
|
// Invalidate cached bookings to ensure edit mode shows updated applicant data
|
||||||
$this->dataLoader->invalidateUserBookingCaches($user);
|
$this->dataLoader->invalidateUserBookingCaches($user);
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||||||
#[ORM\Column(type: 'text')]
|
#[ORM\Column(type: 'text')]
|
||||||
private ?string $password = null;
|
private ?string $password = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'text', nullable: true)]
|
||||||
|
private ?string $previousPassword = null;
|
||||||
|
|
||||||
#[ORM\Column(type: 'integer', nullable: true)]
|
#[ORM\Column(type: 'integer', nullable: true)]
|
||||||
private ?int $personId = null;
|
private ?int $personId = null;
|
||||||
|
|
||||||
@@ -106,6 +109,18 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPreviousPassword(): ?string
|
||||||
|
{
|
||||||
|
return $this->previousPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPreviousPassword(?string $previousPassword): static
|
||||||
|
{
|
||||||
|
$this->previousPassword = $previousPassword;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function hasLocalPasswordHash(): bool
|
public function hasLocalPasswordHash(): bool
|
||||||
{
|
{
|
||||||
if (null === $this->password || '' === $this->password) {
|
if (null === $this->password || '' === $this->password) {
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\BusProNet\Model\PersonalData;
|
||||||
|
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\Form\FormInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
use Symfony\Component\Security\Core\Validator\Constraints\UserPassword;
|
||||||
|
use Symfony\Component\Validator\Constraints\Length;
|
||||||
|
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||||
|
use Symfony\Component\Validator\Constraints\Sequentially;
|
||||||
|
|
||||||
|
/** @extends AbstractType<PersonalData> */
|
||||||
|
class AccountPersonalDataType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('personalData', PersonalDataType::class, [
|
||||||
|
'label' => false,
|
||||||
|
'inherit_data' => true,
|
||||||
|
])
|
||||||
|
->add('currentPassword', PasswordType::class, [
|
||||||
|
'label' => 'Aktuelles Passwort',
|
||||||
|
'mapped' => false,
|
||||||
|
'required' => false,
|
||||||
|
'attr' => [
|
||||||
|
'autocomplete' => 'current-password',
|
||||||
|
],
|
||||||
|
'constraints' => [
|
||||||
|
new Sequentially([
|
||||||
|
new NotBlank([
|
||||||
|
'message' => 'Bitte angeben',
|
||||||
|
]),
|
||||||
|
new UserPassword([
|
||||||
|
'message' => 'Das aktuelle Passwort ist nicht korrekt.',
|
||||||
|
]),
|
||||||
|
], groups: ['password_change']),
|
||||||
|
],
|
||||||
|
])
|
||||||
|
->add('newPassword', RepeatedType::class, [
|
||||||
|
'type' => PasswordType::class,
|
||||||
|
'mapped' => false,
|
||||||
|
'required' => false,
|
||||||
|
'invalid_message' => 'Die beiden Passwortangaben müssen identisch sein.',
|
||||||
|
'first_options' => [
|
||||||
|
'label' => 'Neues Passwort',
|
||||||
|
'required' => false,
|
||||||
|
'attr' => [
|
||||||
|
'autocomplete' => 'new-password',
|
||||||
|
],
|
||||||
|
'constraints' => [
|
||||||
|
new Sequentially([
|
||||||
|
new NotBlank([
|
||||||
|
'message' => 'Bitte angeben',
|
||||||
|
]),
|
||||||
|
new Length(
|
||||||
|
min: 8,
|
||||||
|
minMessage: 'Das Passwort muss mindestens {{ limit }} Zeichen lang sein.',
|
||||||
|
),
|
||||||
|
], groups: ['password_change']),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'second_options' => [
|
||||||
|
'label' => 'Neues Passwort wiederholen',
|
||||||
|
'required' => false,
|
||||||
|
'attr' => [
|
||||||
|
'autocomplete' => 'new-password',
|
||||||
|
],
|
||||||
|
'constraints' => [
|
||||||
|
new NotBlank([
|
||||||
|
'message' => 'Bitte angeben',
|
||||||
|
'groups' => ['password_change'],
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => PersonalData::class,
|
||||||
|
'validation_groups' => function (FormInterface $form): array {
|
||||||
|
$groups = ['Default', 'personal_data'];
|
||||||
|
|
||||||
|
if ($this->isPasswordSectionUsed($form)) {
|
||||||
|
$groups[] = 'password_change';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $groups;
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isPasswordSectionUsed(FormInterface $form): bool
|
||||||
|
{
|
||||||
|
return '' !== (string) $form->get('currentPassword')->getData()
|
||||||
|
|| '' !== (string) $form->get('newPassword')->getData();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -88,6 +88,9 @@ class BpnAuthenticator extends AbstractLoginFormAuthenticator implements Authent
|
|||||||
throw new CustomUserMessageAuthenticationException('Benutzername oder Passwort ist nicht korrekt.');
|
throw new CustomUserMessageAuthenticationException('Benutzername oder Passwort ist nicht korrekt.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (null === $user->getPreviousPassword()) {
|
||||||
|
$user->setPreviousPassword($user->getPassword());
|
||||||
|
}
|
||||||
$user->setPassword($this->passwordHasher->hashPassword($user, $passwordPlain));
|
$user->setPassword($this->passwordHasher->hashPassword($user, $passwordPlain));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,29 +44,37 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="pt-8">
|
<div class="pt-8">
|
||||||
{{ form_start(personalDataForm) }}
|
{{ form_start(personalDataForm) }}
|
||||||
|
{% from '_partials/_validation_errors.html.twig' import validation_alert %}
|
||||||
|
{{ validation_alert(personalDataForm, 'Bitte prüfe deine Angaben und versuche es erneut', false) }}
|
||||||
<div class="grid md:grid-cols-2 gap-x-8 gap-y-4 pb-4">
|
<div class="grid md:grid-cols-2 gap-x-8 gap-y-4 pb-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-white">
|
<h3 class="text-white">
|
||||||
Persönliches
|
Persönliches
|
||||||
</h3>
|
</h3>
|
||||||
{{ form_row(personalDataForm.gender, { 'label_attr': { 'class': 'text-white' } }) }}
|
{{ form_row(personalDataForm.personalData.gender, { 'label_attr': { 'class': 'text-white' } }) }}
|
||||||
{{ form_row(personalDataForm.dateOfBirth, { 'label_attr': { 'class': 'text-white' } }) }}
|
{{ form_row(personalDataForm.personalData.dateOfBirth, { 'label_attr': { 'class': 'text-white' } }) }}
|
||||||
{{ form_row(personalDataForm.nationality, { 'label_attr': { 'class': 'text-white' } }) }}
|
{{ form_row(personalDataForm.personalData.nationality, { 'label_attr': { 'class': 'text-white' } }) }}
|
||||||
<h3 class="text-white">
|
<h3 class="text-white">
|
||||||
Kontakt
|
Kontakt
|
||||||
</h3>
|
</h3>
|
||||||
{{ form_row(personalDataForm.email, { 'label_attr': { 'class': 'text-white' } }) }}
|
{{ form_row(personalDataForm.personalData.email, { 'label_attr': { 'class': 'text-white' } }) }}
|
||||||
{{ form_row(personalDataForm.mobile, { 'label_attr': { 'class': 'text-white' } }) }}
|
{{ form_row(personalDataForm.personalData.mobile, { 'label_attr': { 'class': 'text-white' } }) }}
|
||||||
{{ form_row(personalDataForm.phone, { 'label_attr': { 'class': 'text-white' } }) }}
|
{{ form_row(personalDataForm.personalData.phone, { 'label_attr': { 'class': 'text-white' } }) }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-white">
|
<h3 class="text-white">
|
||||||
Anschrift
|
Anschrift
|
||||||
</h3>
|
</h3>
|
||||||
{{ form_row(personalDataForm.street, { 'label_attr': { 'class': 'text-white' } }) }}
|
{{ form_row(personalDataForm.personalData.street, { 'label_attr': { 'class': 'text-white' } }) }}
|
||||||
{{ form_row(personalDataForm.postCode, { 'label_attr': { 'class': 'text-white' } }) }}
|
{{ form_row(personalDataForm.personalData.postCode, { 'label_attr': { 'class': 'text-white' } }) }}
|
||||||
{{ form_row(personalDataForm.city, { 'label_attr': { 'class': 'text-white' } }) }}
|
{{ form_row(personalDataForm.personalData.city, { 'label_attr': { 'class': 'text-white' } }) }}
|
||||||
{{ form_row(personalDataForm.country, { 'label_attr': { 'class': 'text-white' } }) }}
|
{{ form_row(personalDataForm.personalData.country, { 'label_attr': { 'class': 'text-white' } }) }}
|
||||||
|
<h3 class="text-white">
|
||||||
|
Passwort ändern
|
||||||
|
</h3>
|
||||||
|
{{ form_row(personalDataForm.currentPassword, { 'label_attr': { 'class': 'text-white' } }) }}
|
||||||
|
{{ form_row(personalDataForm.newPassword.first, { 'label_attr': { 'class': 'text-white' } }) }}
|
||||||
|
{{ form_row(personalDataForm.newPassword.second, { 'label_attr': { 'class': 'text-white' } }) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ use Symfony\Component\HttpFoundation\Session\Session;
|
|||||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
use Symfony\Component\Security\Core\User\UserInterface;
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
use Symfony\Component\Validator\Constraint;
|
use Symfony\Component\Validator\Constraint;
|
||||||
use Symfony\Component\Validator\ConstraintValidatorFactory;
|
use Symfony\Component\Validator\ConstraintValidatorFactory;
|
||||||
use Symfony\Component\Validator\ConstraintValidatorInterface;
|
use Symfony\Component\Validator\ConstraintValidatorInterface;
|
||||||
@@ -189,6 +190,7 @@ class PersonalDataControllerTest extends TestCase
|
|||||||
$controller->entityManager
|
$controller->entityManager
|
||||||
->expects(self::once())
|
->expects(self::once())
|
||||||
->method('flush');
|
->method('flush');
|
||||||
|
$controller->passwordHasher->expects(self::never())->method('hashPassword');
|
||||||
|
|
||||||
$request = Request::create('/personal-data', 'POST');
|
$request = Request::create('/personal-data', 'POST');
|
||||||
$request->setSession(new Session(new MockArraySessionStorage()));
|
$request->setSession(new Session(new MockArraySessionStorage()));
|
||||||
@@ -196,18 +198,108 @@ class PersonalDataControllerTest extends TestCase
|
|||||||
|
|
||||||
self::assertSame(302, $response->getStatusCode());
|
self::assertSame(302, $response->getStatusCode());
|
||||||
self::assertTrue($controller->user->isProfileComplete());
|
self::assertTrue($controller->user->isProfileComplete());
|
||||||
|
self::assertSame('secret', $controller->user->getPassword());
|
||||||
|
self::assertSame('legacy-backup', $controller->user->getPreviousPassword());
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
['success', 'Deine persönlichen Daten wurden aktualisiert'],
|
['success', 'Deine persönlichen Daten wurden aktualisiert'],
|
||||||
], $controller->flashes);
|
], $controller->flashes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testIndexHashesPasswordWhenNewPasswordWasProvided(): void
|
||||||
|
{
|
||||||
|
$personalData = $this->createCompletePersonalData();
|
||||||
|
$controller = $this->createController(true, true, $this->validator(), 'NewPassw0rd!');
|
||||||
|
|
||||||
|
$controller->apiClient
|
||||||
|
->expects(self::once())
|
||||||
|
->method('getPersonalData')
|
||||||
|
->with('[email protected]', null, null)
|
||||||
|
->willReturn($personalData);
|
||||||
|
$controller->apiClient
|
||||||
|
->expects(self::once())
|
||||||
|
->method('updatePersonalData')
|
||||||
|
->with('[email protected]', $personalData)
|
||||||
|
->willReturn($personalData);
|
||||||
|
$controller->newsletterManager
|
||||||
|
->expects(self::once())
|
||||||
|
->method('hasConfirmedOptIn')
|
||||||
|
->with('[email protected]')
|
||||||
|
->willReturn(false);
|
||||||
|
$controller->newsletterManager
|
||||||
|
->expects(self::once())
|
||||||
|
->method('hasPendingConfirmation')
|
||||||
|
->with('[email protected]')
|
||||||
|
->willReturn(false);
|
||||||
|
$controller->dataLoader
|
||||||
|
->expects(self::once())
|
||||||
|
->method('invalidateUserBookingCaches')
|
||||||
|
->with($controller->user);
|
||||||
|
$controller->entityManager
|
||||||
|
->expects(self::once())
|
||||||
|
->method('flush');
|
||||||
|
$controller->passwordHasher
|
||||||
|
->expects(self::once())
|
||||||
|
->method('hashPassword')
|
||||||
|
->with($controller->user, 'NewPassw0rd!')
|
||||||
|
->willReturn('new-local-hash');
|
||||||
|
|
||||||
|
$request = Request::create('/personal-data', 'POST');
|
||||||
|
$request->setSession(new Session(new MockArraySessionStorage()));
|
||||||
|
$response = $controller->index($request);
|
||||||
|
|
||||||
|
self::assertSame(302, $response->getStatusCode());
|
||||||
|
self::assertTrue($controller->user->isProfileComplete());
|
||||||
|
self::assertSame('new-local-hash', $controller->user->getPassword());
|
||||||
|
self::assertSame('legacy-backup', $controller->user->getPreviousPassword());
|
||||||
|
self::assertSame([
|
||||||
|
['success', 'Deine persönlichen Daten wurden aktualisiert'],
|
||||||
|
], $controller->flashes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIndexDoesNotPersistInvalidFormSubmission(): void
|
||||||
|
{
|
||||||
|
$controller = $this->createController(true, false, $this->validator(), 'NewPassw0rd!');
|
||||||
|
|
||||||
|
$controller->apiClient
|
||||||
|
->expects(self::once())
|
||||||
|
->method('getPersonalData')
|
||||||
|
->with('[email protected]', null, null)
|
||||||
|
->willReturn($this->createCompletePersonalData());
|
||||||
|
$controller->apiClient->expects(self::never())->method('updatePersonalData');
|
||||||
|
$controller->newsletterManager
|
||||||
|
->expects(self::once())
|
||||||
|
->method('hasConfirmedOptIn')
|
||||||
|
->with('[email protected]')
|
||||||
|
->willReturn(false);
|
||||||
|
$controller->newsletterManager
|
||||||
|
->expects(self::once())
|
||||||
|
->method('hasPendingConfirmation')
|
||||||
|
->with('[email protected]')
|
||||||
|
->willReturn(false);
|
||||||
|
$controller->dataLoader->expects(self::never())->method('invalidateUserBookingCaches');
|
||||||
|
$controller->entityManager->expects(self::never())->method('flush');
|
||||||
|
$controller->passwordHasher->expects(self::never())->method('hashPassword');
|
||||||
|
|
||||||
|
$request = Request::create('/personal-data', 'POST');
|
||||||
|
$request->setSession(new Session(new MockArraySessionStorage()));
|
||||||
|
$response = $controller->index($request);
|
||||||
|
|
||||||
|
self::assertSame(200, $response->getStatusCode());
|
||||||
|
self::assertSame('account/personal_data.html.twig', $controller->renderedView);
|
||||||
|
self::assertSame([], $controller->flashes);
|
||||||
|
self::assertSame('secret', $controller->user->getPassword());
|
||||||
|
self::assertSame('legacy-backup', $controller->user->getPreviousPassword());
|
||||||
|
}
|
||||||
|
|
||||||
private function createController(
|
private function createController(
|
||||||
bool $submitted = false,
|
bool $submitted = false,
|
||||||
bool $valid = false,
|
bool $valid = false,
|
||||||
?ValidatorInterface $validator = null,
|
?ValidatorInterface $validator = null,
|
||||||
|
string $newPassword = '',
|
||||||
): TestablePersonalDataController {
|
): TestablePersonalDataController {
|
||||||
$user = new User('[email protected]');
|
$user = new User('[email protected]');
|
||||||
$user->setPassword('secret');
|
$user->setPassword('secret');
|
||||||
|
$user->setPreviousPassword('legacy-backup');
|
||||||
|
|
||||||
$apiClient = $this->createMock(ApiClient::class);
|
$apiClient = $this->createMock(ApiClient::class);
|
||||||
|
|
||||||
@@ -221,6 +313,14 @@ class PersonalDataControllerTest extends TestCase
|
|||||||
$form
|
$form
|
||||||
->method('isValid')
|
->method('isValid')
|
||||||
->willReturn($valid);
|
->willReturn($valid);
|
||||||
|
$newPasswordField = $this->createMock(FormInterface::class);
|
||||||
|
$newPasswordField
|
||||||
|
->method('getData')
|
||||||
|
->willReturn($newPassword);
|
||||||
|
$form
|
||||||
|
->method('get')
|
||||||
|
->with('newPassword')
|
||||||
|
->willReturn($newPasswordField);
|
||||||
|
|
||||||
$validator ??= $this->createMock(ValidatorInterface::class);
|
$validator ??= $this->createMock(ValidatorInterface::class);
|
||||||
|
|
||||||
@@ -230,6 +330,7 @@ class PersonalDataControllerTest extends TestCase
|
|||||||
$validator,
|
$validator,
|
||||||
$this->createMock(EntityManagerInterface::class),
|
$this->createMock(EntityManagerInterface::class),
|
||||||
$this->createMock(NewsletterManager::class),
|
$this->createMock(NewsletterManager::class),
|
||||||
|
$this->createMock(UserPasswordHasherInterface::class),
|
||||||
$this->createMock(LoggerInterface::class),
|
$this->createMock(LoggerInterface::class),
|
||||||
$user,
|
$user,
|
||||||
$form,
|
$form,
|
||||||
@@ -301,6 +402,7 @@ final class TestablePersonalDataController extends PersonalDataController
|
|||||||
public readonly ValidatorInterface $validator,
|
public readonly ValidatorInterface $validator,
|
||||||
public readonly EntityManagerInterface $entityManager,
|
public readonly EntityManagerInterface $entityManager,
|
||||||
public readonly NewsletterManager $newsletterManager,
|
public readonly NewsletterManager $newsletterManager,
|
||||||
|
public readonly UserPasswordHasherInterface $passwordHasher,
|
||||||
LoggerInterface $logger,
|
LoggerInterface $logger,
|
||||||
public readonly User $user,
|
public readonly User $user,
|
||||||
private readonly FormInterface $form,
|
private readonly FormInterface $form,
|
||||||
@@ -311,6 +413,7 @@ final class TestablePersonalDataController extends PersonalDataController
|
|||||||
$validator,
|
$validator,
|
||||||
$entityManager,
|
$entityManager,
|
||||||
$newsletterManager,
|
$newsletterManager,
|
||||||
|
$passwordHasher,
|
||||||
$logger,
|
$logger,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ class BpnAuthenticatorTest extends TestCase
|
|||||||
$passport = $authenticator->authenticate($this->createRequest('[email protected]', 'secret'));
|
$passport = $authenticator->authenticate($this->createRequest('[email protected]', 'secret'));
|
||||||
|
|
||||||
self::assertInstanceOf(SelfValidatingPassport::class, $passport);
|
self::assertInstanceOf(SelfValidatingPassport::class, $passport);
|
||||||
|
self::assertSame(base64_encode('legacy-ciphertext'), $user->getPreviousPassword());
|
||||||
self::assertSame('local-hash', $user->getPassword());
|
self::assertSame('local-hash', $user->getPassword());
|
||||||
self::assertSame(123, $user->getAddressId());
|
self::assertSame(123, $user->getAddressId());
|
||||||
self::assertSame(456, $user->getPersonId());
|
self::assertSame(456, $user->getPersonId());
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ class PasswordResetManagerTest extends TestCase
|
|||||||
{
|
{
|
||||||
$user = new User('[email protected]');
|
$user = new User('[email protected]');
|
||||||
$user->setPasswordResetNonce('current-nonce');
|
$user->setPasswordResetNonce('current-nonce');
|
||||||
|
$user->setPreviousPassword('legacy-backup');
|
||||||
|
|
||||||
$userRepository = $this->createMock(UserRepository::class);
|
$userRepository = $this->createMock(UserRepository::class);
|
||||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||||
@@ -167,6 +168,7 @@ class PasswordResetManagerTest extends TestCase
|
|||||||
$manager->resetPassword($user, 'new-password');
|
$manager->resetPassword($user, 'new-password');
|
||||||
|
|
||||||
self::assertSame('hashed-password', $user->getPassword());
|
self::assertSame('hashed-password', $user->getPassword());
|
||||||
|
self::assertSame('legacy-backup', $user->getPreviousPassword());
|
||||||
self::assertNotSame('current-nonce', $user->getPasswordResetNonce());
|
self::assertNotSame('current-nonce', $user->getPasswordResetNonce());
|
||||||
self::assertMatchesRegularExpression('/^[a-f0-9]{64}$/', (string) $user->getPasswordResetNonce());
|
self::assertMatchesRegularExpression('/^[a-f0-9]{64}$/', (string) $user->getPasswordResetNonce());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user