diff --git a/LICENSE.md b/LICENSE.md index d4d8a00..8e18fa6 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright © 2023 Ambroise Maupate +Copyright © 2024 Ambroise Maupate Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/composer.json b/composer.json index 2bdfc3e..2c0a650 100644 --- a/composer.json +++ b/composer.json @@ -17,20 +17,26 @@ } ], "type": "symfony-bundle", + "minimum-stability": "dev", "prefer-stable": true, "require": { "php": ">=8.1", - "api-platform/core": "~2.7.0", - "symfony/framework-bundle": "5.4.*", - "symfony/rate-limiter": "5.4.*", - "doctrine/orm": "~2.17.0" + "api-platform/core": "~3.2.14", + "roadiz/core-bundle": "2.3.*", + "symfony/framework-bundle": "6.4.*", + "symfony/rate-limiter": "6.4.*", + "symfony/lock": "6.4.*", + "doctrine/orm": "~2.19.0" }, "require-dev": { - "roadiz/core-bundle": "2.2.*", "php-coveralls/php-coveralls": "^2.4", "phpstan/phpstan": "^1.5.3", "squizlabs/php_codesniffer": "^3.5", - "phpstan/phpstan-doctrine": "^1.3" + "phpstan/phpstan-doctrine": "^1.3", + "roadiz/entity-generator": "2.3.*", + "roadiz/doc-generator": "2.3.*", + "roadiz/random": "2.3.*", + "roadiz/jwt": "2.3.*" }, "config": { "optimize-autoloader": true, @@ -52,8 +58,8 @@ }, "extra": { "branch-alias": { - "dev-main": "2.2.x-dev", - "dev-develop": "2.3.x-dev" + "dev-main": "2.3.x-dev", + "dev-develop": "2.4.x-dev" } } } diff --git a/config/api_resources/user.yaml b/config/api_resources/user.yaml index 288d582..b848be7 100644 --- a/config/api_resources/user.yaml +++ b/config/api_resources/user.yaml @@ -89,14 +89,14 @@ RZ\Roadiz\CoreBundle\Entity\User: method: 'GET' security: "is_granted('ROLE_ACCESS_USERS') or object == user" normalizationContext: - groups: ['user', 'user_security'] + groups: ['user', 'user_personal', 'user_security'] enable_max_depth: true ApiPlatform\Metadata\GetCollection: method: 'GET' security: "is_granted('ROLE_ACCESS_USERS')" normalizationContext: - groups: [ 'user' ] + groups: [ 'user', 'user_personal' ] enable_max_depth: true # Current user information operation MUST be declared AFTER ApiPlatform\Metadata\Get @@ -106,9 +106,11 @@ RZ\Roadiz\CoreBundle\Entity\User: class: ApiPlatform\Metadata\Get # Path must be different from item operation to avoid conflict uriTemplate: '/me' - itemUriTemplate: /users/{id} provider: RZ\Roadiz\UserBundle\State\UserTokenProvider output: RZ\Roadiz\UserBundle\Api\Dto\UserOutput + normalizationContext: + groups: ['user', 'user_personal', 'user_security'] + enable_max_depth: true openapiContext: summary: Get current user (JWT) information description: | diff --git a/phpstan.neon b/phpstan.neon index f312ced..4bc7774 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ parameters: - level: 5 + level: 7 paths: - src excludePaths: @@ -7,6 +7,8 @@ parameters: - */bower_components/* - */static/* ignoreErrors: + - identifier: missingType.iterableValue + - identifier: missingType.generics - '#Call to an undefined method RZ\\Roadiz\\CoreBundle\\Repository#' - '#Call to an undefined method RZ\\Roadiz\\UserBundle\\Repository#' - '#Call to an undefined method Doctrine\\Persistence\\ObjectRepository#' @@ -30,8 +32,6 @@ parameters: - '#does not accept Doctrine\\Common\\Collections\\ReadableCollection]+>#' reportUnmatchedIgnoredErrors: false - checkGenericClassInNonGenericObjectType: false - checkMissingIterableValueType: false doctrine: repositoryClass: RZ\Roadiz\CoreBundle\Repository\EntityRepository includes: diff --git a/src/Api/Dto/UserOutput.php b/src/Api/Dto/UserOutput.php index dd16e8e..7ba5b93 100644 --- a/src/Api/Dto/UserOutput.php +++ b/src/Api/Dto/UserOutput.php @@ -4,21 +4,38 @@ namespace RZ\Roadiz\UserBundle\Api\Dto; -use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use Symfony\Component\Serializer\Attribute\Groups; +#[ApiResource( + operations: [], +)] final class UserOutput { + #[Groups(['user'])] public string $identifier = ''; + #[Groups(['user'])] public array $roles = []; + #[Groups(['user'])] public ?string $firstName = null; + #[Groups(['user'])] public ?string $publicName = null; + #[Groups(['user'])] public ?string $lastName = null; + #[Groups(['user'])] public ?string $phone = null; + #[Groups(['user'])] public ?string $company = null; + #[Groups(['user'])] public ?string $locale = null; + #[Groups(['user'])] public ?string $pictureUrl = null; + #[Groups(['user'])] public ?array $metadata = null; + #[Groups(['user'])] public ?string $job = null; + #[Groups(['user'])] public ?\DateTime $birthday = null; + #[Groups(['user'])] public bool $emailValidated = false; } diff --git a/src/Console/PurgeUserValidationTokenCommand.php b/src/Console/PurgeUserValidationTokenCommand.php index 2996d9c..9dd0140 100644 --- a/src/Console/PurgeUserValidationTokenCommand.php +++ b/src/Console/PurgeUserValidationTokenCommand.php @@ -13,12 +13,9 @@ final class PurgeUserValidationTokenCommand extends Command { - private ManagerRegistry $managerRegistry; - - public function __construct(ManagerRegistry $managerRegistry, string $name = null) + public function __construct(private readonly ManagerRegistry $managerRegistry, string $name = null) { parent::__construct($name); - $this->managerRegistry = $managerRegistry; } protected function configure(): void diff --git a/src/Entity/UserValidationToken.php b/src/Entity/UserValidationToken.php index 5f06756..87bf734 100644 --- a/src/Entity/UserValidationToken.php +++ b/src/Entity/UserValidationToken.php @@ -5,7 +5,6 @@ namespace RZ\Roadiz\UserBundle\Entity; use Doctrine\ORM\Mapping as ORM; -use RZ\Roadiz\CoreBundle\Entity\User; use RZ\Roadiz\UserBundle\Repository\UserValidationTokenRepository; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Security\Core\User\UserInterface; diff --git a/src/EventSubscriber/UserSignedUpSubscriber.php b/src/EventSubscriber/UserSignedUpSubscriber.php index 06813a0..4e72bc0 100644 --- a/src/EventSubscriber/UserSignedUpSubscriber.php +++ b/src/EventSubscriber/UserSignedUpSubscriber.php @@ -10,12 +10,9 @@ final class UserSignedUpSubscriber implements EventSubscriberInterface { - private UserValidationTokenManagerInterface $userValidationTokenManager; - public function __construct( - UserValidationTokenManagerInterface $userValidationTokenManager + private readonly UserValidationTokenManagerInterface $userValidationTokenManager ) { - $this->userValidationTokenManager = $userValidationTokenManager; } /** diff --git a/src/Manager/UserMetadataManager.php b/src/Manager/UserMetadataManager.php index f2628c6..fe6609d 100644 --- a/src/Manager/UserMetadataManager.php +++ b/src/Manager/UserMetadataManager.php @@ -10,14 +10,8 @@ class UserMetadataManager implements UserMetadataManagerInterface { - private ManagerRegistry $managerRegistry; - - /** - * @param ManagerRegistry $managerRegistry - */ - public function __construct(ManagerRegistry $managerRegistry) + public function __construct(private readonly ManagerRegistry $managerRegistry) { - $this->managerRegistry = $managerRegistry; } public function getMetadataForUser(User $user): ?UserMetadata diff --git a/src/Manager/UserValidationTokenManager.php b/src/Manager/UserValidationTokenManager.php index 02628e4..3a99311 100644 --- a/src/Manager/UserValidationTokenManager.php +++ b/src/Manager/UserValidationTokenManager.php @@ -20,39 +20,18 @@ final class UserValidationTokenManager implements UserValidationTokenManagerInterface { - private ManagerRegistry $managerRegistry; - private UrlGeneratorInterface $urlGenerator; - private TranslatorInterface $translator; - private LoggerInterface $logger; - private EmailManagerFactory $emailManagerFactory; - private Settings $settingsBag; - private RoleHierarchyInterface $roleHierarchy; - private string $emailValidatedRoleName; - private int $userValidationExpiresIn; - private string $userValidationUrl; - public function __construct( - ManagerRegistry $managerRegistry, - UrlGeneratorInterface $urlGenerator, - TranslatorInterface $translator, - LoggerInterface $logger, - EmailManagerFactory $emailManagerFactory, - Settings $settingsBag, - RoleHierarchyInterface $roleHierarchy, - string $emailValidatedRoleName, - int $userValidationExpiresIn, - string $userValidationUrl + private readonly ManagerRegistry $managerRegistry, + private readonly UrlGeneratorInterface $urlGenerator, + private readonly TranslatorInterface $translator, + private readonly LoggerInterface $logger, + private readonly EmailManagerFactory $emailManagerFactory, + private readonly Settings $settingsBag, + private readonly RoleHierarchyInterface $roleHierarchy, + private readonly string $emailValidatedRoleName, + private readonly int $userValidationExpiresIn, + private readonly string $userValidationUrl ) { - $this->managerRegistry = $managerRegistry; - $this->logger = $logger; - $this->userValidationExpiresIn = $userValidationExpiresIn; - $this->emailManagerFactory = $emailManagerFactory; - $this->userValidationUrl = $userValidationUrl; - $this->settingsBag = $settingsBag; - $this->urlGenerator = $urlGenerator; - $this->translator = $translator; - $this->roleHierarchy = $roleHierarchy; - $this->emailValidatedRoleName = $emailValidatedRoleName; } public function createForUser(UserInterface $user): UserValidationToken diff --git a/src/State/UserPasswordRequestProcessor.php b/src/State/UserPasswordRequestProcessor.php index 347a35e..52586d5 100644 --- a/src/State/UserPasswordRequestProcessor.php +++ b/src/State/UserPasswordRequestProcessor.php @@ -59,7 +59,7 @@ protected function getRecaptchaHeaderName(): string return $this->recaptchaHeaderName; } - public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): VoidOutput + public function process($data, Operation $operation, array $uriVariables = [], array $context = []): VoidOutput { if (!$data instanceof UserPasswordRequestInput) { throw new \RuntimeException(sprintf('Cannot process %s', get_class($data))); @@ -123,6 +123,7 @@ private function getUser(string $identifier): ?User private function sendPasswordResetLink(Request $request, User $user): void { + $emailManager = $this->emailManagerFactory->create(); $emailContact = $this->settingsBag->get('email_sender'); $siteName = $this->settingsBag->get('site_name'); @@ -146,7 +147,7 @@ private function sendPasswordResetLink(Request $request, User $user): void ] ); } - $emailManager = $this->emailManagerFactory->create(); + $emailManager->setAssignation( [ 'resetLink' => $resetLink, diff --git a/src/State/UserPasswordResetProcessor.php b/src/State/UserPasswordResetProcessor.php index beb3667..350c0d9 100644 --- a/src/State/UserPasswordResetProcessor.php +++ b/src/State/UserPasswordResetProcessor.php @@ -31,7 +31,7 @@ public function __construct( ) { } - public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): VoidOutput + public function process($data, Operation $operation, array $uriVariables = [], array $context = []): VoidOutput { if (!$data instanceof UserPasswordTokenInput) { throw new \RuntimeException(sprintf('Cannot process %s', get_class($data))); diff --git a/src/State/UserSignupProcessor.php b/src/State/UserSignupProcessor.php index 711add6..b13988a 100644 --- a/src/State/UserSignupProcessor.php +++ b/src/State/UserSignupProcessor.php @@ -19,7 +19,7 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; use Symfony\Component\RateLimiter\RateLimiterFactory; -use Symfony\Component\Security\Core\Security; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; final class UserSignupProcessor implements ProcessorInterface @@ -51,7 +51,7 @@ protected function getRecaptchaHeaderName(): string return $this->recaptchaHeaderName; } - public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): VoidOutput + public function process($data, Operation $operation, array $uriVariables = [], array $context = []): VoidOutput { if (!$data instanceof UserInput) { throw new BadRequestHttpException(sprintf('Cannot process %s', get_class($data))); diff --git a/src/State/UserTokenProvider.php b/src/State/UserTokenProvider.php index d4a157d..14e3ccd 100644 --- a/src/State/UserTokenProvider.php +++ b/src/State/UserTokenProvider.php @@ -12,15 +12,15 @@ use RZ\Roadiz\UserBundle\Manager\UserMetadataManagerInterface; use RZ\Roadiz\UserBundle\Manager\UserValidationTokenManagerInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\Security\Core\Security; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Security\Core\User\UserInterface; final class UserTokenProvider implements ProviderInterface { public function __construct( - private Security $security, - private UserValidationTokenManagerInterface $userValidationTokenManager, - private UserMetadataManagerInterface $userMetadataManager, + private readonly Security $security, + private readonly UserValidationTokenManagerInterface $userValidationTokenManager, + private readonly UserMetadataManagerInterface $userMetadataManager, ) { } diff --git a/src/State/UserValidationRequestProcessor.php b/src/State/UserValidationRequestProcessor.php index 46c066f..e8c1b09 100644 --- a/src/State/UserValidationRequestProcessor.php +++ b/src/State/UserValidationRequestProcessor.php @@ -14,7 +14,7 @@ use RZ\Roadiz\UserBundle\Manager\UserValidationTokenManagerInterface; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; -use Symfony\Component\Security\Core\Security; +use Symfony\Bundle\SecurityBundle\Security; final class UserValidationRequestProcessor implements ProcessorInterface { @@ -27,7 +27,7 @@ public function __construct( ) { } - public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): VoidOutput + public function process($data, Operation $operation, array $uriVariables = [], array $context = []): VoidOutput { if (!$data instanceof UserValidationRequestInput) { throw new \RuntimeException(sprintf('Cannot process %s', get_class($data))); diff --git a/src/State/UserValidationTokenProcessor.php b/src/State/UserValidationTokenProcessor.php index e32a7ab..72e2a85 100644 --- a/src/State/UserValidationTokenProcessor.php +++ b/src/State/UserValidationTokenProcessor.php @@ -15,7 +15,7 @@ use RZ\Roadiz\UserBundle\Event\UserEmailValidated; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; -use Symfony\Component\Security\Core\Security; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; final class UserValidationTokenProcessor implements ProcessorInterface @@ -29,7 +29,7 @@ public function __construct( ) { } - public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): VoidOutput + public function process($data, Operation $operation, array $uriVariables = [], array $context = []): VoidOutput { if (!$data instanceof UserValidationTokenInput) { throw new \RuntimeException(sprintf('Cannot process %s', get_class($data)));