Pārlūkot izejas kodu

connection to helloasso ok

Olivier Massot 2 mēneši atpakaļ
vecāks
revīzija
3b2fd32b02

+ 1 - 1
config/packages/monolog.yaml

@@ -23,7 +23,7 @@ monolog:
             path: "%kernel.logs_dir%/%env(LOG_FILE_NAME)%.main.log"
             level: debug
             max_files: 3
-            channels: ['!security', '!doctrine', '!cron', '!event', '!deprecation', '!app']
+            channels: ['!security', '!doctrine', '!cron', '!event', '!deprecation']
 #        file_doctrine:
 #            type: rotating_file
 #            path: "%kernel.logs_dir%/%env(LOG_FILE_NAME)%.doctrine.log"

+ 11 - 10
doc/helloasso.md

@@ -30,7 +30,7 @@ Toutes les URLs mentionnées ici ont une version équivalente de test :
 
 Pour lier une Organization à son compte Helloasso : 
 
-1. Se connecter à HelloAsso : un bouton "se connecter avec HelloAsso" est présent sur une page du logiciel. Il permet de rediriger l'utilisateur vers la page de connexion HelloAsso.
+1. Se connecter à HelloAsso : un bouton "se connecter avec HelloAsso" est présent sur une page du logiciel. Il permet d'afficher une popup contenant le formulaire de connexion HelloAsso.
 2. Une fois identifié, Helloasso redirige le visiteur vers une page de callback dont on a fourni l'url au préalable. Ce retour s'accompagne d'un jeton d'autorisation HelloAsso que l'on récupère.
 3. Grâce à ce jeton d'autorisation, on récupère auprès de l'API HelloAsso un jeton d'accès (durée de vie: 30min) et un jeton de renouvellement (durée de vie: 30jours).
 4. Ce jeton d'accès est stocké en base, et sera ensuite attaché aux requêtes envoyées à l'API HelloAsso.
@@ -43,27 +43,28 @@ Par ailleurs, l'utilisateur peut :
 
 #### Se connecter avec HelloAsso
 
