From 02f5877f46c2a2709d8d77fe4fd77a0dfc0f469c Mon Sep 17 00:00:00 2001 From: bidi Date: Tue, 10 Dec 2024 16:03:10 +0200 Subject: [PATCH] updated user deleted status Signed-off-by: bidi --- .../migrations/Version20241120160406.php | 2 +- .../src/Middleware/RememberMeMiddleware.php | 2 +- src/User/src/Controller/AccountController.php | 13 ++++--- src/User/src/Entity/User.php | 21 +--------- src/User/src/Entity/UserInterface.php | 2 - src/User/src/Enum/UserStatusEnum.php | 1 + src/User/src/Form/ProfileDeleteForm.php | 2 +- src/User/src/Repository/UserRepository.php | 17 +++++++-- src/User/src/Service/UserService.php | 38 +++++++++++-------- src/User/src/Service/UserServiceInterface.php | 2 + .../Middleware/RememberMeMiddlewareTest.php | 2 - 11 files changed, 52 insertions(+), 50 deletions(-) diff --git a/data/doctrine/migrations/Version20241120160406.php b/data/doctrine/migrations/Version20241120160406.php index 89697f69..9ed9fbb7 100644 --- a/data/doctrine/migrations/Version20241120160406.php +++ b/data/doctrine/migrations/Version20241120160406.php @@ -21,7 +21,7 @@ public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs $this->addSql('CREATE TABLE contact_message (uuid BINARY(16) NOT NULL, email VARCHAR(150) NOT NULL, name VARCHAR(150) NOT NULL, subject LONGTEXT NOT NULL, message LONGTEXT NOT NULL, platform LONGTEXT NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('CREATE TABLE user (uuid BINARY(16) NOT NULL, identity VARCHAR(191) NOT NULL, password VARCHAR(191) NOT NULL, status ENUM(\'active\', \'pending\') DEFAULT \'pending\' NOT NULL, isDeleted TINYINT(1) NOT NULL, hash VARCHAR(64) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_8D93D6496A95E9C4 (identity), UNIQUE INDEX UNIQ_8D93D649D1B862B8 (hash), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4'); + $this->addSql('CREATE TABLE user (uuid BINARY(16) NOT NULL, identity VARCHAR(191) NOT NULL, password VARCHAR(191) NOT NULL, status ENUM(\'active\', \'pending\', \'deleted\') DEFAULT \'pending\' NOT NULL, hash VARCHAR(64) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_8D93D6496A95E9C4 (identity), UNIQUE INDEX UNIQ_8D93D649D1B862B8 (hash), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4'); $this->addSql('CREATE TABLE user_roles (userUuid BINARY(16) NOT NULL, roleUuid BINARY(16) NOT NULL, INDEX IDX_54FCD59FD73087E9 (userUuid), INDEX IDX_54FCD59F88446210 (roleUuid), PRIMARY KEY(userUuid, roleUuid)) DEFAULT CHARACTER SET utf8mb4'); $this->addSql('CREATE TABLE user_avatar (uuid BINARY(16) NOT NULL, name VARCHAR(191) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, userUuid BINARY(16) NOT NULL, UNIQUE INDEX UNIQ_73256912D73087E9 (userUuid), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4'); $this->addSql('CREATE TABLE user_detail (uuid BINARY(16) NOT NULL, firstName VARCHAR(191) DEFAULT NULL, lastName VARCHAR(191) DEFAULT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, userUuid BINARY(16) NOT NULL, UNIQUE INDEX UNIQ_4B5464AED73087E9 (userUuid), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4'); diff --git a/src/App/src/Middleware/RememberMeMiddleware.php b/src/App/src/Middleware/RememberMeMiddleware.php index e8148e03..80699b3e 100644 --- a/src/App/src/Middleware/RememberMeMiddleware.php +++ b/src/App/src/Middleware/RememberMeMiddleware.php @@ -46,7 +46,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $hash === $rememberUser->getRememberMeToken() && $rememberUser->getUserAgent() === $deviceType && $rememberUser->getExpireDate() > new DateTimeImmutable('now') && - $user->getIsDeleted() === false + $user->isDeleted() === false ) { $userIdentity = UserIdentity::fromEntity($user); $this->authenticationService->getStorage()->write($userIdentity); diff --git a/src/User/src/Controller/AccountController.php b/src/User/src/Controller/AccountController.php index 3bf88ee4..d8d4896f 100644 --- a/src/User/src/Controller/AccountController.php +++ b/src/User/src/Controller/AccountController.php @@ -66,6 +66,11 @@ public function activateAction(): ResponseInterface return new RedirectResponse($this->router->generateUri('user', ['action' => 'login'])); } + if ($user->isDeleted()) { + $this->messenger->addError(Message::ACCOUNT_NOT_FOUND, 'user-login'); + return new RedirectResponse($this->router->generateUri('user', ['action' => 'login'])); + } + if ($user->isActive()) { $this->messenger->addError(Message::USER_ALREADY_ACTIVATED, 'user-login'); return new RedirectResponse($this->router->generateUri('user', ['action' => 'login'])); @@ -96,7 +101,7 @@ public function unregisterAction(): ResponseInterface return new RedirectResponse($this->router->generateUri('user', ['action' => 'login'])); } - if ($user->getIsDeleted() === User::IS_DELETED_YES) { + if ($user->isDeleted()) { $this->messenger->addError(Message::USER_ALREADY_DEACTIVATED, 'user-login'); return new RedirectResponse($this->router->generateUri('user', ['action' => 'login'])); } @@ -107,7 +112,7 @@ public function unregisterAction(): ResponseInterface } try { - $this->userService->updateUser($user, ['isDeleted' => User::IS_DELETED_YES]); + $this->userService->deleteUser($user); } catch (Exception $exception) { $this->messenger->addError($exception->getMessage(), 'user-login'); return new RedirectResponse($this->router->generateUri('user', ['action' => 'login'])); @@ -423,10 +428,8 @@ public function deleteAccountAction(): ResponseInterface if (RequestMethodInterface::METHOD_POST === $this->request->getMethod()) { $form->setData($this->request->getParsedBody()); if ($form->isValid()) { - /** @var array $userData */ - $userData = $form->getData(); try { - $this->userService->updateUser($user, $userData); + $this->userService->deleteUser($user); $this->userService->deleteAvatar($user); } catch (Exception $e) { $this->messenger->addData('shouldRebind', true); diff --git a/src/User/src/Entity/User.php b/src/User/src/Entity/User.php index 4457895e..cd04414f 100644 --- a/src/User/src/Entity/User.php +++ b/src/User/src/Entity/User.php @@ -45,9 +45,6 @@ class User extends AbstractEntity implements UserInterface #[ORM\Column(type: 'user_status_enum', options: ['default' => UserStatusEnum::Pending])] protected UserStatusEnum $status = UserStatusEnum::Pending; - #[ORM\Column(name: 'isDeleted', type: 'boolean')] - protected bool $isDeleted = self::IS_DELETED_NO; - #[ORM\Column(name: 'hash', type: 'string', length: 64, unique: true, nullable: false)] protected string $hash; @@ -138,18 +135,6 @@ public function setStatus(UserStatusEnum $status): self return $this; } - public function getIsDeleted(): bool - { - return $this->isDeleted; - } - - public function setIsDeleted(bool $isDeleted): self - { - $this->isDeleted = $isDeleted; - - return $this; - } - public function getHash(): string { return $this->hash; @@ -213,11 +198,9 @@ public function isPending(): bool return $this->status === UserStatusEnum::Pending; } - public function markAsDeleted(): self + public function isDeleted(): bool { - $this->isDeleted = self::IS_DELETED_YES; - - return $this; + return $this->status === UserStatusEnum::Deleted; } public function getName(): string diff --git a/src/User/src/Entity/UserInterface.php b/src/User/src/Entity/UserInterface.php index 892a2914..3e93b875 100644 --- a/src/User/src/Entity/UserInterface.php +++ b/src/User/src/Entity/UserInterface.php @@ -38,8 +38,6 @@ public function addRole(UserRole $role): UserInterface; public function removeRole(UserRole $role): UserInterface; - public function getIsDeleted(): bool; - public function getArrayCopy(): array; public function activate(): UserInterface; diff --git a/src/User/src/Enum/UserStatusEnum.php b/src/User/src/Enum/UserStatusEnum.php index 9953d498..c9ccb486 100644 --- a/src/User/src/Enum/UserStatusEnum.php +++ b/src/User/src/Enum/UserStatusEnum.php @@ -8,4 +8,5 @@ enum UserStatusEnum: string { case Active = 'active'; case Pending = 'pending'; + case Deleted = 'deleted'; } diff --git a/src/User/src/Form/ProfileDeleteForm.php b/src/User/src/Form/ProfileDeleteForm.php index a6dbb0f3..0f3f150e 100644 --- a/src/User/src/Form/ProfileDeleteForm.php +++ b/src/User/src/Form/ProfileDeleteForm.php @@ -44,7 +44,7 @@ public function init(): void 'title' => 'Delete account', ], 'options' => [ - 'label' => 'I want to delete account', + 'label' => 'I want to delete my account', 'use_hidden_element' => false, 'checked_value' => (string) User::IS_DELETED_YES, 'unchecked_value' => (string) User::IS_DELETED_NO, diff --git a/src/User/src/Repository/UserRepository.php b/src/User/src/Repository/UserRepository.php index 697e3b43..0c06d75c 100644 --- a/src/User/src/Repository/UserRepository.php +++ b/src/User/src/Repository/UserRepository.php @@ -11,6 +11,7 @@ use Exception; use Frontend\User\Entity\User; use Frontend\User\Entity\UserRememberMe; +use Frontend\User\Enum\UserStatusEnum; use Ramsey\Uuid\Uuid; use function is_string; @@ -36,6 +37,9 @@ public function findByUuid(string $uuid): ?User ->where("user.uuid = :uuid") ->setParameter('uuid', $uuid) ->setMaxResults(1); + + //ignore deleted users + $qb->andWhere('user.status != :status')->setParameter('status', UserStatusEnum::Deleted); return $qb->getQuery()->useQueryCache(true)->getOneOrNullResult(); } @@ -72,7 +76,10 @@ public function findByResetPasswordHash(string $hash): ?User $qb = $this->getEntityManager()->createQueryBuilder(); $qb->select(['user', 'resetPasswords'])->from(User::class, 'user') ->leftJoin('user.resetPasswords', 'resetPasswords') - ->andWhere('resetPasswords.hash = :hash')->setParameter('hash', $hash); + ->andWhere('resetPasswords.hash = :hash') + ->setParameter('hash', $hash) + ->andWhere('user.status != :deleted') + ->setParameter('deleted', UserStatusEnum::Deleted); return $qb->getQuery()->useQueryCache(true)->getSingleResult(); } catch (Exception) { @@ -95,7 +102,9 @@ public function getRememberUser(string $token): ?UserRememberMe $qb->select('user_remember_me') ->from(UserRememberMe::class, 'user_remember_me') ->where('user_remember_me.rememberMeToken = :token') - ->setParameter('token', $token); + ->setParameter('token', $token) + ->andWhere('user.status != :deleted') + ->setParameter('deleted', UserStatusEnum::Deleted); return $qb->getQuery()->useQueryCache(true)->getOneOrNullResult(); } @@ -111,7 +120,9 @@ public function findRememberMeUser(User $user, string $userAgent): ?UserRemember ->where('user_remember_me.user = :uuid') ->setParameter('uuid', $user->getUuid()->getBytes()) ->andWhere('user_remember_me.userAgent = :userAgent') - ->setParameter('userAgent', $userAgent); + ->setParameter('userAgent', $userAgent) + ->andWhere('user.status != :deleted') + ->setParameter('deleted', UserStatusEnum::Deleted); return $qb->getQuery()->useQueryCache(true)->getOneOrNullResult(); } diff --git a/src/User/src/Service/UserService.php b/src/User/src/Service/UserService.php index 43ef421e..ec9c0c3e 100644 --- a/src/User/src/Service/UserService.php +++ b/src/User/src/Service/UserService.php @@ -138,22 +138,6 @@ public function updateUser(User $user, array $data = []): User $user->setStatus($data['status']); } - if (isset($data['isDeleted'])) { - $user->setIsDeleted((bool) $data['isDeleted']); - - if ((bool) $data['isDeleted'] === true) { - // make user anonymous - $user->setIdentity( - sprintf('anonymous%s@%s', date('dmYHis'), $this->config['userAnonymizeAppend']) - ); - $userDetails = $user->getDetail(); - $userDetails->setFirstName('anonymous' . date('dmYHis')); - $userDetails->setLastName('anonymous' . date('dmYHis')); - - $user->setDetail($userDetails); - } - } - if (isset($data['hash'])) { $user->setHash($data['hash']); } @@ -189,6 +173,28 @@ public function updateUser(User $user, array $data = []): User return $this->userRepository->saveUser($user); } + public function deleteUser(User $user): User + { + $user->setStatus(UserStatusEnum::Deleted); + + if ($user->isDeleted()) { + $placeholder = $this->getAnonymousPlaceholder(); + + // make user anonymous + $user + ->setIdentity($placeholder . $this->config['userAnonymizeAppend']) + ->getDetail() + ->setFirstName($placeholder) + ->setLastName($placeholder); + } + return $this->userRepository->saveUser($user); + } + + private function getAnonymousPlaceholder(): string + { + return 'anonymous' . date('dmYHis'); + } + protected function createAvatar(User $user, UploadedFile $uploadedFile): UserAvatar { $path = sprintf('%s/%s/', $this->config['uploads']['user']['path'], $user->getUuid()->toString()); diff --git a/src/User/src/Service/UserServiceInterface.php b/src/User/src/Service/UserServiceInterface.php index 4eaf646d..324ff8c3 100644 --- a/src/User/src/Service/UserServiceInterface.php +++ b/src/User/src/Service/UserServiceInterface.php @@ -34,6 +34,8 @@ public function deleteExpiredRememberMeTokens(): void; public function updateUser(User $user, array $data = []): UserInterface; + public function deleteUser(User $user): UserInterface; + public function deleteAvatar(User $user): void; public function deleteAvatarFile(string $path): bool; diff --git a/test/Unit/App/Middleware/RememberMeMiddlewareTest.php b/test/Unit/App/Middleware/RememberMeMiddlewareTest.php index 97c0975c..f0ccac7f 100644 --- a/test/Unit/App/Middleware/RememberMeMiddlewareTest.php +++ b/test/Unit/App/Middleware/RememberMeMiddlewareTest.php @@ -68,7 +68,6 @@ public function testAutologin(): void $user = (new User()) ->setIdentity('test@dotkernel.com') - ->setIsDeleted(false) ->activate(); $detail = (new UserDetail()) @@ -126,7 +125,6 @@ public function testAutologinExpired(): void $user = (new User()) ->setIdentity('test@dotkernel.com') - ->setIsDeleted(false) ->activate(); $detail = (new UserDetail())