Commit cfc42927 authored by Thibaut Vallee's avatar Thibaut Vallee
Browse files

Release: mise à jour version API 0.0.9

parent abf49957
Loading
Loading
Loading
Loading
Loading
+12 −0
Changes for README.md: 12 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -46,6 +46,18 @@
   ```
   Vous devez passer votre instance axios à l'option `axios` pour que le composant puisse communiquer avec l'API en utilisant votre système d'authentification.

4. Configuration du token JWT
Dans `config/packages/gesdinet_jwt_refresh_token.yaml`
```yaml
gesdinet_jwt_refresh_token:
    # Définit l'entité PHP qui sera utilisée pour stocker les jetons en base de données.
    refresh_token_class: Unicaen\Authentification\Entity\RefreshToken
   #...
```

## Configuration pour Ldap
Si vous souhaitez utiliser l'authentification par LDAP  : [LDAP.md](api/documentation/LDAP.md)


## Contribution

+1 −1
Changes for api/.version: 1 added line, 1 removed line.
Original line number Diff line number Diff line
0.0.8
0.0.9

api/config/services.yaml

deleted100644 → 0
+0 −7
Changes for api/config/services.yaml: 0 added lines, 7 removed lines.
Original line number Diff line number Diff line
services:
  _defaults:
    autowire: true
    autoconfigure: true

  Unicaen\Authentification\:
    resource: '../src/*'
 No newline at end of file
+12 −0
Changes for api/src/src/Entity/RefreshToken.php: 12 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Unicaen\Authentification\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gesdinet\JWTRefreshTokenBundle\Entity\RefreshToken as BaseRefreshToken;

#[ORM\Entity]
#[ORM\Table(name: 'refresh_tokens')]
class RefreshToken extends BaseRefreshToken
{
}
+143 −0
Changes for api/src/src/Security/LdapPasswordAuthenticator.php: 143 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Unicaen\Authentification\Security;

use Unicaen\Authentification\Service\LdapService;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Ldap\Ldap;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Unicaen\Utilisateur\Model\UserInterface;

/**
 * Authentificateur personnalisé pour gérer la connexion via l'annuaire LDAP.
 * Il intercepte les tentatives de connexion pour les utilisateurs marqués 'ldap' en base de données.
 */
class LdapPasswordAuthenticator extends AbstractAuthenticator
{
    private Ldap $ldap;
    private LdapService $ldapService;
    private UserProviderInterface $userProvider;
    private AuthenticationSuccessHandlerInterface $successHandler;
    private AuthenticationFailureHandlerInterface $failureHandler;

    /**
     * Injection des dépendances nécessaires :
     * - LdapService : Pour la recherche d'UID et la vérification du mot de passe (bind).
     * - UserProvider : Pour charger l'utilisateur local et vérifier son type.
     * - Handlers : Pour générer les réponses JWT (LexikJWT) en cas de succès ou d'échec.
     */
    public function __construct(

        //Ldap $ldap,
        LdapService $ldapService,
        UserProviderInterface $userProvider,
        AuthenticationSuccessHandlerInterface $successHandler,
        AuthenticationFailureHandlerInterface $failureHandler
    )
    {
        //$this->ldap = $ldap;
        $this->ldapService = $ldapService;
        $this->userProvider = $userProvider;
        $this->successHandler = $successHandler;
        $this->failureHandler = $failureHandler;
    }

    /**
     * Étape 1 : Déterminer si cet authentificateur doit gérer la requête.
     * On ne l'active que si :
     * 1. La route correspond à l'authentification (/auth).
     * 2. L'utilisateur existe en base locale et possède le marqueur 'ldap' dans son champ password.
     */
    public function supports(Request $request): ?bool
    {
        // Vérifie que nous sommes sur la bonne route en méthode POST
        if (!($request->isMethod('POST') && $request->attributes->get('_route') === 'auth')) {
            return false;
        }

        // Extraction du login depuis le corps de la requête JSON
        $data = json_decode($request->getContent(), true);
        $login = $data['username'] ?? null;

        if (!$login) {
            return false;
        }
        try {
            // On charge l'utilisateur local pour vérifier s'il doit être authentifié via LDAP
            $user = $this->userProvider->loadUserByIdentifier($login);
            // Si le mot de passe commence par 'ldap', cet authentificateur prend la main.
            // Sinon (utilisateur local), on retourne false pour laisser passer l'authentificateur standard (JsonLogin).

            return $user instanceof UserInterface && str_starts_with($user->getPassword() ?? '', 'ldap');
        } catch (\Exception $e) {
            // Si l'utilisateur n'est pas trouvé, on ne supporte pas la requête ici.
            return false;
        }
    }

    /**
     * Étape 2 : Procéder à la vérification des identifiants (Authentification réelle).
     * Cette méthode n'est appelée que si supports() a renvoyé true.
     */
    public function authenticate(Request $request): Passport
    {
        // Récupération des données JSON
        $data = json_decode($request->getContent(), true);
        $login = $data['username'] ?? '';
        $password = $data['password'] ?? '';

        if (empty($login) || empty($password)) {
            throw new CustomUserMessageAuthenticationException('Identifiants manquants.');
        }


        // Le login peut être un alias (ex: baloche191), on doit trouver l'UID réel (ex: p00147990)
            $realUid = $this->ldapService->findUidByLogin($login);

            if (!$realUid) {
                throw new CustomUserMessageAuthenticationException('Compte LDAP introuvable.');
            }

            try {
                // Tentative de "Bind" LDAP avec l'UID et le mot de passe saisi.
                // Si le bind réussit, le mot de passe est valide.
                $this->ldapService->attemptBind($realUid, $password);

                // Création du Passeport.
                // SelfValidatingPassport est utilisé car le mot de passe a déjà été validé par le LDAP.
                return new SelfValidatingPassport(new UserBadge($login));

            } catch (\Exception $e) {
                // En cas d'échec du bind (mauvais mot de passe ou erreur technique)
                throw new CustomUserMessageAuthenticationException('Mot de passe LDAP invalide.');
            }
    }

    /**
     * Étape 3 : Gestion du succès.
     * On délègue au handler de LexikJWT pour générer et renvoyer le jeton JSON.
     */
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        return $this->successHandler->onAuthenticationSuccess($request, $token);
    }

    /**
     * Étape 4 : Gestion de l'échec.
     * On délègue au handler de LexikJWT pour renvoyer une erreur 401 standard.
     */
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
    {
        return $this->failureHandler->onAuthenticationFailure($request, $exception);
    }
}
Loading