Skip to content

Commit

Permalink
Add account deletion
Browse files Browse the repository at this point in the history
  • Loading branch information
core23 committed Mar 15, 2021
1 parent f73fd22 commit 9759bdb
Show file tree
Hide file tree
Showing 23 changed files with 679 additions and 2 deletions.
25 changes: 25 additions & 0 deletions docs/deletion.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Account deletion
================

The NucleosUserBundle has built-in support for deleting the user account.

Enable feature
--------------

The feature is disabled by default. You can enable it by using the following configuration:

.. code-block:: yaml
# config/packages/nucleos_user.yaml
nucleos_user:
# ...
deletion:
enable: true
Add the routing config:

.. code-block:: yaml
# config/routes/nucleos_user.yaml
nucleos_user_deletion:
resource: "@NucleosUserBundle/Resources/config/routing/deletion.xml"
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ The following documents are available:
custom_storage_layer
routing
security
deletion
configuration_reference
migrate_from_fos
7 changes: 6 additions & 1 deletion phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ parameters:

-
message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:addDefaultsIfNotSet\\(\\)\\.$#"
count: 1
count: 2
path: src/DependencyInjection/Configuration.php

-
Expand All @@ -150,6 +150,11 @@ parameters:
count: 1
path: src/DependencyInjection/NucleosUserExtension.php

-
message: "#^Method Nucleos\\\\UserBundle\\\\DependencyInjection\\\\NucleosUserExtension\\:\\:loadDeletion\\(\\) has parameter \\$config with no value type specified in iterable type array\\.$#"
count: 1
path: src/DependencyInjection/NucleosUserExtension.php

-
message: "#^Method Nucleos\\\\UserBundle\\\\DependencyInjection\\\\NucleosUserExtension\\:\\:loadResetting\\(\\) has parameter \\$config with no value type specified in iterable type array\\.$#"
count: 1
Expand Down
2 changes: 1 addition & 1 deletion psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
</NullableReturnStatement>
</file>
<file src="src/DependencyInjection/Configuration.php">
<UndefinedMethod occurrences="4">
<UndefinedMethod occurrences="5">
<code>addDefaultsIfNotSet</code>
<code>children</code>
<code>children</code>
Expand Down
146 changes: 146 additions & 0 deletions src/Action/AccountDeletionAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php

declare(strict_types=1);

/*
* This file is part of the NucleosUserBundle package.
*
* (c) Christian Gripp <mail@core23.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Nucleos\UserBundle\Action;

use Nucleos\UserBundle\Event\AccountDeletionEvent;
use Nucleos\UserBundle\Event\AccountDeletionResponseEvent;
use Nucleos\UserBundle\Event\GetResponseAccountDeletionEvent;
use Nucleos\UserBundle\Form\Model\AccountDeletion;
use Nucleos\UserBundle\Form\Type\AccountDeletionFormType;
use Nucleos\UserBundle\Model\UserInterface;
use Nucleos\UserBundle\Model\UserManagerInterface;
use Nucleos\UserBundle\NucleosUserEvents;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Twig\Environment;

final class AccountDeletionAction
{
/**
* @var Environment
*/
private $twig;

/**
* @var RouterInterface
*/
private $router;

/**
* @var UserManagerInterface
*/
private $userManager;

/**
* @var TokenStorageInterface
*/
private $tokenStorage;

/**
* @var FormFactoryInterface
*/
private $formFactory;

/**
* @var EventDispatcherInterface
*/
private $eventDispatcher;

public function __construct(
Environment $twig,
RouterInterface $router,
UserManagerInterface $userManager,
TokenStorageInterface $tokenStorage,
FormFactoryInterface $formFactory,
EventDispatcherInterface $eventDispatcher
) {
$this->twig = $twig;
$this->router = $router;
$this->userManager = $userManager;
$this->formFactory = $formFactory;
$this->eventDispatcher = $eventDispatcher;
$this->tokenStorage = $tokenStorage;
}

public function __invoke(Request $request): Response
{
$user = $this->getUser();

if (!$user instanceof UserInterface) {
throw new AccessDeniedException('Access Denied.');
}

$event = new GetResponseAccountDeletionEvent($user, $request);
$this->eventDispatcher->dispatch($event, NucleosUserEvents::ACCOUNT_DELETION_INITIALIZE);

if (null !== $response = $event->getResponse()) {
return $response;
}

$form = $this->formFactory
->create(AccountDeletionFormType::class, new AccountDeletion(), [
'action' => $this->router->generate('nucleos_user_delete_account'),
])
->add('delete', SubmitType::class, [
'label' => 'deletion.submit',
])
;
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
return $this->processDeletion($user, $request);
}

return new Response($this->twig->render('@NucleosUser/Account/deletion.html.twig', [
'form' => $form->createView(),
]));
}

