mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 09:02:12 +00:00
feature #14153 [Admin][UI][API] Reset password validation (NoResponseMate)
This PR was merged into the 1.12 branch. Discussion ---------- | Q | A | |-----------------|--------------------------------------------------------------| | Branch? | 1.12 | | Bug fix? | no | | New feature? | yes | | BC breaks? | no | | Deprecations? | no <!-- don't forget to update the UPGRADE-*.md file --> | | Related tickets | - | | License | MIT | Based on #14147 Extracted it to not mix the concepts as the base PR only touches UI, but the validation is for both UI and API. Commits -------6c1336a886Add administrator password reset ui testsad5cf70619[Admin][UI] Rendering the reset password page5f845b1c83Move ResetPassword command and handler into Core99dd0b2eca[Admin][UI] Password resettingf430bfb8c2[Behat][Admin] Better elements' names in ResetPasswordPagec37d52f999[Admin][Core] Extract ResetPassword message dispatching to a standalone serviceea77ea005c[Behat] Fix admin's logging in logic23ef23e223[Behat][Admin] Fix and cleanup of password resettingf6ce7838cbFix sylius.admin.login.before_form event's priorities53542d27db[Core] Remove "experimental" tag from ResetPassword related classesddbfa821e8[Behat] More precise password changed notification check0c596f6d41[Admin][UI] Rework password reset template events3ede1e5532[Admin][UI] Add completely forgotten password reset email link5c320f0ca3[API][UI] Add validation for expired admin password reset tokenfd24b11ee7[API][UI] Add password validation to admin's password reset workflow
This commit is contained in:
commit
6193a33da8
54 changed files with 1064 additions and 59 deletions
|
|
@ -5,7 +5,8 @@ Feature: Resetting an administrator's password
|
|||
I want to be able to reset my password
|
||||
|
||||
Background:
|
||||
Given there is an administrator "sylius@example.com" identified by "sylius"
|
||||
Given the store operates on a single channel in "United States"
|
||||
And there is an administrator "sylius@example.com" identified by "sylius"
|
||||
|
||||
@email @api @ui
|
||||
Scenario: Sending an administrator's password reset request
|
||||
|
|
@ -23,7 +24,7 @@ Feature: Resetting an administrator's password
|
|||
Then I should be notified that email with reset instruction has been sent
|
||||
But "does-not-exist@example.com" should receive no emails
|
||||
|
||||
@api
|
||||
@ui @api
|
||||
Scenario: Changing my administrator's password
|
||||
Given I have already received a resetting password email
|
||||
When I follow the instructions to reset my password
|
||||
|
|
@ -33,7 +34,7 @@ Feature: Resetting an administrator's password
|
|||
Then I should be notified that my password has been successfully changed
|
||||
And I should be able to log in as "sylius@example.com" authenticated by "newp@ssw0rd" password
|
||||
|
||||
@api
|
||||
@ui @api
|
||||
Scenario: Trying to change my administrator's password twice without sending a new password reset request
|
||||
Given I have already received an administrator's password resetting email
|
||||
When I follow the instructions to reset my password
|
||||
|
|
@ -41,3 +42,20 @@ Feature: Resetting an administrator's password
|
|||
And I confirm my new password as "newp@ssw0rd"
|
||||
And I reset it
|
||||
Then I should not be able to change my password again with the same token
|
||||
|
||||
@api
|
||||
Scenario: Trying to change my administrator's password using an expired reset token
|
||||
Given I have already received an administrator's password resetting email
|
||||
But my password reset token has already expired
|
||||
When I try to follow the instructions to reset my password
|
||||
And I specify my new password as "newp@ssw0rd"
|
||||
And I confirm my new password as "newp@ssw0rd"
|
||||
And I reset it
|
||||
Then I should be notified that the password reset token has expired
|
||||
|
||||
@ui @no-api
|
||||
Scenario: Trying to change my administrator's password using an expired reset token
|
||||
Given I have already received an administrator's password resetting email
|
||||
But my password reset token has already expired
|
||||
When I try to follow the instructions to reset my password
|
||||
Then I should be notified that the password reset token has expired
|
||||
|
|
|
|||
|
|
@ -14,6 +14,15 @@ Feature: Resetting an administrator's password validation
|
|||
And I try to reset it
|
||||
Then I should be notified that the email is required
|
||||
|
||||
@ui @api
|
||||
Scenario: Trying to reset my administrator's password with an empty value
|
||||
Given I have already received an administrator's password resetting email
|
||||
When I follow the instructions to reset my password
|
||||
And I do not specify my new password
|
||||
And I do not confirm my new password
|
||||
And I try to reset it
|
||||
Then I should be notified that the new password is required
|
||||
|
||||
@ui @api
|
||||
Scenario: Trying to reset my administrator's password with an invalid email
|
||||
When I want to reset password
|
||||
|
|
@ -21,19 +30,19 @@ Feature: Resetting an administrator's password validation
|
|||
And I try to reset it
|
||||
Then I should be notified that the email is not valid
|
||||
|
||||
@todo
|
||||
@ui @api
|
||||
Scenario: Trying to reset my administrator's password with a wrong confirmation password
|
||||
Given I have already received an administrator's password resetting email
|
||||
When I follow link on my email to reset my password
|
||||
When I follow the instructions to reset my password
|
||||
And I specify my new password as "newp@ssw0rd"
|
||||
And I confirm my new password as "wrongp@ssw0rd"
|
||||
And I try to reset it
|
||||
Then I should be notified that the entered passwords do not match
|
||||
|
||||
@todo
|
||||
@ui @api
|
||||
Scenario: Trying to reset my administrator's password with a too short password
|
||||
Given I have already received an administrator's password resetting email
|
||||
When I follow link on my email to reset my password
|
||||
When I follow the instructions to reset my password
|
||||
And I specify my new password as "fu"
|
||||
And I confirm my new password as "fu"
|
||||
And I try to reset it
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ final class ResettingPasswordContext implements Context
|
|||
}
|
||||
|
||||
/**
|
||||
* @When /^(I) follow the instructions to reset my password$/
|
||||
* @When /^(I)(?:| try to) follow the instructions to reset my password$/
|
||||
*/
|
||||
public function iFollowTheInstructionsToResetMyPassword(AdminUserInterface $admin): void
|
||||
{
|
||||
|
|
@ -74,16 +74,18 @@ final class ResettingPasswordContext implements Context
|
|||
|
||||
/**
|
||||
* @When I specify my new password as :password
|
||||
* @When I do not specify my new password
|
||||
*/
|
||||
public function iSpecifyMyNewPassword(string $password): void
|
||||
public function iSpecifyMyNewPassword(string $password = ''): void
|
||||
{
|
||||
$this->request->updateContent(['newPassword' => $password]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I confirm my new password as :password
|
||||
* @When I do not confirm my new password
|
||||
*/
|
||||
public function iConfirmMyNewPassword(string $password): void
|
||||
public function iConfirmMyNewPassword(string $password = ''): void
|
||||
{
|
||||
$this->request->updateContent(['confirmNewPassword' => $password]);
|
||||
}
|
||||
|
|
@ -139,4 +141,47 @@ final class ResettingPasswordContext implements Context
|
|||
'This email is not valid.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that the password reset token has expired
|
||||
*/
|
||||
public function iShouldBeNotifiedThatThePasswordResetTokenHasExpired(): void
|
||||
{
|
||||
$message = $this->responseChecker->getError($this->client->getLastResponse());
|
||||
Assert::same($message, 'The password reset token has expired.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that the new password is required
|
||||
*/
|
||||
public function iShouldBeNotifiedThatTheNewPasswordIsRequired(): void
|
||||
{
|
||||
$this->assertResponseHasValidationMessageForNewPassword('Please enter the password.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that the entered passwords do not match
|
||||
*/
|
||||
public function iShouldBeNotifiedThatTheEnteredPasswordsDoNotMatch(): void
|
||||
{
|
||||
$this->assertResponseHasValidationMessageForNewPassword('The entered passwords do not match.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^I should be notified that the password should be ([^"]+)$/
|
||||
*/
|
||||
public function iShouldBeNotifiedThatThePasswordShouldBe(string $validationMessage): void
|
||||
{
|
||||
$this->assertResponseHasValidationMessageForNewPassword(sprintf('Password must be %s.', $validationMessage));
|
||||
}
|
||||
|
||||
private function assertResponseHasValidationMessageForNewPassword(string $message): void
|
||||
{
|
||||
$lastResponse = $this->client->getLastResponse();
|
||||
|
||||
Assert::true(
|
||||
$this->responseChecker->hasViolationWithMessage($lastResponse, $message, 'newPassword'),
|
||||
$lastResponse->getContent(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,4 +109,14 @@ final class AdminUserContext implements Context
|
|||
|
||||
$this->objectManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^(my) password reset token has already expired$/
|
||||
*/
|
||||
public function myPasswordResetTokenHasAlreadyExpired(AdminUserInterface $administrator): void
|
||||
{
|
||||
$administrator->setPasswordRequestedAt(new \DateTime('-1 year'));
|
||||
|
||||
$this->objectManager->flush();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,11 +14,8 @@ declare(strict_types=1);
|
|||
namespace Sylius\Behat\Context\Ui\Admin;
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Sylius\Behat\NotificationType;
|
||||
use Sylius\Behat\Page\Admin\Account\LoginPageInterface;
|
||||
use Sylius\Behat\Page\Admin\Account\RequestPasswordResetPage;
|
||||
use Sylius\Behat\Page\Admin\DashboardPageInterface;
|
||||
use Sylius\Behat\Service\NotificationCheckerInterface;
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
|
|
@ -55,6 +52,14 @@ final class LoginContext implements Context
|
|||
$this->loginPage->specifyPassword($password);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^(this administrator) logs in using "([^"]+)" password$/
|
||||
*/
|
||||
public function theyLogIn(AdminUserInterface $adminUser, $password)
|
||||
{
|
||||
$this->logInAgain($adminUser->getUsername(), $password);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I log in
|
||||
*/
|
||||
|
|
@ -101,16 +106,7 @@ final class LoginContext implements Context
|
|||
public function iShouldBeAbleToLogInAsAuthenticatedByPassword($username, $password)
|
||||
{
|
||||
$this->logInAgain($username, $password);
|
||||
|
||||
$this->dashboardPage->verify();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^(this administrator) logs in using "([^"]+)" password$/
|
||||
*/
|
||||
public function theyLogIn(AdminUserInterface $adminUser, $password)
|
||||
{
|
||||
$this->logInAgain($adminUser->getUsername(), $password);
|
||||
$this->iShouldBeLoggedIn();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -132,14 +128,12 @@ final class LoginContext implements Context
|
|||
Assert::true($this->loginPage->isOpen());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
*/
|
||||
private function logInAgain($username, $password)
|
||||
private function logInAgain(string $username, string $password): void
|
||||
{
|
||||
$this->dashboardPage->open();
|
||||
$this->dashboardPage->logOut();
|
||||
$this->dashboardPage->tryToOpen();
|
||||
if ($this->dashboardPage->isOpen()) {
|
||||
$this->dashboardPage->logOut();
|
||||
}
|
||||
|
||||
$this->loginPage->open();
|
||||
$this->loginPage->specifyUsername($username);
|
||||
|
|
|
|||
|
|
@ -14,15 +14,20 @@ declare(strict_types=1);
|
|||
namespace Sylius\Behat\Context\Ui\Admin;
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Sylius\Behat\Element\Admin\Account\ResetElementInterface;
|
||||
use Sylius\Behat\NotificationType;
|
||||
use Sylius\Behat\Page\Admin\Account\RequestPasswordResetPage;
|
||||
use Sylius\Behat\Page\Admin\Account\ResetPasswordPageInterface;
|
||||
use Sylius\Behat\Service\NotificationCheckerInterface;
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class ResettingPasswordContext implements Context
|
||||
{
|
||||
public function __construct(
|
||||
private RequestPasswordResetPage $requestPasswordResetPage,
|
||||
private ResetPasswordPageInterface $resetPasswordPage,
|
||||
private ResetElementInterface $resetElement,
|
||||
private NotificationCheckerInterface $notificationChecker,
|
||||
) {
|
||||
}
|
||||
|
|
@ -49,7 +54,33 @@ final class ResettingPasswordContext implements Context
|
|||
*/
|
||||
public function iResetIt(): void
|
||||
{
|
||||
$this->requestPasswordResetPage->resetPassword();
|
||||
$this->resetElement->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^(I)(?:| try to) follow the instructions to reset my password$/
|
||||
*/
|
||||
public function iFollowTheInstructionsToResetMyPassword(AdminUserInterface $admin): void
|
||||
{
|
||||
$this->resetPasswordPage->tryToOpen(['token' => $admin->getPasswordResetToken()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I specify my new password as :password
|
||||
* @When I do not specify my new password
|
||||
*/
|
||||
public function iSpecifyMyNewPassword(string $password = ''): void
|
||||
{
|
||||
$this->resetPasswordPage->specifyNewPassword($password);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I confirm my new password as :password
|
||||
* @When I do not confirm my new password
|
||||
*/
|
||||
public function iConfirmMyNewPassword(string $password = ''): void
|
||||
{
|
||||
$this->resetPasswordPage->specifyPasswordConfirmation($password);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -84,4 +115,63 @@ final class ResettingPasswordContext implements Context
|
|||
'This email is not valid.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that my password has been successfully changed
|
||||
*/
|
||||
public function iShouldBeNotifiedThatMyPasswordHasBeenSuccessfullyChanged(): void
|
||||
{
|
||||
$this->notificationChecker->checkNotification('has been changed successfully!', NotificationType::success());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should not be able to change my password again with the same token
|
||||
*/
|
||||
public function iShouldNotBeAbleToChangeMyPasswordAgainWithTheSameToken(): void
|
||||
{
|
||||
$this->resetPasswordPage->tryToOpen(['token' => 'itotallyforgotmypassword']);
|
||||
|
||||
Assert::false($this->resetPasswordPage->isOpen(), 'User should not be on the forgotten password page');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that the password reset token has expired
|
||||
*/
|
||||
public function iShouldBeNotifiedThatThePasswordResetTokenHasExpired(): void
|
||||
{
|
||||
$this->notificationChecker->checkNotification('has expired', NotificationType::failure());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that the new password is required
|
||||
*/
|
||||
public function iShouldBeNotifiedThatTheNewPasswordIsRequired(): void
|
||||
{
|
||||
Assert::contains(
|
||||
$this->resetPasswordPage->getValidationMessageFor('new_password'),
|
||||
'Please enter the password.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that the entered passwords do not match
|
||||
*/
|
||||
public function iShouldBeNotifiedThatTheEnteredPasswordsDoNotMatch(): void
|
||||
{
|
||||
Assert::contains(
|
||||
$this->resetPasswordPage->getValidationMessageFor('new_password'),
|
||||
'The entered passwords do not match.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^I should be notified that the password should be ([^"]+)$/
|
||||
*/
|
||||
public function iShouldBeNotifiedThatThePasswordShouldBe(string $validationMessage): void
|
||||
{
|
||||
Assert::contains(
|
||||
$this->resetPasswordPage->getValidationMessageFor('new_password'),
|
||||
$validationMessage,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
24
src/Sylius/Behat/Element/Admin/Account/ResetElement.php
Normal file
24
src/Sylius/Behat/Element/Admin/Account/ResetElement.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Behat\Element\Admin\Account;
|
||||
|
||||
use FriendsOfBehat\PageObjectExtension\Element\Element;
|
||||
|
||||
final class ResetElement extends Element implements ResetElementInterface
|
||||
{
|
||||
public function reset(): void
|
||||
{
|
||||
$this->getDocument()->find('css', 'button[type="submit"]:contains("Reset")')->click();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Behat\Element\Admin\Account;
|
||||
|
||||
interface ResetElementInterface
|
||||
{
|
||||
public function reset(): void;
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ class RequestPasswordResetPage extends SymfonyPage implements RequestPasswordRes
|
|||
{
|
||||
public function getRouteName(): string
|
||||
{
|
||||
return 'sylius_admin_request_password_reset';
|
||||
return 'sylius_admin_render_reset_password_page';
|
||||
}
|
||||
|
||||
public function specifyEmail(string $email): void
|
||||
|
|
@ -28,11 +28,6 @@ class RequestPasswordResetPage extends SymfonyPage implements RequestPasswordRes
|
|||
$this->getElement('email')->setValue($email);
|
||||
}
|
||||
|
||||
public function resetPassword(): void
|
||||
{
|
||||
$this->getDocument()->pressButton('Reset password');
|
||||
}
|
||||
|
||||
public function getEmailValidationMessage(): string
|
||||
{
|
||||
$errorLabel = $this->getElement('email')->getParent()->find('css', '[data-test-validation-error]');
|
||||
|
|
|
|||
|
|
@ -19,7 +19,5 @@ interface RequestPasswordResetPageInterface extends SymfonyPageInterface
|
|||
{
|
||||
public function specifyEmail(string $email): void;
|
||||
|
||||
public function resetPassword(): void;
|
||||
|
||||
public function getEmailValidationMessage(): string;
|
||||
}
|
||||
|
|
|
|||
54
src/Sylius/Behat/Page/Admin/Account/ResetPasswordPage.php
Normal file
54
src/Sylius/Behat/Page/Admin/Account/ResetPasswordPage.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Behat\Page\Admin\Account;
|
||||
|
||||
use Behat\Mink\Exception\ElementNotFoundException;
|
||||
use FriendsOfBehat\PageObjectExtension\Page\SymfonyPage;
|
||||
|
||||
class ResetPasswordPage extends SymfonyPage implements ResetPasswordPageInterface
|
||||
{
|
||||
public function specifyNewPassword(string $password): void
|
||||
{
|
||||
$this->getElement('new_password')->setValue($password);
|
||||
}
|
||||
|
||||
public function specifyPasswordConfirmation(string $password): void
|
||||
{
|
||||
$this->getElement('confirm_new_password')->setValue($password);
|
||||
}
|
||||
|
||||
public function getValidationMessageFor(string $element): string
|
||||
{
|
||||
$errorLabel = $this->getElement($element)->getParent()->find('css', '[data-test-validation-error]');
|
||||
|
||||
if (null === $errorLabel) {
|
||||
throw new ElementNotFoundException($this->getSession(), 'Validation message', 'css', '[data-test-validation-error]');
|
||||
}
|
||||
|
||||
return $errorLabel->getText();
|
||||
}
|
||||
|
||||
public function getRouteName(): string
|
||||
{
|
||||
return 'sylius_admin_render_password_reset';
|
||||
}
|
||||
|
||||
protected function getDefinedElements(): array
|
||||
{
|
||||
return array_merge(parent::getDefinedElements(), [
|
||||
'confirm_new_password' => '[data-test-confirm-new-password]',
|
||||
'new_password' => '[data-test-new-password]',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Behat\Page\Admin\Account;
|
||||
|
||||
use FriendsOfBehat\PageObjectExtension\Page\SymfonyPageInterface;
|
||||
|
||||
interface ResetPasswordPageInterface extends SymfonyPageInterface
|
||||
{
|
||||
public function specifyNewPassword(string $password): void;
|
||||
|
||||
public function specifyPasswordConfirmation(string $password): void;
|
||||
|
||||
public function getValidationMessageFor(string $element): string;
|
||||
}
|
||||
|
|
@ -319,6 +319,8 @@
|
|||
|
||||
<service id="sylius.behat.context.ui.admin.resetting_password" class="Sylius\Behat\Context\Ui\Admin\ResettingPasswordContext">
|
||||
<argument type="service" id="sylius.behat.page.admin.request_password_reset" />
|
||||
<argument type="service" id="sylius.behat.page.admin.reset_password" />
|
||||
<argument type="service" id="sylius.behat.element.admin.account.reset" />
|
||||
<argument type="service" id="sylius.behat.notification_checker" />
|
||||
</service>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"
|
||||
>
|
||||
<services>
|
||||
<service id="sylius.behat.element.admin.account.reset" class="Sylius\Behat\Element\Admin\Account\ResetElement" parent="sylius.behat.element" />
|
||||
|
||||
<service id="sylius.behat.element.admin.channel.shop_billing_data" class="Sylius\Behat\Element\Admin\Channel\ShopBillingDataElement" parent="sylius.behat.element" />
|
||||
|
||||
<service id="sylius.behat.element.admin.top_bar" class="Sylius\Behat\Element\Admin\TopBarElement" parent="sylius.behat.element" />
|
||||
|
|
|
|||
|
|
@ -12,11 +12,17 @@
|
|||
-->
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services" 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">
|
||||
<parameters>
|
||||
<parameter key="sylius.behat.page.admin.reset_password">Sylius\Behat\Page\Admin\Account\ResetPasswordPage</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
<defaults public="true" />
|
||||
|
||||
<service id="sylius.behat.page.admin.login" class="Sylius\Behat\Page\Admin\Account\LoginPage" parent="sylius.behat.symfony_page" public="false" />
|
||||
|
||||
<service id="sylius.behat.page.admin.request_password_reset" class="Sylius\Behat\Page\Admin\Account\RequestPasswordResetPage" parent="sylius.behat.symfony_page" public="false" />
|
||||
|
||||
<service id="sylius.behat.page.admin.reset_password" class="%sylius.behat.page.admin.reset_password%" parent="sylius.behat.symfony_page" public="false" />
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ default:
|
|||
contexts:
|
||||
- sylius.behat.context.hook.doctrine_orm
|
||||
|
||||
- sylius.behat.context.transform.user
|
||||
- sylius.behat.context.transform.admin
|
||||
|
||||
- sylius.behat.context.setup.channel
|
||||
- sylius.behat.context.setup.admin_security
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AdminBundle\Action\Account;
|
||||
|
||||
use Sylius\Bundle\AdminBundle\Form\Type\ResetPasswordType;
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Sylius\Component\User\Repository\UserRepositoryInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
final class RenderResetPasswordPageAction
|
||||
{
|
||||
public function __construct(
|
||||
private UserRepositoryInterface $userRepository,
|
||||
private FormFactoryInterface $formFactory,
|
||||
private FlashBagInterface $flashBag,
|
||||
private RouterInterface $router,
|
||||
private Environment $twig,
|
||||
private string $tokenTtl,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(Request $request, string $token): Response
|
||||
{
|
||||
/** @var AdminUserInterface|null $admin */
|
||||
$admin = $this->userRepository->findOneBy(['passwordResetToken' => $token]);
|
||||
if (null === $admin) {
|
||||
throw new NotFoundHttpException('Token not found');
|
||||
}
|
||||
|
||||
$lifetime = new \DateInterval($this->tokenTtl);
|
||||
|
||||
if (!$admin->isPasswordRequestNonExpired($lifetime)) {
|
||||
$this->flashBag->add('error', 'sylius.admin.password_reset.token_expired');
|
||||
|
||||
$attributes = $request->attributes->get('_sylius');
|
||||
$redirect = $attributes['redirect'] ?? 'sylius_admin_login';
|
||||
|
||||
if (is_array($redirect)) {
|
||||
return new RedirectResponse($this->router->generate(
|
||||
$redirect['route'] ?? 'sylius_admin_login',
|
||||
$redirect['params'] ?? [],
|
||||
));
|
||||
}
|
||||
|
||||
return new RedirectResponse($this->router->generate($redirect));
|
||||
}
|
||||
|
||||
$form = $this->formFactory->create(ResetPasswordType::class);
|
||||
|
||||
return new Response(
|
||||
$this->twig->render('@SyliusAdmin/Security/resetPassword.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AdminBundle\Action\Account;
|
||||
|
||||
use Sylius\Bundle\AdminBundle\Form\Model\PasswordReset;
|
||||
use Sylius\Bundle\AdminBundle\Form\Type\ResetPasswordType;
|
||||
use Sylius\Bundle\CoreBundle\MessageDispatcher\ResetPasswordDispatcherInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
final class ResetPasswordAction
|
||||
{
|
||||
public function __construct(
|
||||
private FormFactoryInterface $formFactory,
|
||||
private ResetPasswordDispatcherInterface $resetPasswordDispatcher,
|
||||
private FlashBagInterface $flashBag,
|
||||
private RouterInterface $router,
|
||||
private Environment $twig,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(Request $request, string $token): Response
|
||||
{
|
||||
$form = $this->formFactory->create(ResetPasswordType::class);
|
||||
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/** @var PasswordReset $passwordReset */
|
||||
$passwordReset = $form->getData();
|
||||
|
||||
$this->resetPasswordDispatcher->dispatch($token, $passwordReset->getPassword());
|
||||
|
||||
$this->flashBag->add('success', 'sylius.admin.password_reset.success');
|
||||
|
||||
$attributes = $request->attributes->get('_sylius');
|
||||
$redirect = $attributes['redirect'] ?? 'sylius_admin_login';
|
||||
|
||||
if (is_array($redirect)) {
|
||||
return new RedirectResponse(
|
||||
$redirect['route'] ?? 'sylius_admin_login',
|
||||
$redirect['params'] ?? [],
|
||||
);
|
||||
}
|
||||
|
||||
return new RedirectResponse($this->router->generate($redirect));
|
||||
}
|
||||
|
||||
return new Response(
|
||||
$this->twig->render('@SyliusAdmin/Security/resetPassword.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
29
src/Sylius/Bundle/AdminBundle/Form/Model/PasswordReset.php
Normal file
29
src/Sylius/Bundle/AdminBundle/Form/Model/PasswordReset.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AdminBundle\Form\Model;
|
||||
|
||||
class PasswordReset
|
||||
{
|
||||
private ?string $password = null;
|
||||
|
||||
public function getPassword(): ?string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function setPassword(?string $password): void
|
||||
{
|
||||
$this->password = $password;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AdminBundle\Form\Type;
|
||||
|
||||
use Sylius\Bundle\AdminBundle\Form\Model\PasswordReset;
|
||||
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;
|
||||
|
||||
final class ResetPasswordType extends AbstractType
|
||||
{
|
||||
public function __construct(private array $validationGroups = [])
|
||||
{
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('password', RepeatedType::class, [
|
||||
'type' => PasswordType::class,
|
||||
'first_name' => 'newPassword',
|
||||
'first_options' => ['label' => 'sylius.form.admin.reset_password.password.label'],
|
||||
'second_name' => 'confirmNewPassword',
|
||||
'second_options' => ['label' => 'sylius.form.admin.reset_password.password.confirmation'],
|
||||
'invalid_message' => 'sylius.admin.reset_password.mismatch',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => PasswordReset::class,
|
||||
'validation_groups' => $this->validationGroups,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'sylius_admin_reset_password';
|
||||
}
|
||||
}
|
||||
|
|
@ -1311,3 +1311,27 @@ sylius_ui:
|
|||
submit:
|
||||
template: '@SyliusAdmin/Security/RequestPasswordReset/_submit.html.twig'
|
||||
priority: 10
|
||||
|
||||
sylius.admin.reset_password.content:
|
||||
blocks:
|
||||
form:
|
||||
template: '@SyliusAdmin/Security/ResetPassword/_content.html.twig'
|
||||
priority: 10
|
||||
|
||||
sylius.admin.reset_password.form:
|
||||
blocks:
|
||||
logo:
|
||||
template: '@SyliusAdmin/Security/_logo.html.twig'
|
||||
priority: 20
|
||||
content:
|
||||
template: '@SyliusAdmin/Security/ResetPassword/_form.html.twig'
|
||||
priority: 10
|
||||
|
||||
sylius.admin.reset_password.form.content:
|
||||
blocks:
|
||||
passwords:
|
||||
template: '@SyliusAdmin/Security/ResetPassword/Form/_passwords.html.twig'
|
||||
priority: 20
|
||||
submit:
|
||||
template: '@SyliusAdmin/Security/ResetPassword/Form/_submit.html.twig'
|
||||
priority: 10
|
||||
|
|
|
|||
|
|
@ -27,3 +27,15 @@ sylius_admin_request_password_reset:
|
|||
path: /forgotten-password
|
||||
methods: [POST]
|
||||
controller: Sylius\Bundle\AdminBundle\Action\Account\RequestPasswordResetAction
|
||||
|
||||
sylius_admin_render_password_reset:
|
||||
path: /forgotten-password/{token}
|
||||
methods: [GET]
|
||||
defaults:
|
||||
_controller: Sylius\Bundle\AdminBundle\Action\Account\RenderResetPasswordPageAction
|
||||
|
||||
sylius_admin_password_reset:
|
||||
path: /forgotten-password/{token}
|
||||
methods: [POST]
|
||||
defaults:
|
||||
_controller: Sylius\Bundle\AdminBundle\Action\Account\ResetPasswordAction
|
||||
|
|
|
|||
|
|
@ -19,6 +19,23 @@
|
|||
<services>
|
||||
<defaults public="true" />
|
||||
|
||||
<service id="Sylius\Bundle\AdminBundle\Action\Account\RenderResetPasswordPageAction" public="true">
|
||||
<argument type="service" id="sylius.repository.admin_user" />
|
||||
<argument type="service" id="form.factory" />
|
||||
<argument type="service" id="session.flash_bag" />
|
||||
<argument type="service" id="router" />
|
||||
<argument type="service" id="twig" />
|
||||
<argument type="string">%sylius.admin_user.token.password_reset.ttl%</argument>
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\AdminBundle\Action\Account\ResetPasswordAction">
|
||||
<argument type="service" id="form.factory" />
|
||||
<argument type="service" id="Sylius\Bundle\CoreBundle\MessageDispatcher\ResetPasswordDispatcher" />
|
||||
<argument type="service" id="session.flash_bag" />
|
||||
<argument type="service" id="router" />
|
||||
<argument type="service" id="twig" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\AdminBundle\Action\RemoveAvatarAction" public="true">
|
||||
<argument type='service' id="sylius.repository.avatar_image" />
|
||||
<argument type="service" id="router" />
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@
|
|||
<parameter key="sylius.form.type.admin.password_reset_request.validation_groups" type="collection">
|
||||
<parameter>sylius</parameter>
|
||||
</parameter>
|
||||
<parameter key="sylius.form.type.admin.reset_password.validation_groups" type="collection">
|
||||
<parameter>sylius</parameter>
|
||||
</parameter>
|
||||
</parameters>
|
||||
<services>
|
||||
<service id="Sylius\Bundle\AdminBundle\Form\Extension\CatalogPromotionActionTypeExtension">
|
||||
|
|
@ -37,5 +40,10 @@
|
|||
<argument>%sylius.form.type.admin.password_reset_request.validation_groups%</argument>
|
||||
<tag name="form.type" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\AdminBundle\Form\Type\ResetPasswordType">
|
||||
<argument>%sylius.form.type.admin.reset_password.validation_groups%</argument>
|
||||
<tag name="form.type" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<!--
|
||||
~ This file is part of the Sylius package.
|
||||
~
|
||||
~ (c) Paweł Jędrzejewski
|
||||
~
|
||||
~ For the full copyright and license information, please view the LICENSE
|
||||
~ file that was distributed with this source code.
|
||||
-->
|
||||
|
||||
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
|
||||
<class name="Sylius\Bundle\AdminBundle\Form\Model\PasswordReset">
|
||||
<property name="password">
|
||||
<constraint name="NotBlank">
|
||||
<option name="message">sylius.admin.reset_password.password.not_blank</option>
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
<constraint name="Length">
|
||||
<option name="min">4</option>
|
||||
<option name="minMessage">sylius.admin.reset_password.password.min</option>
|
||||
<option name="max">254</option>
|
||||
<option name="maxMessage">sylius.admin.reset_password.password.max</option>
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
</property>
|
||||
</class>
|
||||
</constraint-mapping>
|
||||
|
|
@ -5,6 +5,9 @@ sylius:
|
|||
admin:
|
||||
request_reset_password:
|
||||
success: 'If the email you have specified exists in our system, we have sent there an instruction on how to reset your password.'
|
||||
password_reset:
|
||||
success: 'Your password has been changed successfully!'
|
||||
token_expired: 'The password reset token has expired.'
|
||||
email:
|
||||
order_confirmation_resent: 'Order confirmation has been successfully resent to the customer.'
|
||||
shipment_confirmation_resent: 'Shipment confirmation has been successfully resent to the customer.'
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@ sylius:
|
|||
thank_you_for_transaction: 'Thank you for a successful transaction.'
|
||||
you_can_check_its_location_with_the_tracking_code: 'You can check it''s location with the %tracking_code% tracking code.'
|
||||
your_order_with_number: 'Your order with number'
|
||||
form:
|
||||
admin:
|
||||
reset_password:
|
||||
password:
|
||||
label: New password
|
||||
confirmation: Confirm new password
|
||||
menu:
|
||||
admin:
|
||||
main:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
{{ form_row(form.password.newPassword, sylius_test_form_attribute('new-password')) }}
|
||||
{{ form_row(form.password.confirmNewPassword, sylius_test_form_attribute('confirm-new-password')) }}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<button type="submit" class="ui fluid large primary submit button">{{ 'sylius.ui.reset'|trans }}</button>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<div class="ui middle aligned center aligned grid">
|
||||
<div class="column">
|
||||
{{ sylius_template_event('sylius.admin.reset_password.form', _context) }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{% form_theme form '@SyliusAdmin/Form/theme.html.twig' %}
|
||||
|
||||
{{ form_start(form, {action: path('sylius_admin_password_reset', {'token': app.request.attributes.get('token')}), 'attr': {'class': 'ui large loadable form', 'novalidate': 'novalidate'}}) }}
|
||||
<div class="ui left aligned very padded segment">
|
||||
{{ sylius_template_event('sylius.admin.reset_password.form.content', _context) }}
|
||||
</div>
|
||||
{{ form_row(form._token) }}
|
||||
{{ form_end(form, {'render_rest': false}) }}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{% extends '@SyliusUi/Layout/centered.html.twig' %}
|
||||
|
||||
{% block title %}Sylius | {{ 'sylius.ui.administration_reset_password'|trans }}{% endblock %}
|
||||
|
||||
{% block stylesheets %}
|
||||
{{ sylius_template_event('sylius.admin.layout.stylesheets') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{ sylius_template_event('sylius.admin.reset_password.content', _context) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ sylius_template_event('sylius.admin.layout.javascripts') }}
|
||||
{% endblock %}
|
||||
|
|
@ -182,6 +182,7 @@ final class TestKernel extends BaseKernel
|
|||
*/
|
||||
protected function configureRoutes(object $routes): void
|
||||
{
|
||||
$routes->import('@SyliusAdminBundle/Resources/config/routing.yml');
|
||||
}
|
||||
|
||||
public function getCacheDir(): string
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Sylius\Bundle\ApiBundle\DataProvider;
|
|||
|
||||
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
|
||||
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
|
||||
use Sylius\Bundle\ApiBundle\Command\Admin\ResetPassword;
|
||||
use Sylius\Bundle\CoreBundle\Message\Admin\Account\ResetPassword;
|
||||
|
||||
/** @experimental */
|
||||
final class AdminResetPasswordItemDataProvider implements RestrictedDataProviderInterface, ItemDataProviderInterface
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://api-platform.com/schema/metadata https://api-platform.com/schema/metadata/metadata-2.0.xsd"
|
||||
>
|
||||
<resource class="Sylius\Bundle\ApiBundle\Command\Admin\ResetPassword" shortName="AdminResetPasswordRequest">
|
||||
<resource class="Sylius\Bundle\CoreBundle\Message\Admin\Account\ResetPassword" shortName="AdminResetPasswordRequest">
|
||||
<attribute name="validation_groups">sylius</attribute>
|
||||
<attribute name="messenger">input</attribute>
|
||||
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
<itemOperation name="admin_update_reset_password_request">
|
||||
<attribute name="method">PATCH</attribute>
|
||||
<attribute name="path">/admin/reset-password-requests/{resetPasswordToken}</attribute>
|
||||
<attribute name="input">Sylius\Bundle\ApiBundle\Command\Admin\ResetPassword</attribute>
|
||||
<attribute name="input">Sylius\Bundle\CoreBundle\Message\Admin\Account\ResetPassword</attribute>
|
||||
<attribute name="output">false</attribute>
|
||||
<attribute name="status">202</attribute>
|
||||
<attribute name="denormalization_context">
|
||||
|
|
|
|||
|
|
@ -188,12 +188,6 @@
|
|||
<tag name="messenger.message_handler" bus="sylius_default.bus" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\CommandHandler\Admin\ResetPasswordHandler">
|
||||
<argument type="service" id="sylius.user_password_resetter.admin" />
|
||||
<tag name="messenger.message_handler" bus="sylius.command_bus" />
|
||||
<tag name="messenger.message_handler" bus="sylius_default.bus" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\CommandHandler\Checkout\SendOrderConfirmationHandler">
|
||||
<argument type="service" id="sylius.email_sender" />
|
||||
<argument type="service" id="sylius.repository.order" />
|
||||
|
|
|
|||
|
|
@ -120,6 +120,12 @@
|
|||
<tag name="validator.constraint_validator" alias="sylius_api_unique_reviewer_email_validator" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\Validator\Constraints\AdminResetPasswordTokenNonExpiredValidator">
|
||||
<argument type="service" id="sylius.repository.admin_user" />
|
||||
<argument type="string">%sylius.admin_user.token.password_reset.ttl%</argument>
|
||||
<tag name="validator.constraint_validator" alias="sylius_api_validator_admin_non_expired_password_reset_token" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\Validator\CatalogPromotion\PercentageDiscountActionValidator">
|
||||
<argument type="service" id="Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface" />
|
||||
<tag name="sylius.catalog_promotion.action_validator" key="percentage_discount"/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<!--
|
||||
~ This file is part of the Sylius package.
|
||||
~
|
||||
~ (c) Paweł Jędrzejewski
|
||||
~
|
||||
~ For the full copyright and license information, please view the LICENSE
|
||||
~ file that was distributed with this source code.
|
||||
-->
|
||||
|
||||
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
|
||||
<class name="Sylius\Bundle\CoreBundle\Message\Admin\Account\ResetPassword">
|
||||
<constraint name="Sylius\Bundle\ApiBundle\Validator\Constraints\AdminResetPasswordTokenNonExpired">
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
</constraint>
|
||||
<property name="newPassword">
|
||||
<constraint name="NotBlank">
|
||||
<option name="message">sylius.admin.reset_password.password.not_blank</option>
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
<constraint name="Length">
|
||||
<option name="min">4</option>
|
||||
<option name="minMessage">sylius.admin.reset_password.password.min</option>
|
||||
<option name="max">254</option>
|
||||
<option name="maxMessage">sylius.admin.reset_password.password.max</option>
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
<constraint name="Expression">
|
||||
<option name="expression">this.newPassword === this.confirmNewPassword</option>
|
||||
<option name="message">sylius.admin.reset_password.mismatch</option>
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
</property>
|
||||
<property name="confirmNewPassword">
|
||||
<constraint name="NotBlank">
|
||||
<option name="message">sylius.admin.reset_password.password.not_blank</option>
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
<constraint name="Length">
|
||||
<option name="min">4</option>
|
||||
<option name="minMessage">sylius.admin.reset_password.password.min</option>
|
||||
<option name="max">254</option>
|
||||
<option name="maxMessage">sylius.admin.reset_password.password.max</option>
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
</property>
|
||||
</class>
|
||||
</constraint-mapping>
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
sylius:
|
||||
admin:
|
||||
expired_password_reset_token: 'The password reset token has expired.'
|
||||
account:
|
||||
invalid_verification_token: 'There is no shop user with %verificationToken% email verification token.'
|
||||
is_verified: 'Account with email %email% is currently verified.'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
final class AdminResetPasswordTokenNonExpired extends Constraint
|
||||
{
|
||||
public string $message = 'sylius.admin.expired_password_reset_token';
|
||||
|
||||
public function validatedBy(): string
|
||||
{
|
||||
return 'sylius_api_validator_admin_non_expired_password_reset_token';
|
||||
}
|
||||
|
||||
public function getTargets(): string
|
||||
{
|
||||
return self::CLASS_CONSTRAINT;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use Sylius\Bundle\CoreBundle\Message\Admin\Account\ResetPassword;
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Sylius\Component\User\Repository\UserRepositoryInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class AdminResetPasswordTokenNonExpiredValidator extends ConstraintValidator
|
||||
{
|
||||
public function __construct(
|
||||
private UserRepositoryInterface $adminUserRepository,
|
||||
private string $tokenTtl,
|
||||
) {
|
||||
}
|
||||
|
||||
/** @param ResetPassword|mixed $value */
|
||||
public function validate($value, Constraint $constraint): void
|
||||
{
|
||||
Assert::isInstanceOf($value, ResetPassword::class);
|
||||
|
||||
/** @var AdminResetPasswordTokenNonExpired $constraint */
|
||||
Assert::isInstanceOf($constraint, AdminResetPasswordTokenNonExpired::class);
|
||||
|
||||
/** @var AdminUserInterface|null $user */
|
||||
$user = $this->adminUserRepository->findOneBy(['passwordResetToken' => $value->resetPasswordToken]);
|
||||
if (null === $user) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lifetime = new \DateInterval($this->tokenTtl);
|
||||
|
||||
if (!$user->isPasswordRequestNonExpired($lifetime)) {
|
||||
$this->context->addViolation(
|
||||
$constraint->message,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ declare(strict_types=1);
|
|||
namespace spec\Sylius\Bundle\ApiBundle\DataProvider;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Bundle\ApiBundle\Command\Admin\ResetPassword;
|
||||
use Sylius\Bundle\CoreBundle\Message\Admin\Account\ResetPassword;
|
||||
use Sylius\Component\Core\Model\AddressInterface;
|
||||
|
||||
final class AdminResetPasswordItemDataProviderSpec extends ObjectBehavior
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace spec\Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Prophecy\Argument;
|
||||
use Sylius\Bundle\ApiBundle\Validator\Constraints\AdminResetPasswordTokenNonExpired;
|
||||
use Sylius\Bundle\CoreBundle\Message\Admin\Account\ResetPassword;
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Sylius\Component\User\Repository\UserRepositoryInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidatorInterface;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
|
||||
final class AdminResetPasswordTokenNonExpiredValidatorSpec extends ObjectBehavior
|
||||
{
|
||||
public function let(UserRepositoryInterface $userRepository): void
|
||||
{
|
||||
$this->beConstructedWith($userRepository, 'P5D');
|
||||
}
|
||||
|
||||
function it_is_a_constraint_validator(): void
|
||||
{
|
||||
$this->shouldImplement(ConstraintValidatorInterface::class);
|
||||
}
|
||||
|
||||
public function it_throws_exception_when_value_is_not_a_reset_password(): void
|
||||
{
|
||||
$constraint = new AdminResetPasswordTokenNonExpired();
|
||||
|
||||
$this
|
||||
->shouldThrow(\InvalidArgumentException::class)
|
||||
->during('validate', ['', $constraint])
|
||||
;
|
||||
}
|
||||
|
||||
public function it_throws_exception_when_constraint_is_not_admin_reset_password_token_non_expired(
|
||||
Constraint $constraint,
|
||||
): void {
|
||||
$value = new ResetPassword('token');
|
||||
|
||||
$this
|
||||
->shouldThrow(\InvalidArgumentException::class)
|
||||
->during('validate', [$value, $constraint])
|
||||
;
|
||||
}
|
||||
|
||||
public function it_does_nothing_when_a_user_for_given_token_does_not_exist(
|
||||
UserRepositoryInterface $userRepository,
|
||||
ExecutionContextInterface $executionContext,
|
||||
): void {
|
||||
$value = new ResetPassword('token');
|
||||
$constraint = new AdminResetPasswordTokenNonExpired();
|
||||
|
||||
$this->initialize($executionContext);
|
||||
|
||||
$userRepository->findOneBy(['passwordResetToken' => 'token'])->willReturn(null);
|
||||
|
||||
$executionContext->addViolation(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$this->validate($value, $constraint);
|
||||
}
|
||||
|
||||
public function it_does_nothing_when_user_password_reset_token_is_non_expired(
|
||||
UserRepositoryInterface $userRepository,
|
||||
AdminUserInterface $adminUser,
|
||||
ExecutionContextInterface $executionContext,
|
||||
): void {
|
||||
$value = new ResetPassword('token');
|
||||
$constraint = new AdminResetPasswordTokenNonExpired();
|
||||
|
||||
$this->initialize($executionContext);
|
||||
|
||||
$adminUser->isPasswordRequestNonExpired(
|
||||
Argument::that(static fn (\DateInterval $dateInterval) => $dateInterval->format('%d') === '5'),
|
||||
)->willReturn(true);
|
||||
$userRepository->findOneBy(['passwordResetToken' => 'token'])->willReturn($adminUser);
|
||||
|
||||
$executionContext->addViolation(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$this->validate($value, $constraint);
|
||||
}
|
||||
|
||||
public function it_adds_a_violation_when_user_password_reset_token_is_expired(
|
||||
UserRepositoryInterface $userRepository,
|
||||
AdminUserInterface $adminUser,
|
||||
ExecutionContextInterface $executionContext,
|
||||
): void {
|
||||
$value = new ResetPassword('token');
|
||||
$constraint = new AdminResetPasswordTokenNonExpired();
|
||||
|
||||
$this->initialize($executionContext);
|
||||
|
||||
$adminUser->isPasswordRequestNonExpired(
|
||||
Argument::that(static fn (\DateInterval $dateInterval) => $dateInterval->format('%d') === '5'),
|
||||
)->willReturn(false);
|
||||
$userRepository->findOneBy(['passwordResetToken' => 'token'])->willReturn($adminUser);
|
||||
|
||||
$executionContext->addViolation($constraint->message)->shouldBeCalled();
|
||||
|
||||
$this->validate($value, $constraint);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,9 +11,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Command\Admin;
|
||||
namespace Sylius\Bundle\CoreBundle\Message\Admin\Account;
|
||||
|
||||
/** @experimental */
|
||||
class ResetPassword
|
||||
{
|
||||
public function __construct(
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\MessageDispatcher;
|
||||
|
||||
use Sylius\Bundle\CoreBundle\Message\Admin\Account\ResetPassword;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
final class ResetPasswordDispatcher implements ResetPasswordDispatcherInterface
|
||||
{
|
||||
public function __construct(private MessageBusInterface $messageBus)
|
||||
{
|
||||
}
|
||||
|
||||
public function dispatch(string $token, string $password): void
|
||||
{
|
||||
$this->messageBus->dispatch(new ResetPassword($token, $password));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\MessageDispatcher;
|
||||
|
||||
interface ResetPasswordDispatcherInterface
|
||||
{
|
||||
public function dispatch(string $token, string $password): void;
|
||||
}
|
||||
|
|
@ -11,13 +11,12 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\CommandHandler\Admin;
|
||||
namespace Sylius\Bundle\CoreBundle\MessageHandler\Admin\Account;
|
||||
|
||||
use Sylius\Bundle\ApiBundle\Command\Admin\ResetPassword;
|
||||
use Sylius\Bundle\CoreBundle\Message\Admin\Account\ResetPassword;
|
||||
use Sylius\Bundle\CoreBundle\Security\UserPasswordResetterInterface;
|
||||
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
|
||||
|
||||
/** @experimental */
|
||||
final class ResetPasswordHandler implements MessageHandlerInterface
|
||||
{
|
||||
public function __construct(private UserPasswordResetterInterface $userPasswordResetter)
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/serializer-mapping https://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"
|
||||
>
|
||||
<class name="Sylius\Bundle\ApiBundle\Command\Admin\ResetPassword">
|
||||
<class name="Sylius\Bundle\CoreBundle\Message\Admin\Account\ResetPassword">
|
||||
<attribute name="newPassword">
|
||||
<group>admin:reset_password:update</group>
|
||||
</attribute>
|
||||
|
|
@ -280,5 +280,9 @@
|
|||
<argument type="service" id="Sylius\Component\User\Security\PasswordUpdaterInterface" />
|
||||
<argument type="string">%sylius.shop_user.token.password_reset.ttl%</argument>
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\CoreBundle\MessageDispatcher\ResetPasswordDispatcher">
|
||||
<argument type="service" id="sylius.command_bus" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@
|
|||
<tag name="messenger.message_handler" bus="sylius_default.bus" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\CoreBundle\MessageHandler\Admin\Account\ResetPasswordHandler">
|
||||
<argument type="service" id="sylius.user_password_resetter.admin" />
|
||||
|
||||
<tag name="messenger.message_handler" bus="sylius.command_bus" />
|
||||
<tag name="messenger.message_handler" bus="sylius_default.bus" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\CoreBundle\MessageHandler\Admin\Account\SendResetPasswordEmailHandler">
|
||||
<argument type="service" id="sylius.repository.admin_user"/>
|
||||
<argument type="service" id="sylius.email_sender"/>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@ sylius:
|
|||
max: Email must be at most {{ limit }} characters long.
|
||||
min: Email must be at least {{ limit }} characters long.
|
||||
not_blank: Please enter an email.
|
||||
reset_password:
|
||||
mismatch: The entered passwords do not match.
|
||||
password:
|
||||
min: Password must be at least {{ limit }} characters long.
|
||||
max: Password must be at most {{ limit }} characters long.
|
||||
not_blank: Please enter the password.
|
||||
avatar_image:
|
||||
file:
|
||||
max_size: The image is too big - {{ size }}{{ suffix }}. Maximum allowed size is {{ limit }}{{ suffix }}.
|
||||
|
|
|
|||
|
|
@ -38,7 +38,8 @@
|
|||
|
||||
<div style="text-align: center;">
|
||||
{% if sylius_bundle_loaded_checker('SyliusAdminBundle') %}
|
||||
<a href="#" style="display: inline-block; text-align: center; background: #1abb9c; padding: 18px 28px; color: #fff; text-decoration: none; border-radius: 3px;">
|
||||
{% set url = url('sylius_admin_render_password_reset', {'token': adminUser.passwordResetToken}) %}
|
||||
<a href="{{ url|raw }}" style="display: inline-block; text-align: center; background: #1abb9c; padding: 18px 28px; color: #fff; text-decoration: none; border-radius: 3px;">
|
||||
{{ 'sylius.email.admin_password_reset.reset_your_password'|trans({}, null, localeCode) }}
|
||||
</a>
|
||||
{% else %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace spec\Sylius\Bundle\CoreBundle\MessageDispatcher;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Bundle\CoreBundle\Message\Admin\Account\ResetPassword;
|
||||
use Sylius\Bundle\CoreBundle\MessageDispatcher\ResetPasswordDispatcher;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
final class ResetPasswordDispatcherSpec extends ObjectBehavior
|
||||
{
|
||||
function let(MessageBusInterface $messageBus): void
|
||||
{
|
||||
$this->beConstructedWith($messageBus);
|
||||
}
|
||||
|
||||
function it_dispatches_a_reset_password_message(MessageBusInterface $messageBus): void
|
||||
{
|
||||
$message = new ResetPassword('token', 'password');
|
||||
|
||||
$messageBus->dispatch($message)->willReturn(new Envelope($message))->shouldBeCalled();
|
||||
|
||||
$this->dispatch('token', 'password');
|
||||
}
|
||||
}
|
||||
|
|
@ -11,10 +11,10 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace spec\Sylius\Bundle\ApiBundle\CommandHandler\Admin;
|
||||
namespace spec\Sylius\Bundle\CoreBundle\MessageHandler\Admin\Account;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Bundle\ApiBundle\Command\Admin\ResetPassword;
|
||||
use Sylius\Bundle\CoreBundle\Message\Admin\Account\ResetPassword;
|
||||
use Sylius\Bundle\CoreBundle\Security\UserPasswordResetterInterface;
|
||||
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
|
||||
|
||||
Loading…
Add table
Reference in a new issue