<?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 App\Controller\Shop;
use App\Entity\User\ShopUser;
use App\Security\Guard\ShopUserProxyAuthenticator;
use App\Service\ApiBridge;
use Doctrine\ORM\EntityManagerInterface;
use FOS\RestBundle\View\View;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Sylius\Bundle\ResourceBundle\Controller\RequestConfiguration;
use Sylius\Bundle\ResourceBundle\Controller\ResourceController;
use Sylius\Bundle\UserBundle\Form\Model\ChangePassword;
use Sylius\Bundle\UserBundle\Form\Model\PasswordResetRequest;
use Sylius\Bundle\UserBundle\Form\Type\UserChangePasswordType;
use Sylius\Bundle\UserBundle\Form\Type\UserRequestPasswordResetType;
use Sylius\Bundle\UserBundle\Form\Type\UserResetPasswordType;
use Sylius\Bundle\UserBundle\UserEvents;
use Sylius\Component\User\Model\UserInterface;
use Sylius\Component\User\Repository\UserRepositoryInterface;
use Sylius\Component\User\Security\Generator\GeneratorInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Webmozart\Assert\Assert;
class UserController extends ResourceController
{
public function changePasswordAction(Request $request): Response
{
$configuration = $this->requestConfigurationFactory->create($this->metadata, $request);
if (!$this->container->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
throw new AccessDeniedException('You have to be registered user to access this section.');
}
$user = $this->container->get('security.token_storage')->getToken()->getUser();
$changePassword = new ChangePassword();
$formType = $this->getSyliusAttribute($request, 'form', UserChangePasswordType::class);
$form = $this->createResourceForm($configuration, $formType, $changePassword);
if (in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'], true) && $form->handleRequest($request)->isValid()) {
return $this->handleChangePassword($request, $configuration, $user, $changePassword->getNewPassword());
}
if (!$configuration->isHtmlRequest()) {
return $this->viewHandler->handle($configuration, View::create($form, Response::HTTP_BAD_REQUEST));
}
return new Response($this->container->get('twig')->render(
$configuration->getTemplate('changePassword.html'),
['form' => $form->createView()]
));
}
public function requestPasswordResetTokenAction(Request $request, ApiBridge $api): Response
{
/** @var GeneratorInterface $generator */
$generator = $this->container->get(sprintf('sylius.%s.token_generator.password_reset', $this->metadata->getName()));
return $this->prepareResetPasswordRequest(
$request,
$api,
UserEvents::REQUEST_RESET_PASSWORD_TOKEN
);
}
public function requestPasswordResetPinAction(Request $request): Response
{
/** @var GeneratorInterface $generator */
$generator = $this->container->get(sprintf('sylius.%s.pin_generator.password_reset', $this->metadata->getName()));
return $this->prepareResetPasswordRequest($request, $generator, UserEvents::REQUEST_RESET_PASSWORD_PIN);
}
/**
* @param Request $request
* @param ApiBridge $apiBridge
* @param string $token
* @param EntityManagerInterface $em
* @param GuardAuthenticatorHandler $guardAuthenticatorHandler
* @param ShopUserProxyAuthenticator $shopUserProxyAuthenticator
* @return Response
* @throws ClientExceptionInterface
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws RedirectionExceptionInterface
* @throws ServerExceptionInterface
* @throws TransportExceptionInterface
* @throws \JsonException
*/
public function resetPasswordAction(
Request $request,
ApiBridge $apiBridge,
string $token,
EntityManagerInterface $em,
GuardAuthenticatorHandler $guardAuthenticatorHandler,
ShopUserProxyAuthenticator $shopUserProxyAuthenticator
): Response {
if (!$account = $apiBridge->findCrmAccount($token)) {
throw new NotFoundHttpException('Account not found');
}
$form = $this->createForm(UserResetPasswordType::class);
$form->handleRequest($request);
/** @var ShopUser */
$shopUser = $em->getRepository(ShopUser::class)->findOneBy(['username' => $account['email']]);
if (!$shopUser) {
throw new NotFoundHttpException('Account not found');
}
$shopUser->setPasswordResetToken($token);
if ($form->isSubmitted()) {
if ($form->isValid()) {
$data = [
'id' => $account['id'],
'password' => $form->get('password')->getData(), // TODO FIXME ne pas envoyer le pswd en clair d'une api à l'autre
'enabled' => true,
'activationToken' => null
];
try {
$apiBridge->updateCrmAccount($data);
} catch (\Exception|TransportExceptionInterface $e) {
$this->addTranslatedFlash('error', 'Le nouveau mot de passe est identique au précédent');
return $this->redirectToRoute('sylius_shop_password_reset', ['token' => $token]);
}
/** @var ShopUser $shopUser */
$shopUser->setEnabled(true);
$shopUser->setEmailVerificationToken(null);
$shopUser->setVerifiedAt(new \DateTime('now'));
$em->flush();
$this->addTranslatedFlash('success', 'sylius.user.reset_password');
// Auto-login ShopUser & return to Homepage
return $guardAuthenticatorHandler->authenticateUserAndHandleSuccess(
$shopUser,
$request,
$shopUserProxyAuthenticator,
'shop'
);
}
}
$configuration = $this->requestConfigurationFactory->create($this->metadata, $request);
return new Response($this->container->get('twig')->render(
$configuration->getTemplate('resetPassword.html'),
[
'form' => $form->createView(),
'user' => $shopUser,
]
));
}
public function verifyAction(Request $request, string $token): Response
{
$configuration = $this->requestConfigurationFactory->create($this->metadata, $request);
$redirectRoute = $this->getSyliusAttribute($request, 'redirect', null);
$response = $this->redirectToRoute($redirectRoute);
/** @var UserInterface|null $user */
$user = $this->repository->findOneBy(['emailVerificationToken' => $token]);
if (null === $user) {
if (!$configuration->isHtmlRequest()) {
return $this->viewHandler->handle($configuration, View::create($configuration, Response::HTTP_BAD_REQUEST));
}
$this->addTranslatedFlash('error', 'sylius.user.verify_email_by_invalid_token');
return $this->redirectToRoute($redirectRoute);
}
$user->setVerifiedAt(new \DateTime());
$user->setEmailVerificationToken(null);
$user->enable();
$eventDispatcher = $this->container->get('event_dispatcher');
$eventDispatcher->dispatch(new GenericEvent($user), UserEvents::PRE_EMAIL_VERIFICATION);
$this->manager->flush();
$eventDispatcher->dispatch(new GenericEvent($user), UserEvents::POST_EMAIL_VERIFICATION);
if (!$configuration->isHtmlRequest()) {
return $this->viewHandler->handle($configuration, View::create($user));
}
$flashMessage = $this->getSyliusAttribute($request, 'flash', 'sylius.user.verify_email');
$this->addTranslatedFlash('success', $flashMessage);
return $response;
}
public function requestVerificationTokenAction(Request $request): Response
{
$configuration = $this->requestConfigurationFactory->create($this->metadata, $request);
$redirectRoute = $this->getSyliusAttribute($request, 'redirect', 'referer');
$user = $this->getUser();
if (null === $user) {
if (!$configuration->isHtmlRequest()) {
return $this->viewHandler->handle($configuration, View::create($configuration, Response::HTTP_UNAUTHORIZED));
}
$this->addTranslatedFlash('notice', 'sylius.user.verify_no_user');
return $this->redirectHandler->redirectToRoute($configuration, $redirectRoute);
}
if (null !== $user->getVerifiedAt()) {
if (!$configuration->isHtmlRequest()) {
return $this->viewHandler->handle($configuration, View::create($configuration, Response::HTTP_BAD_REQUEST));
}
$this->addTranslatedFlash('notice', 'sylius.user.verify_verified_email');
return $this->redirectHandler->redirectToRoute($configuration, $redirectRoute);
}
/** @var GeneratorInterface $tokenGenerator */
$tokenGenerator = $this->container->get(sprintf('sylius.%s.token_generator.email_verification', $this->metadata->getName()));
$user->setEmailVerificationToken($tokenGenerator->generate());
$this->manager->flush();
$eventDispatcher = $this->container->get('event_dispatcher');
$eventDispatcher->dispatch(new GenericEvent($user), UserEvents::REQUEST_VERIFICATION_TOKEN);
if (!$configuration->isHtmlRequest()) {
return $this->viewHandler->handle($configuration, View::create(null, Response::HTTP_NO_CONTENT));
}
$this->addTranslatedFlash('success', 'sylius.user.verify_email_request');
return $this->redirectHandler->redirectToRoute($configuration, $redirectRoute);
}
protected function prepareResetPasswordRequest(Request $request, ApiBridge $api, string $senderEvent): Response
{
$configuration = $this->requestConfigurationFactory->create($this->metadata, $request);
$passwordReset = new PasswordResetRequest();
$formType = $this->getSyliusAttribute($request, 'form', UserRequestPasswordResetType::class);
$form = $this->createResourceForm($configuration, $formType, $passwordReset);
$template = $this->getSyliusAttribute($request, 'template', null);
if ($configuration->isHtmlRequest()) {
Assert::notNull($template, 'Template is not configured.');
}
if (in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'], true) && $form->handleRequest($request)->isValid()) {
$userRepository = $this->repository;
/** @var UserRepositoryInterface $userRepository */
Assert::isInstanceOf($userRepository, UserRepositoryInterface::class);
$user = $userRepository->findOneByEmail($passwordReset->getEmail());
if (null !== $user) {
$this->handleResetPasswordRequest($api, $user, $senderEvent);
}
if (!$configuration->isHtmlRequest()) {
return $this->viewHandler->handle($configuration, View::create(null, Response::HTTP_NO_CONTENT));
}
$this->addTranslatedFlash('success', 'sylius.user.reset_password_request');
$redirectRoute = $this->getSyliusAttribute($request, 'redirect', null);
Assert::notNull($redirectRoute, 'Redirect is not configured.');
if (is_array($redirectRoute)) {
return $this->redirectHandler->redirectToRoute(
$configuration,
$configuration->getParameters()->get('redirect')['route'],
$configuration->getParameters()->get('redirect')['parameters']
);
}
return $this->redirectHandler->redirectToRoute($configuration, $redirectRoute);
}
if (!$configuration->isHtmlRequest()) {
return $this->viewHandler->handle($configuration, View::create($form, Response::HTTP_BAD_REQUEST));
}
return new Response($this->container->get('twig')->render(
$template,
[
'form' => $form->createView(),
]
));
}
protected function addTranslatedFlash(string $type, string $message): void
{
$translator = $this->container->get('translator');
$this->container->get('session')->getFlashBag()->add($type, $translator->trans($message, [], 'flashes'));
}
/**
* @param object $object
*/
protected function createResourceForm(
RequestConfiguration $configuration,
string $type,
$object
): FormInterface {
if (!$configuration->isHtmlRequest()) {
return $this->container->get('form.factory')->createNamed('', $type, $object, ['csrf_protection' => false]);
}
return $this->container->get('form.factory')->create($type, $object);
}
protected function handleExpiredToken(Request $request, RequestConfiguration $configuration, UserInterface $user): Response
{
$user->setPasswordResetToken(null);
$user->setPasswordRequestedAt(null);
$this->manager->flush();
if (!$configuration->isHtmlRequest()) {
return $this->viewHandler->handle($configuration, View::create($user, Response::HTTP_BAD_REQUEST));
}
$this->addTranslatedFlash('error', 'sylius.user.expire_password_reset_token');
$redirectRouteName = $this->getSyliusAttribute($request, 'redirect', null);
Assert::notNull($redirectRouteName, 'Redirect is not configured.');
return new RedirectResponse($this->container->get('router')->generate($redirectRouteName));
}
protected function handleResetPasswordRequest(
ApiBridge $api,
UserInterface $user
): void {
$redirectUrl = $this->generateUrl(
'sylius_shop_request_password_reset_token',
[],
UrlGeneratorInterface::ABSOLUTE_URL
);
$api->requestCrmResetPassword($user->getEmail(), $redirectUrl . '/');
}
protected function handleResetPassword(
Request $request,
RequestConfiguration $configuration,
UserInterface $user,
string $newPassword
): Response {
$user->setPlainPassword($newPassword);
$user->setPasswordResetToken(null);
$user->setPasswordRequestedAt(null);
$dispatcher = $this->container->get('event_dispatcher');
$dispatcher->dispatch(new GenericEvent($user), UserEvents::PRE_PASSWORD_RESET);
$this->manager->flush();
$this->addTranslatedFlash('success', 'sylius.user.reset_password');
$dispatcher->dispatch(new GenericEvent($user), UserEvents::POST_PASSWORD_RESET);
if (!$configuration->isHtmlRequest()) {
return $this->viewHandler->handle($configuration, View::create(null, Response::HTTP_NO_CONTENT));
}
$redirectRouteName = $this->getSyliusAttribute($request, 'redirect', null);
Assert::notNull($redirectRouteName, 'Redirect is not configured.');
return new RedirectResponse($this->container->get('router')->generate($redirectRouteName));
}
protected function handleChangePassword(
Request $request,
RequestConfiguration $configuration,
UserInterface $user,
string $newPassword
): Response {
$user->setPlainPassword($newPassword);
$dispatcher = $this->container->get('event_dispatcher');
$dispatcher->dispatch(new GenericEvent($user), UserEvents::PRE_PASSWORD_CHANGE);
$this->manager->flush();
$this->addTranslatedFlash('success', 'sylius.user.change_password');
$dispatcher->dispatch(new GenericEvent($user), UserEvents::POST_PASSWORD_CHANGE);
if (!$configuration->isHtmlRequest()) {
return $this->viewHandler->handle($configuration, View::create(null, Response::HTTP_NO_CONTENT));
}
$redirectRouteName = $this->getSyliusAttribute($request, 'redirect', null);
Assert::notNull($redirectRouteName, 'Redirect is not configured.');
return new RedirectResponse($this->container->get('router')->generate($redirectRouteName));
}
protected function getUser(): ?UserInterface
{
$user = parent::getUser();
$authorizationChecker = $this->container->get('security.authorization_checker');
if (
$authorizationChecker->isGranted('IS_AUTHENTICATED_REMEMBERED') &&
$user instanceof UserInterface
) {
return $user;
}
return null;
}
private function getSyliusAttribute(Request $request, string $attribute, $default = null)
{
$attributes = $request->attributes->get('_sylius');
return $attributes[$attribute] ?? $default;
}
}