From 1f53f186fa4824141966b425f10a29243364e4e7 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Tue, 4 Jun 2024 17:25:27 +0200 Subject: [PATCH 1/3] refs #450. Reset password from user area --- config/api_platform/User.yaml | 10 ++++++- config/packages/api_platform.yaml | 1 + src/Controller/.gitignore | 0 src/Controller/ResetPasswordAction.php | 31 +++++++++++++++++++ src/Dto/Input/UserInput.php | 37 +++++++++++++++++++---- src/Entity/User.php | 41 ++++++++++++++++++++++++++ src/Handler/ResetPasswordHandler.php | 30 +++++++++++++++++++ src/State/Provider/UserProvider.php | 1 + 8 files changed, 145 insertions(+), 6 deletions(-) delete mode 100644 src/Controller/.gitignore create mode 100644 src/Controller/ResetPasswordAction.php create mode 100644 src/Handler/ResetPasswordHandler.php diff --git a/config/api_platform/User.yaml b/config/api_platform/User.yaml index efe4597..6d39866 100644 --- a/config/api_platform/User.yaml +++ b/config/api_platform/User.yaml @@ -8,7 +8,6 @@ resources: groups: ['default', 'user:read'] denormalization_context: groups: ['user:write'] - operations: ApiPlatform\Metadata\GetCollection: provider: App\State\Provider\UserProvider @@ -26,6 +25,15 @@ resources: validationContext: groups: [ 'default', 'user:post' ] ApiPlatform\Metadata\Delete: ~ + reset_password: + provider: App\State\Provider\UserProvider + class: ApiPlatform\Metadata\Put + method: PUT + input: App\Dto\Input\UserInput + uriTemplate: /users/{uuid}/reset-password + controller: App\Controller\ResetPasswordAction + validationContext: + groups: [ 'user:reset-password' ] properties: App\Entity\User: diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index ee2c7ec..6a6729c 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -9,6 +9,7 @@ api_platform: jsonld: ['application/ld+json', 'application/json'] mapping: paths: ['%kernel.project_dir%/config/api_platform', '%kernel.project_dir%/src/Dto'] + use_symfony_listeners: true defaults: pagination_client_items_per_page: true denormalization_context: diff --git a/src/Controller/.gitignore b/src/Controller/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/src/Controller/ResetPasswordAction.php b/src/Controller/ResetPasswordAction.php new file mode 100644 index 0000000..d172373 --- /dev/null +++ b/src/Controller/ResetPasswordAction.php @@ -0,0 +1,31 @@ +userRepository->findOneByUuid($uuid); + $this->resetPasswordHandler->handle($user, $input->currentPassword, $input->newPassword); + + return new UserInput($user); + } +} \ No newline at end of file diff --git a/src/Dto/Input/UserInput.php b/src/Dto/Input/UserInput.php index 97b1230..8b37575 100644 --- a/src/Dto/Input/UserInput.php +++ b/src/Dto/Input/UserInput.php @@ -13,32 +13,47 @@ use Symfony\Component\Validator\Constraints as Assert; final class UserInput { #[Assert\NotBlank] - #[Groups(['user:write'])] + #[Groups('user:write')] public ?string $username = null; /** * @var OrganizationalUnit[] */ - #[Groups(['user:write'])] + #[Groups('user:write')] #[ApiProperty(readableLink: false, writableLink: false)] public array $allowedOrganizationalUnits = []; #[Assert\NotBlank(groups: ['user:post'])] #[Assert\Length(min: 8, groups: ['user:write', 'user:post'])] - #[Groups(['user:write'])] + #[Groups('user:write')] public ?string $password = null; #[Assert\NotNull] - #[Groups(['user:write'])] + #[Groups('user:write')] public ?bool $enabled = true; /** * @var UserGroup[] */ - #[Groups(['user:write'])] + #[Groups('user:write')] #[ApiProperty(readableLink: false, writableLink: false)] public array $userGroups = []; + #[Assert\NotBlank(groups: ['user:reset-password'])] + #[Groups('user:reset-password')] + public ?string $currentPassword = null; + + #[Assert\NotBlank(groups: ['user:reset-password'])] + #[Assert\Length(min: 8, groups: ['user:reset-password'])] + #[Groups('user:reset-password')] + public ?string $newPassword = null; + + #[Assert\NotBlank(groups: ['user:reset-password'])] + #[Assert\Length(min: 8, groups: ['user:reset-password'])] + #[Assert\Expression(expression: 'this.newPassword === this.repeatNewPassword', message: 'This value should be the same as the new password', groups: ['user:reset-password'])] + #[Groups('user:reset-password')] + public ?string $repeatNewPassword = null; + public function __construct(?User $user = null) { if (!$user) { @@ -74,6 +89,18 @@ final class UserInput } $user->setAllowedOrganizationalUnits( $allowedOrganizationalUnitToAdd ?? [] ); + if ($this->currentPassword !== null) { + $user->setCurrentPassword($this->currentPassword); + } + + if ($this->newPassword !== null) { + $user->setNewPassword($this->newPassword); + } + + if ($this->repeatNewPassword !== null) { + $user->setRepeatNewPassword($this->repeatNewPassword); + } + return $user; } } \ No newline at end of file diff --git a/src/Entity/User.php b/src/Entity/User.php index e7f4c6d..8e6e399 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -47,6 +47,11 @@ class User extends AbstractEntity implements UserInterface, PasswordAuthenticate #[ORM\ManyToMany(targetEntity: OrganizationalUnit::class, inversedBy: 'users')] private Collection $allowedOrganizationalUnits; + private ?string $currentPassword = null; + private ?string $newPassword = null; + private ?string $repeatNewPassword = null; + + public function __construct() { parent::__construct(); @@ -204,4 +209,40 @@ class User extends AbstractEntity implements UserInterface, PasswordAuthenticate return $this; } + + public function getCurrentPassword(): ?string + { + return $this->currentPassword; + } + + public function setCurrentPassword(?string $currentPassword): static + { + $this->currentPassword = $currentPassword; + + return $this; + } + + public function getNewPassword(): ?string + { + return $this->newPassword; + } + + public function setNewPassword(?string $newPassword): static + { + $this->newPassword = $newPassword; + + return $this; + } + + public function getRepeatNewPassword(): ?string + { + return $this->repeatNewPassword; + } + + public function setRepeatNewPassword(?string $repeatNewPassword): static + { + $this->repeatNewPassword = $repeatNewPassword; + + return $this; + } } diff --git a/src/Handler/ResetPasswordHandler.php b/src/Handler/ResetPasswordHandler.php new file mode 100644 index 0000000..9069b53 --- /dev/null +++ b/src/Handler/ResetPasswordHandler.php @@ -0,0 +1,30 @@ +userPasswordHasher->isPasswordValid($user, $currentPassword); + if ($currentHashedPassword === false) { + throw new \InvalidArgumentException('The current password is invalid.'); + } + + $user->setPassword($this->userPasswordHasher->hashPassword($user, $newPassword)); + + return $user; + } +} \ No newline at end of file diff --git a/src/State/Provider/UserProvider.php b/src/State/Provider/UserProvider.php index f3902eb..a850ef7 100644 --- a/src/State/Provider/UserProvider.php +++ b/src/State/Provider/UserProvider.php @@ -6,6 +6,7 @@ use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Put; use ApiPlatform\State\Pagination\TraversablePaginator; use ApiPlatform\State\ProviderInterface; From 2e12af3c2f81f50d0b8f02f38e1ea7a955561f17 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 13 Jun 2024 15:31:30 +0200 Subject: [PATCH 2/3] refs #450. Updated security User endpoint --- config/api_platform/User.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/config/api_platform/User.yaml b/config/api_platform/User.yaml index 6d39866..5e005fb 100644 --- a/config/api_platform/User.yaml +++ b/config/api_platform/User.yaml @@ -1,6 +1,5 @@ resources: App\Entity\User: - security: 'is_granted("ROLE_SUPER_ADMIN")' input: App\Dto\Input\UserInput output: App\Dto\Output\UserOutput processor: App\State\Processor\UserProcessor @@ -10,6 +9,7 @@ resources: groups: ['user:write'] operations: ApiPlatform\Metadata\GetCollection: + security: 'is_granted("ROLE_SUPER_ADMIN")' provider: App\State\Provider\UserProvider filters: - 'api_platform.filter.user.order' @@ -22,9 +22,11 @@ resources: ApiPlatform\Metadata\Patch: provider: App\State\Provider\UserProvider ApiPlatform\Metadata\Post: + security: 'is_granted("ROLE_SUPER_ADMIN")' validationContext: groups: [ 'default', 'user:post' ] - ApiPlatform\Metadata\Delete: ~ + ApiPlatform\Metadata\Delete: + security: 'is_granted("ROLE_SUPER_ADMIN")' reset_password: provider: App\State\Provider\UserProvider class: ApiPlatform\Metadata\Put From 8e03d347209161438168ec188396e022021c3647 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 13 Jun 2024 15:52:46 +0200 Subject: [PATCH 3/3] refs #450. Added new JWT Decoded data --- src/Lexik/JWTCreatedListener.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/Lexik/JWTCreatedListener.php diff --git a/src/Lexik/JWTCreatedListener.php b/src/Lexik/JWTCreatedListener.php new file mode 100644 index 0000000..34ca078 --- /dev/null +++ b/src/Lexik/JWTCreatedListener.php @@ -0,0 +1,29 @@ +getUser(); + if (!$user instanceof User) { + return; + } + + $payload = $event->getData(); + + $payload['id'] = $user->getId(); + $payload['username'] = $user->getUsername(); + $payload['uuid'] = $user->getUuid(); + $payload['roles'] = $user->getRoles(); + + $event->setData($payload); + } +} \ No newline at end of file