-La [page HelloAsso](https://local.app.opentalent.fr/helloasso) du front (V2) donne accès à un bouton "se connecter avec HelloAsso".
+La [page HelloAsso](https://local.app.opentalent.fr/helloasso) du front (V2) donne accès à un bouton "Connecter à HelloAsso".
 
-Ce bouton permet de rediriger l'utilisateur vers la page de connexion HelloAsso : `https://auth.helloasso.com/authorize?<params>`
+Ce bouton permet d'afficher une popup contenant le forumaire de connexion HelloAsso : `https://auth.helloasso.com/authorize?<params>`
 
-On ajoute à cette URL une query composée des éléments suivants :
+On ajoute à l'URL de la page HelloAsso une query composée des éléments suivants :
 
 * `client_id` : l'identifiant client Opentalent enregistré comme variable d'environnement.
 * `redirect_uri` : l'url vers laquelle l'utilisateur sera redirigé après s'être authentifié.
 * `code_challenge`: code challenge PKCE à générer en amont (voir : `OAuthPkceGenerator::generatePkce()`).
 * `code_challenge_method` : méthode du test, doit être égal à "S256".
-* `state` : *optionnel, utilité à revoir*.
+* `state` : *optionnel, pas utilisé*.
 
-Une fois redirigé vers cette URL, l'utilisateur a accès à un formulaire de connexion lui permettant 
-de se connecter à son compte Helloasso. 
+Le formulaire ainsi affiché permet à l'utilisateur de s'authentifier sur HelloAsso, puis en cas de réussite, de 
+confirmer vouloir lier son compte HelloAsso à son compte Opentalent. 
 
 
 #### Le callback après authentification
 
 Une fois l'authentification effectuée via le formulaire HelloAsso, l'utilisateur est redirigé vers l'URL de 
-callback fournie précédemment, URL à laquelle aura été ajoutée une query.
+callback fournie précédemment : https://app.opentalent.fr/helloasso/callback.
 
+Lorsqu'il redirige vers l'url de callback, HelloAsso y ajoute une query.
 Cette query de retour contient :
 
 * Un authorization_code en cas de succès
@@ -80,8 +81,8 @@ contenant le body suivant (`application/x-www-form-urlencoded`) :
 * `client_secret` : la clé secrète Opentalent enregistré comme variable d'environnement.
 * `grant_type`: doit valoir 'authorization_code'.
 * `code`: l'authorization_code fourni précédemment.
-* `code_verifier`: la chaine de caractère générée pour le challenge, avant encodage *à revoir*.
-* `redirect_uri` : *à revoir*.
+* `code_verifier`: la valeur initiale utilisée pour le challenge PCKE, stockée en base.
+* `redirect_uri` : l'url de callback passée précédemment au formulaire HelloAsso..
 
 On récupère ensuite une réponse JSON de la forme : 
 

+ 1 - 2
src/ApiResources/HelloAsso/AuthUrl.php

@@ -28,7 +28,7 @@ class AuthUrl
      * de l'IRI par api platform.
      */
     #[ApiProperty(identifier: true)]
-    private int $id = 1;
+    private int $id = 0;
 
     /**
      * URL d'authentification HelloAsso pour l'autorisation OAuth2.
@@ -43,7 +43,6 @@ class AuthUrl
     public function setId(int $id): self
     {
         $this->id = $id;
-
         return $this;
     }
 

+ 35 - 0
src/ApiResources/HelloAsso/HelloAssoProfile.php

@@ -24,6 +24,19 @@ use App\State\Provider\HelloAsso\HelloAssoProfileProvider;
 )]
 class HelloAssoProfile
 {
+    /**
+     * Id 'bidon' ajouté par défaut pour permettre la construction
+     * de l'IRI par api platform.
+     */
+    #[ApiProperty(identifier: true)]
+    private int $id = 0;
+
+    /**
+     * Is there a HelloAsso profile linked to this organization ?
+     * @var bool
+     */
+    private bool $existing = false;
+
     /**
      * Token HelloAsso pour l'autorisation OAuth2.
      */
@@ -34,6 +47,28 @@ class HelloAssoProfile
      */
     private string | null $organizationSlug = null;
 
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function setId(int $id): self
+    {
+        $this->id = $id;
+        return $this;
+    }
+
+    public function isExisting(): bool
+    {
+        return $this->existing;
+    }
+
+    public function setExisting(bool $existing): self
+    {
+        $this->existing = $existing;
+        return $this;
+    }
+
     public function getToken(): ?string
     {
         return $this->token;

+ 1 - 1
src/ApiResources/Organization/OrganizationMemberCreationRequest.php

@@ -14,7 +14,7 @@ use Symfony\Component\Validator\Constraints as Assert;
  */
 class OrganizationMemberCreationRequest
 {
-    #[Assert\Type(type: FileTypeEnum::class)]
+    #[Assert\Type(type: GenderEnum::class)]
     private GenderEnum $gender = GenderEnum::MISTER;
 
     #[Assert\Regex(pattern: '/^[a-z0-9\-]{3,}$/')]

+ 1 - 0
src/Enum/Cotisation/CategoryTypeOfPracticeEnum.php

@@ -17,5 +17,6 @@ enum CategoryTypeOfPracticeEnum: string
     case CATEGORY_AMBULATORY = 'CATEGORY_AMBULATORY';
     case CATEGORY_CHORUS = 'CATEGORY_CHORUS';
     case CATEGORY_BAND = 'CATEGORY_BAND';
+    case CATEGORY_TEACHING = 'CATEGORY_TEACHING';
     case CATEGORY_OTHER = 'CATEGORY_OTHER';
 }

+ 14 - 2
src/Service/HelloAsso/ConnectionService.php

@@ -12,6 +12,7 @@ use App\Service\Rest\ApiRequestService;
 use App\Service\Security\OAuthPkceGenerator;
 use App\Service\Utils\UrlBuilder;
 use Doctrine\ORM\EntityManagerInterface;
+use Psr\Log\LoggerInterface;
 use Symfony\Component\HttpKernel\Exception\HttpException;
 use Symfony\Contracts\HttpClient\HttpClientInterface;
 
@@ -32,6 +33,7 @@ class ConnectionService extends ApiRequestService
         private readonly string $helloAssoClientId,
         private readonly string $helloAssoClientSecret,
         private readonly EntityManagerInterface $entityManager,
+        private readonly LoggerInterface $logger
     ) {
         parent::__construct($client);
     }
@@ -146,12 +148,14 @@ class ConnectionService extends ApiRequestService
             throw new \RuntimeException('Organization not found');
         }
 
+        $profile = new HelloAssoProfile();
+
         $helloAssoEntity = $organization->getHelloAsso();
         if (!$helloAssoEntity) {
-            throw new \RuntimeException('HelloAsso entity not found');
+            return $profile;
         }
 
-        $profile = new HelloAssoProfile();
+        $profile->setExisting(true);
         $profile->setToken($helloAssoEntity->getToken());
         $profile->setOrganizationSlug($helloAssoEntity->getOrganizationSlug());
 
@@ -191,6 +195,10 @@ class ConnectionService extends ApiRequestService
             $body['redirect_uri'] = $this->getCallbackUrl();
         }
 
