Merge branch 'main' into feature/organizational-unit-hierarchy
commit
26f4595b2a
|
@ -9,6 +9,7 @@ resources:
|
||||||
groups: ['user:write']
|
groups: ['user:write']
|
||||||
operations:
|
operations:
|
||||||
ApiPlatform\Metadata\GetCollection:
|
ApiPlatform\Metadata\GetCollection:
|
||||||
|
security: 'is_granted("ROLE_SUPER_ADMIN")'
|
||||||
provider: App\State\Provider\UserProvider
|
provider: App\State\Provider\UserProvider
|
||||||
filters:
|
filters:
|
||||||
- 'api_platform.filter.user.order'
|
- 'api_platform.filter.user.order'
|
||||||
|
@ -21,9 +22,20 @@ resources:
|
||||||
ApiPlatform\Metadata\Patch:
|
ApiPlatform\Metadata\Patch:
|
||||||
provider: App\State\Provider\UserProvider
|
provider: App\State\Provider\UserProvider
|
||||||
ApiPlatform\Metadata\Post:
|
ApiPlatform\Metadata\Post:
|
||||||
|
security: 'is_granted("ROLE_SUPER_ADMIN")'
|
||||||
validationContext:
|
validationContext:
|
||||||
groups: [ 'default', 'user:post' ]
|
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
|
||||||
|
method: PUT
|
||||||
|
input: App\Dto\Input\UserInput
|
||||||
|
uriTemplate: /users/{uuid}/reset-password
|
||||||
|
controller: App\Controller\ResetPasswordAction
|
||||||
|
validationContext:
|
||||||
|
groups: [ 'user:reset-password' ]
|
||||||
|
|
||||||
properties:
|
properties:
|
||||||
App\Entity\User:
|
App\Entity\User:
|
||||||
|
|
|
@ -9,6 +9,7 @@ api_platform:
|
||||||
jsonld: ['application/ld+json', 'application/json']
|
jsonld: ['application/ld+json', 'application/json']
|
||||||
mapping:
|
mapping:
|
||||||
paths: ['%kernel.project_dir%/config/api_platform', '%kernel.project_dir%/src/Dto']
|
paths: ['%kernel.project_dir%/config/api_platform', '%kernel.project_dir%/src/Dto']
|
||||||
|
use_symfony_listeners: true
|
||||||
defaults:
|
defaults:
|
||||||
pagination_client_items_per_page: true
|
pagination_client_items_per_page: true
|
||||||
denormalization_context:
|
denormalization_context:
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Dto\Input\UserInput;
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Handler\ResetPasswordHandler;
|
||||||
|
use App\Repository\UserRepository;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpKernel\Attribute\AsController;
|
||||||
|
|
||||||
|
#[AsController]
|
||||||
|
class ResetPasswordAction extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ResetPasswordHandler $resetPasswordHandler,
|
||||||
|
private readonly UserRepository $userRepository
|
||||||
|
)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(string $uuid, UserInput $input): UserInput
|
||||||
|
{
|
||||||
|
/** @var User $user */
|
||||||
|
$user = $this->userRepository->findOneByUuid($uuid);
|
||||||
|
$this->resetPasswordHandler->handle($user, $input->currentPassword, $input->newPassword);
|
||||||
|
|
||||||
|
return new UserInput($user);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,32 +13,47 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||||
final class UserInput
|
final class UserInput
|
||||||
{
|
{
|
||||||
#[Assert\NotBlank]
|
#[Assert\NotBlank]
|
||||||
#[Groups(['user:write'])]
|
#[Groups('user:write')]
|
||||||
public ?string $username = null;
|
public ?string $username = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var OrganizationalUnit[]
|
* @var OrganizationalUnit[]
|
||||||
*/
|
*/
|
||||||
#[Groups(['user:write'])]
|
#[Groups('user:write')]
|
||||||
#[ApiProperty(readableLink: false, writableLink: false)]
|
#[ApiProperty(readableLink: false, writableLink: false)]
|
||||||
public array $allowedOrganizationalUnits = [];
|
public array $allowedOrganizationalUnits = [];
|
||||||
|
|
||||||
#[Assert\NotBlank(groups: ['user:post'])]
|
#[Assert\NotBlank(groups: ['user:post'])]
|
||||||
#[Assert\Length(min: 8, groups: ['user:write', 'user:post'])]
|
#[Assert\Length(min: 8, groups: ['user:write', 'user:post'])]
|
||||||
#[Groups(['user:write'])]
|
#[Groups('user:write')]
|
||||||
public ?string $password = null;
|
public ?string $password = null;
|
||||||
|
|
||||||
#[Assert\NotNull]
|
#[Assert\NotNull]
|
||||||
#[Groups(['user:write'])]
|
#[Groups('user:write')]
|
||||||
public ?bool $enabled = true;
|
public ?bool $enabled = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var UserGroup[]
|
* @var UserGroup[]
|
||||||
*/
|
*/
|
||||||
#[Groups(['user:write'])]
|
#[Groups('user:write')]
|
||||||
#[ApiProperty(readableLink: false, writableLink: false)]
|
#[ApiProperty(readableLink: false, writableLink: false)]
|
||||||
public array $userGroups = [];
|
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)
|
public function __construct(?User $user = null)
|
||||||
{
|
{
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
|
@ -74,6 +89,18 @@ final class UserInput
|
||||||
}
|
}
|
||||||
$user->setAllowedOrganizationalUnits( $allowedOrganizationalUnitToAdd ?? [] );
|
$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;
|
return $user;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -47,6 +47,11 @@ class User extends AbstractEntity implements UserInterface, PasswordAuthenticate
|
||||||
#[ORM\ManyToMany(targetEntity: OrganizationalUnit::class, inversedBy: 'users')]
|
#[ORM\ManyToMany(targetEntity: OrganizationalUnit::class, inversedBy: 'users')]
|
||||||
private Collection $allowedOrganizationalUnits;
|
private Collection $allowedOrganizationalUnits;
|
||||||
|
|
||||||
|
private ?string $currentPassword = null;
|
||||||
|
private ?string $newPassword = null;
|
||||||
|
private ?string $repeatNewPassword = null;
|
||||||
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
@ -204,4 +209,40 @@ class User extends AbstractEntity implements UserInterface, PasswordAuthenticate
|
||||||
|
|
||||||
return $this;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Handler;
|
||||||
|
|
||||||
|
use App\Dto\Input\UserInput;
|
||||||
|
use App\Entity\User;
|
||||||
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
|
|
||||||
|
class ResetPasswordHandler
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
Security $security,
|
||||||
|
private readonly UserPasswordHasherInterface $userPasswordHasher
|
||||||
|
)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
public function handle(User $user, string $currentPassword, string $newPassword): User
|
||||||
|
{
|
||||||
|
$currentHashedPassword = $this->userPasswordHasher->isPasswordValid($user, $currentPassword);
|
||||||
|
if ($currentHashedPassword === false) {
|
||||||
|
throw new \InvalidArgumentException('The current password is invalid.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->setPassword($this->userPasswordHasher->hashPassword($user, $newPassword));
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Lexik;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use Lexik\Bundle\JWTAuthenticationBundle\Events;
|
||||||
|
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent;
|
||||||
|
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||||
|
|
||||||
|
#[AsEventListener(event: Events::JWT_CREATED, priority: 1)]
|
||||||
|
class JWTCreatedListener
|
||||||
|
{
|
||||||
|
public function __invoke(JWTCreatedEvent $event): void
|
||||||
|
{
|
||||||
|
$user = $event->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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ use ApiPlatform\Metadata\Get;
|
||||||
use ApiPlatform\Metadata\GetCollection;
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
use ApiPlatform\Metadata\Operation;
|
use ApiPlatform\Metadata\Operation;
|
||||||
use ApiPlatform\Metadata\Patch;
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
use ApiPlatform\Metadata\Put;
|
use ApiPlatform\Metadata\Put;
|
||||||
use ApiPlatform\State\Pagination\TraversablePaginator;
|
use ApiPlatform\State\Pagination\TraversablePaginator;
|
||||||
use ApiPlatform\State\ProviderInterface;
|
use ApiPlatform\State\ProviderInterface;
|
||||||
|
|
Loading…
Reference in New Issue