private function getUser(): ?UserInterface
{
$token = $this->tokenStorage->getToken();

if (null === $token) {
return null;
}

$user = $token->getUser();

if ($user instanceof UserInterface) {
return $user;
}

return null;
}

private function processDeletion(UserInterface $user, Request $request): Response
{
$event = new AccountDeletionEvent($user, $request);
$this->eventDispatcher->dispatch($event, NucleosUserEvents::ACCOUNT_DELETION);

$this->userManager->deleteUser($user);

$event = new AccountDeletionResponseEvent($user, $request, new RedirectResponse($this->router->generate('nucleos_user_security_logout')));
$this->eventDispatcher->dispatch($event, NucleosUserEvents::ACCOUNT_DELETION_SUCCESS);

return $event->getResponse();
}
}
13 changes: 13 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public function getConfigTreeBuilder(): TreeBuilder

$this->addMainSection($rootNode);
$this->addResettingSection($rootNode);
$this->addDeletionSection($rootNode);
$this->addGroupSection($rootNode);
$this->addServiceSection($rootNode);

Expand Down Expand Up @@ -89,6 +90,18 @@ private function addResettingSection(NodeDefinition $node): void
;
}

private function addDeletionSection(NodeDefinition $node): void
{
$node
->addDefaultsIfNotSet()
->children()
->arrayNode('deletion')
->canBeEnabled()
->end()
->end()
;
}

private function addServiceSection(NodeDefinition $node): void
{
$node
Expand Down
10 changes: 10 additions & 0 deletions src/DependencyInjection/NucleosUserExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public function load(array $configs, ContainerBuilder $container): void
]);

$this->loadChangePassword($loader);
$this->loadDeletion($config['deletion'], $loader);
$this->loadResetting($config['resetting'], $container, $loader, $config['from_email']);

if (isset($config['group'])) {
Expand Down Expand Up @@ -204,6 +205,15 @@ private function loadChangePassword(FileLoader $loader): void
$loader->load('change_password.php');
}

private function loadDeletion(array $config, FileLoader $loader): void
{
if (true !== $config['enabled']) {
return;
}

$loader->load('deletion.php');
}

private function loadResetting(array $config, ContainerBuilder $container, FileLoader $loader, string $fromEmail): void
{
$this->mailerNeeded = true;
Expand Down
47 changes: 47 additions & 0 deletions src/Event/AccountDeletionEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

/*
* This file is part of the NucleosUserBundle package.
*
* (c) Christian Gripp <mail@core23.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Nucleos\UserBundle\Event;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Contracts\EventDispatcher\Event;

class AccountDeletionEvent extends Event
{
/**
* @var Request
*/
private $request;

/**
* @var UserInterface
*/
private $user;

public function __construct(UserInterface $user, Request $request)
{
$this->user = $user;
$this->request = $request;
}

public function getRequest(): Request
{
return $this->request;
}

public function getUser(): UserInterface
{
return $this->user;
}
}
38 changes: 38 additions & 0 deletions src/Event/AccountDeletionResponseEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

/*
* This file is part of the NucleosUserBundle package.
*
* (c) Christian Gripp <mail@core23.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Nucleos\UserBundle\Event;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\User\UserInterface;

final class AccountDeletionResponseEvent extends AccountDeletionEvent
{
/**
* @var Response
*/
private $response;

public function __construct(UserInterface $user, Request $request, Response $response)
{
parent::__construct($user, $request);

$this->response = $response;
}

public function getResponse(): Response
{
return $this->response;
}
}
34 changes: 34 additions & 0 deletions src/Event/GetResponseAccountDeletionEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

/*
* This file is part of the NucleosUserBundle package.
*
* (c) Christian Gripp <mail@core23.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Nucleos\UserBundle\Event;

use Symfony\Component\HttpFoundation\Response;

final class GetResponseAccountDeletionEvent extends AccountDeletionEvent
{
/**
* @var Response|null
*/
private $response;

public function setResponse(Response $response): void
{
$this->response = $response;
}

public function getResponse(): ?Response
{
return $this->response;
}
}
2 changes: 2 additions & 0 deletions src/EventListener/FlashListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ final class FlashListener implements EventSubscriberInterface
private static $successMessages = [
NucleosUserEvents::CHANGE_PASSWORD_COMPLETED => 'change_password.flash.success',
NucleosUserEvents::RESETTING_RESET_COMPLETED => 'resetting.flash.success',
NucleosUserEvents::ACCOUNT_DELETION_SUCCESS => 'deletion.success',
];

/**
Expand All @@ -51,6 +52,7 @@ public static function getSubscribedEvents(): array
return [
NucleosUserEvents::CHANGE_PASSWORD_COMPLETED => 'addSuccessFlash',
NucleosUserEvents::RESETTING_RESET_COMPLETED => 'addSuccessFlash',
NucleosUserEvents::ACCOUNT_DELETION_SUCCESS => 'addSuccessFlash',
];
}

Expand Down
Loading

0 comments on commit 9759bdb

Please sign in to comment.