+        if ($challengeVerifier !== null) {
+            $body['code_verifier'] = $challengeVerifier;
+        }
+
         $response = $this->client->request('POST',
             UrlBuilder::concat($this->helloAssoApiBaseUrl, ['/oauth2/token']),
             [
@@ -201,6 +209,10 @@ class ConnectionService extends ApiRequestService
             ]
         );
 
+        if ($response->getStatusCode() !== 200) {
+            throw new HttpException(500, 'Failed to fetch access token: '.$response->getContent(false));
+        }
+
 //        $options = [
 //            'headers' => [
 //                'Content-Type' => 'application/x-www-form-urlencoded',

+ 5 - 3
src/State/Processor/HelloAsso/ConnectionRequestProcessor.php

@@ -8,7 +8,9 @@ use ApiPlatform\Metadata\Operation;
 use ApiPlatform\Metadata\Post;
 use ApiPlatform\State\ProcessorInterface;
 use App\ApiResources\HelloAsso\ConnectionRequest;
+use App\ApiResources\HelloAsso\HelloAssoProfile;
 use App\Entity\Access\Access;
+use App\Entity\Organization\HelloAsso;
 use App\Service\HelloAsso\ConnectionService;
 use http\Client\Response;
 use Symfony\Bundle\SecurityBundle\Security;
@@ -29,7 +31,7 @@ class ConnectionRequestProcessor implements ProcessorInterface
      *
      * @throws \Exception
      */
-    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): RedirectResponse
+    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): ConnectionRequest
     {
         /**
          * @var ConnectionRequest $connectionRequest
@@ -47,11 +49,11 @@ class ConnectionRequestProcessor implements ProcessorInterface
             throw new \RuntimeException('Forbidden');
         }
 
-        $helloAssoEntity = $this->connectionService->connect(
+        $this->connectionService->connect(
             $connectionRequest->getOrganizationId(),
             $connectionRequest->getAuthorizationCode()
         );
 
-        return $helloAssoEntity;
+        return $connectionRequest;
     }
 }

+ 1 - 1
src/State/Provider/HelloAsso/HelloAssoProfileProvider.php

@@ -31,7 +31,7 @@ final class HelloAssoProfileProvider implements ProviderInterface
      *
      * @throws \Exception
      */
-    public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?HelloAssoProfile
+    public function provide(Operation $operation, array $uriVariables = [], array $context = []): HelloAssoProfile
     {
         if ($operation instanceof GetCollection) {
             throw new \RuntimeException('not supported', Response::HTTP_METHOD_NOT_ALLOWED);