Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Admin][UI] Sending administrator's password reset email #14138

Merged
merged 16 commits into from Jul 18, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/packages/security.yaml
Expand Up @@ -110,6 +110,8 @@ security:
- { path: "%sylius.security.shop_regex%/_partial", role: IS_AUTHENTICATED_ANONYMOUSLY, ips: [127.0.0.1, ::1] }
- { path: "%sylius.security.shop_regex%/_partial", role: ROLE_NO_ACCESS }

- { path: "%sylius.security.admin_regex%/forgotten-password", role: IS_AUTHENTICATED_ANONYMOUSLY }
jakubtobiasz marked this conversation as resolved.
Show resolved Hide resolved

- { path: "%sylius.security.admin_regex%/login", role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: "%sylius.security.shop_regex%/login", role: IS_AUTHENTICATED_ANONYMOUSLY }

Expand Down
2 changes: 1 addition & 1 deletion features/admin/resetting_password.feature
Expand Up @@ -7,7 +7,7 @@ Feature: Resetting an administrator's password
Background:
Given there is an administrator "sylius@example.com" identified by "sylius"

@email @api
@email @api @ui
Scenario: Sending an administrator's password reset request
When I want to reset password
And I specify email as "sylius@example.com"
Expand Down
2 changes: 1 addition & 1 deletion src/Sylius/Behat/Context/Api/EmailContext.php
Expand Up @@ -156,7 +156,7 @@ public function aWelcomingEmailShouldHaveBeenSentTo(string $recipient, string $l
public function anEmailWithInstructionsOnHowToResetTheAdministratorsPasswordShouldBeSentTo(string $recipient): void
{
$this->assertEmailContainsMessageTo(
$this->translator->trans('sylius.email.admin_password_reset.to_reset_your_password_token', [], null, 'en_US'),
$this->translator->trans('sylius.email.admin_password_reset.to_reset_your_password', [], null, 'en_US'),
$recipient
);
}
Expand Down
42 changes: 41 additions & 1 deletion src/Sylius/Behat/Context/Ui/Admin/LoginContext.php
Expand Up @@ -14,16 +14,21 @@
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;

final class LoginContext implements Context
{
public function __construct(
private DashboardPageInterface $dashboardPage,
private LoginPageInterface $loginPage
private LoginPageInterface $loginPage,
private RequestPasswordResetPage $requestPasswordResetPage,
private NotificationCheckerInterface $notificationChecker
) {
}

Expand Down Expand Up @@ -60,6 +65,30 @@ public function iLogIn()
$this->loginPage->logIn();
}

/**
* @When I want to reset password
*/
public function iWantToResetPassword(): void
{
$this->requestPasswordResetPage->open();
}

/**
* @When I specify email as :email
*/
public function iSpecifyEmailAs(string $email): void
{
$this->requestPasswordResetPage->specifyEmail($email);
}

/**
* @When I reset it
*/
public function iResetIt(): void
{
$this->requestPasswordResetPage->resetPassword();
}

/**
* @Then I should be logged in
*/
Expand Down Expand Up @@ -121,6 +150,17 @@ public function iShouldNotBeAbleToLogInAsAuthenticatedByPassword($username, $pas
Assert::false($this->dashboardPage->isOpen());
}

/**
* @Then I should be notified that email with reset instruction has been sent
*/
public function iShouldBeNotifiedThatEmailWithResetInstructionHasBeenSent(): void
{
$this->notificationChecker->checkNotification(
'If the email you have specified exists in our system, we have sent there an instruction on how to reset your password.',
NotificationType::success()
);
}

/**
* @param string $username
* @param string $password
Expand Down
11 changes: 11 additions & 0 deletions src/Sylius/Behat/Context/Ui/EmailContext.php
Expand Up @@ -147,6 +147,17 @@ public function anEmailWithShipmentDetailsOfOrderShouldBeSentTo(
}
}

/**
* @Then an email with instructions on how to reset the administrator's password should be sent to :recipient
*/
public function anEmailWithInstructionsOnHowToResetTheAdministratorsPasswordShouldBeSentTo(string $recipient): void
{
$this->assertEmailContainsMessageTo(
$this->translator->trans('sylius.email.admin_password_reset.to_reset_your_password', [], null, 'en_US'),
$recipient
);
}

private function assertEmailContainsMessageTo(string $message, string $recipient): void
{
Assert::true($this->emailChecker->hasMessageTo($message, $recipient));
Expand Down
@@ -0,0 +1,34 @@
<?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\SymfonyPage;

class RequestPasswordResetPage extends SymfonyPage implements RequestPasswordResetPageInterface
{
public function getRouteName(): string
{
return 'sylius_admin_request_password_reset';
}

public function specifyEmail(string $email): void
{
$this->getDocument()->fillField('Email', $email);
}

public function resetPassword(): void
{
$this->getDocument()->pressButton('Reset password');
}
}
@@ -0,0 +1,23 @@
<?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 RequestPasswordResetPageInterface extends SymfonyPageInterface
{
public function specifyEmail(string $email): void;

public function resetPassword(): void;
}
2 changes: 2 additions & 0 deletions src/Sylius/Behat/Resources/config/services/contexts/ui.xml
Expand Up @@ -28,6 +28,8 @@
<service id="sylius.behat.context.ui.admin.login" class="Sylius\Behat\Context\Ui\Admin\LoginContext">
<argument type="service" id="sylius.behat.page.admin.dashboard" />
<argument type="service" id="sylius.behat.page.admin.login" />
<argument type="service" id="sylius.behat.page.admin.request_password_reset" />
<argument type="service" id="sylius.behat.notification_checker"/>
</service>

<service id="sylius.behat.context.ui.admin.managing_administrators" class="Sylius\Behat\Context\Ui\Admin\ManagingAdministratorsContext">
Expand Down
Expand Up @@ -16,5 +16,7 @@
<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" />
</services>
</container>
Expand Up @@ -16,6 +16,7 @@ default:

- sylius.behat.context.ui.admin.dashboard
- sylius.behat.context.ui.admin.login
- sylius.behat.context.ui.email

filters:
tags: "@administrator_login&&@ui"
1 change: 1 addition & 0 deletions src/Sylius/Bundle/AdminBundle/.gitignore
Expand Up @@ -3,3 +3,4 @@ bin/

composer.phar
composer.lock
/.phpunit.result.cache
@@ -0,0 +1,39 @@
<?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\RequestPasswordResetType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Response;
use Twig\Environment;

final class RenderRequestPasswordResetPageAction
{
public function __construct(
private Environment $twig,
private FormFactoryInterface $formFactory
) {
}

public function __invoke(): Response
{
$form = $this->formFactory->create(RequestPasswordResetType::class);

return new Response(
$this->twig->render('@SyliusAdmin/Security/requestPasswordReset.html.twig', [
'form' => $form->createView(),
])
);
}
}
@@ -0,0 +1,65 @@
<?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\PasswordResetRequest;
use Sylius\Bundle\AdminBundle\Form\RequestPasswordResetType;
use Sylius\Bundle\CoreBundle\Message\Admin\Account\RequestResetPasswordEmail;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\RouterInterface;

final class RequestPasswordResetAction
{
public function __construct(
private FormFactoryInterface $formFactory,
private MessageBusInterface $messageBus,
private FlashBagInterface $flashBag,
private RouterInterface $router
) {
}

public function __invoke(Request $request): RedirectResponse
{
$form = $this->formFactory->create(RequestPasswordResetType::class);

$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var PasswordResetRequest $formData */
$formData = $form->getData();
$requestPasswordResetMessage = new RequestResetPasswordEmail(
$formData->getEmail()
);

$this->messageBus->dispatch($requestPasswordResetMessage);
}

$this->flashBag->set('success', 'sylius.admin.request_reset_password.success');

$options = $request->attributes->get('_sylius');
$redirectRoute = $options['redirect'] ?? 'sylius_admin_login';

if (is_array($redirectRoute)) {
return new RedirectResponse($this->router->generate(
$redirectRoute['route'] ?? 'sylius_admin_login',
$redirectRoute['params'] ?? []
));
}

return new RedirectResponse($this->router->generate($redirectRoute));
}
}
@@ -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 PasswordResetRequest
{
private ?string $email = null;

public function getEmail(): ?string
{
return $this->email;
}

public function setEmail(?string $email): void
jakubtobiasz marked this conversation as resolved.
Show resolved Hide resolved
{
$this->email = $email;
}
}
45 changes: 45 additions & 0 deletions src/Sylius/Bundle/AdminBundle/Form/RequestPasswordResetType.php
@@ -0,0 +1,45 @@
<?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;

use Sylius\Bundle\AdminBundle\Form\Model\PasswordResetRequest;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

final class RequestPasswordResetType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('email', EmailType::class, [
'label' => 'sylius.ui.email',
'required' => true,
])
;
}

public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => PasswordResetRequest::class,
]);
}

public function getBlockPrefix(): string
{
return 'sylius_admin_render_reset_password_page';
jakubtobiasz marked this conversation as resolved.
Show resolved Hide resolved
}
}