IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Symfony PHP Discussion :

Symfony Guard Authenticator


Sujet :

Symfony PHP

  1. #1
    Membre du Club
    Femme Profil pro
    Étudiant
    Inscrit en
    Mai 2017
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2017
    Messages : 87
    Points : 49
    Points
    49
    Par défaut Symfony Guard Authenticator
    Bonjour,
    J'aurai besoin d'aide pour mettre en place un guard authenticator. J'ai suivi la documentation de symfony mais j'ai un peu de mal. Mon but c'est d'utiliser un ChainProvider pour l'authentification sur mon site web, un utilisateur peut se connecter via LDAP ou via la BDD. J'ai mis en place la connexion LDAP, elle marche parfaitement, mais je n'arrive pas à lier la connexion BDD. Ce que je voudrais c'est que sur le même formulaire de login, Symfony vérifie LDAP puis BDD. Voici mon code pour l'instant
    security.yaml
    Code yaml : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    security:
        providers:
            chain_provider:
                chain:
                    providers: [intranet_ldap, from_database]
            from_database:
                entity:
                    class: App\Entity\User
                    property: username
            fos_userbundle:
                id: fos_user.user_provider.username
            intranet_ldap:
                id: App\Security\LdapUserProvider
     
        firewalls:
            dev:
                pattern: ^/(_(profiler|wdt)|css|images|js)/
                security: false
            admin:
                pattern:            /admin(.*)
                context:            user
                form_login:
                    provider:       fos_userbundle
                    login_path:     /admin/login
                    use_forward:    false
                    check_path:     /admin/login_check
                    failure_path:   null
                logout:
                    path:           /admin/logout
                    target:         /admin/login
                anonymous:          true
                #switch_user: true
            main:
                guard:
                    authenticators:
                        - App\Security\TokenAuthenticator
                provider:       chain_provider
                pattern:            ^/
                context:            user
                anonymous:          true
                form_login_ldap:
                    service:        Symfony\Component\Ldap\Ldap
                    login_path:     /login
                    check_path:     /login_check
                    dn_string:      'DC=xxx, DC=lan'
                    query_string:   '(&(sAMAccountName={username}))'
                    use_forward:    false
                    always_use_default_target_path: true
                    default_target_path: /profile
                    use_referer:    true
                logout:
                    path:  /logout
                    target: /
     
     
        access_control:
            - { path: ^/efconnect, role: ROLE_USER }
            - { path: ^/elfinder, role: ROLE_USER }
            - { path: ^/profile/, role: ROLE_USER }
            # URL of FOSUserBundle which need to be available to anonymous users
            - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
            #- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
            - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
            - { path: ^/logout, role: IS_AUTHENTICATED_ANONYMOUSLY }
     
            # Admin login page needs to be accessed without credential
            - { path: ^/admin/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
            - { path: ^/admin/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }
            - { path: ^/admin/login_check$, role: IS_AUTHENTICATED_ANONYMOUSLY }
            - { path: ^/admin/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
     
            - { path: ^/admin/, role: [ROLE_ADMIN, ROLE_SONATA_ADMIN] }
            - { path: ^/efconnect, role: ROLE_USER }
            - { path: ^/elfinder, role: ROLE_USER }
            - { path: ^/blog/post/create$, role: ROLE_USER }
            - { path: ^/blog/post/edit, role: ROLE_USER }
            - { path: ^/blog/post/delete, role: ROLE_USER }
            - { path: ^/organigrammes/candidats, role: [ROLE_ALGAM_INTRANET_ADMIN_MEMBRE_STAFF, ROLE_SUPER_ADMIN] }
            - { path: ^/.*, role: IS_AUTHENTICATED_ANONYMOUSLY }
        role_hierarchy:
            ROLE_ADMIN:       ROLE_USER
            ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_SONATA_ADMIN, ROLE_ALLOWED_TO_SWITCH]
     
     
        encoders:
            FOS\UserBundle\Model\UserInterface: sha512
     
    acl:
        connection: default
    TokenAuthenticator.php
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    <?php
     
     
    namespace App\Security;
     
     
    use Symfony\Component\HttpFoundation\JsonResponse;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
    use Symfony\Component\Security\Core\Exception\AuthenticationException;
    use Symfony\Component\Security\Core\User\UserInterface;
    use Symfony\Component\Security\Core\User\UserProviderInterface;
    use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
     
    class TokenAuthenticator extends AbstractGuardAuthenticator
    {
     
        public function start(Request $request, AuthenticationException $authException = null)
        {
            $data = array(
                'message' => 'Authentification requise'
            );
     
            return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
        }
     
        public function supports(Request $request)
        {
            return $request->headers->has('X-AUTH-TOKEN');
        }
     
        public function getCredentials(Request $request)
        {
            return array(
                'token' => $request->headers->get('X-AUTH-TOKEN'),
            );
        }
     
        public function getUser($credentials, UserProviderInterface $userProvider)
        {
            $apiKey = $credentials['token'];
     
            if(null === $apiKey){
                return;
            }
     
            return $userProvider->loadUserByUsername($credentials['username']);
        }
     
        public function checkCredentials($credentials, UserInterface $user)
        {
            // TODO: Implement checkCredentials() method.
        }
     
        public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
        {
            $data = array(
                'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
            );
     
            return new JsonResponse($data, Response::HTTP_FORBIDDEN);
        }
     
        public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
        {
            return null;
        }
     
        public function supportsRememberMe()
        {
            return false;
        }
    }
    J'ai aussi dans mon rep Securirty LdapUser.php et LdapUserProvider.php mais je ne pense pas que ce soit utile donc je ne les fournis pas pour le moment. Est-ce que je dois créé un UserProvider pour la BDD aussi?
    Quelqu'un pourrait m'aider pour mettre en place la connexion avec la BDD en parallèle avec le LDAP? Je suis vraiment bloquée et je n'arrive pas à adapter les exemples que je trouve sur le net. Merci d'avance!

  2. #2
    Membre confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juillet 2011
    Messages
    351
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2011
    Messages : 351
    Points : 582
    Points
    582
    Par défaut
    Salut,

    Citation Envoyé par Akame14 Voir le message
    Ce que je voudrais c'est que sur le même formulaire de login, Symfony vérifie LDAP puis BDD.
    Tu voudrais que ce soit toujours "LDAP + BDD" (pour être authentifié, l'utilisateur devrait se trouver dans le LDAP ET dans la BDD), ou alors que l'authentification "BDD" soit une fallback utilisée uniquement quand l'authentification LDAP a échoué ?

    Quelle version de Symfony utilises-tu ? Visiblement le support des Authenticator dans le composant LDAP est récent (Symfony 5.1 https://github.com/symfony/ldap/comm...41f1751b405e45) et il utilise le nouveau système d'authentification qui est encore "experimental" dans la 5.1 (cf. https://symfony.com/doc/current/secu...nticators.html).

    Actuellement quand tu authentifies un utilisateur via LDAP, ton App\Security\TokenAuthenticator est exécuté ? Ou bien c'est uniquement Symfony\Component\Ldap\Ldap qui fait son job et redirige vers /profile ?

  3. #3
    Membre du Club
    Femme Profil pro
    Étudiant
    Inscrit en
    Mai 2017
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2017
    Messages : 87
    Points : 49
    Points
    49
    Par défaut
    Bonjour, merci de votre réponse.
    Pardon, je n'ai aps été claire dans mes explications. Je voudrais que ce soit un fallback, on teste le LDAP et si l'utilisateur n'est pas présent dedans, on va alors tester en BDD.

    Ma version de Symfony est la 4.4.11. J'ai utilisé Authenticator car je ne voyais pas comment faire autrement et je n'ai pas fait attetion à la version de Symfony

    Quand je clique sur le bouton "Se connecter" le TokenAuthenticator semble bien être appelé. En tout cas on passe bien dans le constructeur, je ne sais pas trop comment vérifier autrement ^^.

  4. #4
    Membre confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juillet 2011
    Messages
    351
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2011
    Messages : 351
    Points : 582
    Points
    582
    Par défaut
    Salut,

    À mon avis il faudrait que tu aies deux Guards, le premier qui s'occupe de l'authentification LDAP et le second dédié à l'authentification BDD.
    Sur cette page de la doc tu trouveras comment configurer le composant pour qu'ils partagent le même point d'entrée : https://symfony.com/doc/current/secu...ed-entry-point

    En gros si la méthode supports de ton TokenAuthenticator retourne false, Symfony exécutera le second Guard paramétré, donc celui dédié à la BDD dans lequel tu auras implémenté ta logique. (si besoin le rôle de chaque méthode est détaillé ici : https://symfony.com/doc/4.4/security...icator-methods).

    Cela dit je pense que cette solution ne colle pas tout à faire à ton besoin, car il faudrait être capable de distinguer si une requête concerne une authentification via LDAP ou via BDD (dès la méthode supports). Or toi tu voudrais avoir un système de fallback qui exécute le second Guard lorsque le premier échoue (càd quand on atteint sa méthode onAuthenticationFailure) et ça... je vois pas trop comment faire pour l'instant^^

    Aussi je me pose des questions sur ton code, car dans ton TokenAuthenticator, tu récupères la valeur d'un header (X-AUTH-TOKEN) quand elle existe, mais finalement tu charges l'utilisateur uniquement à partir du username de la requête (sans jamais te servir de la valeur du X-AUTH-TOKEN) :
    • Est-ce normal que ton TokenAuthenticator fonctionne ainsi ?
    • Qui définit la valeur du X-AUTH-TOKEN ?
    • Est-ce que tu es certaine que l'authentification LDAP est fonctionnelle ?

  5. #5
    Membre du Club
    Femme Profil pro
    Étudiant
    Inscrit en
    Mai 2017
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2017
    Messages : 87
    Points : 49
    Points
    49
    Par défaut
    Merci pour tes réponses. Pour tout te dire, j'avance un peu à l'aveugle donc je tente des choses sans vraiment savoir à quoi elles correspondent. On m'a conseillé d'utiliser le GuardAuthenticator et j'ai cherché des exemples sur Internet et je les ai copiés collés en essayant d'adapter un peu mais je ne suis absolument pas sûre de ce que j'ai fait, c'est pour cela que je viens demander de l'aide.
    Pour ce qui est du LDAP, oui je suis sûre que l'authentification est fonctionnelle

  6. #6
    Membre confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juillet 2011
    Messages
    351
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2011
    Messages : 351
    Points : 582
    Points
    582
    Par défaut
    On passe tous par là quand on découvre, t'inquiète pas^^

    Je vais essayer de t'expliquer dans les grandes lignes comment fonctionne le composant Security dans Symfony (sachant que je peux me tromper, avoir mal compris et/ou trop simplifier un point, donc toute correction est la bienvenue). Grosso modo Symfony a besoin de deux choses :

    1. Un "user provider", très souvent ça sera ton entité User. Tu peux lire cette page de la doc qui détaille à quoi sert un "user provider", liste ceux existants de base dans le composant Securty, et explique comment configurer ce dernier (a minima pour spécifier quel "user provider" on veut utiliser).

    2. Un "authentication provider", on peut se le représenter comme "du code qui va s'exécuter automatiquement AVANT le code du contrôleur qu'on va associer à l'authentification dans la config. De base il y en a plusieurs qui correspondent à des cas d'utilisation standards (formulaire de login, ldap, etc.), facile à mettre en place mais pas toujours évident à personnaliser ensuite (cf. https://symfony.com/doc/4.4/security...providers.html).


    Si tu parcoures la doc, tu verras qu'à de nombreux endroits il est recommandé de ne plus utiliser ces "authentication providers" de base mais à la place de créer et d'utiliser son propre "Guard authenticator". Un Guard est une classe qui va te permettre de contrôler, étape par étape, ce que faisait un peu "magiquement" les "authentication providers" de base. Pour saisir la différence entre les deux systèmes (et si ça t'intéresse, que tu as le temps, etc.), tu peux comparer cette page qui détaille comment mettre en place un Guard authenticator pour gérer l'authentification via un formulaire "username/password" standard, avec cette page qui explique comment "faire la même chose" mais en utilisant le provider "form_login" (ce qui n'est plus recommandé). Dernier point, un Guard doit contenir impérativement certaines méthodes qui sont appelées chacune à leur tour (selon le résultat de la méthode précédente), cette page détaille le rôle de chaque méthode (c'est important de comprendre ça car quand tu crées un Guard, c'est à toi t'implémenter ta logique d'authentification dans ces méthodes).


    Bref, assez de blabla conceptuel, mais ça va permettre de faire le lien avec ta situation : actuellement tu utilises un des "built-in authentication providers" (https://symfony.com/doc/4.4/security/ldap.html), le problème c'est qu'il est plutôt difficile de personnaliser son comportement (c'est le reproche principal de ces providers de base dès qu'on sort des cas d'utilisation standards). Tu as aussi un custom Guard appelé "TokenAuthenticator" mais qui à mon avis est mal nommé (car tu ne veux pas t'authentifier via un token), je pense que la doc (qui n'est pas du tout limpide sur ces parties) t'as induite en erreur.

    À ta place pour faire les choses bien (càd utiliser uniquement un Guard custom dans lequel on aura toute la logique "LDAP" et "BDD") :

    1. Je repartirai de zéro (désolé), càd faire le ménage dans la config de Security, supprimer le TokenAuthenticator, etc... histoire de pas être pollué par les différentes tentatives précédentes
    2. Je suivrai point par point cette page : https://symfony.com/doc/4.4/security...gin_setup.html (hésite pas à regarder le code des fichiers générés par les commandes "make")
    3. Normalement arrivée ici, tu auras une authentification "BDD" (formulaire, login/password, qui récupère le User dans la BDD) fonctionnelle et surtout réalisée par un Guard custom (donc plus facilement modifiable)
    4. Arrivée là, il te restera à modifier à nouveau les méthodes de ton Guard custom pour y implémenter la partie "authentification LDAP" (qui peut vite être complexe)


    À propos du dernier point, j'ai trouvé ça (attention c'est du Symfony 3) qui exprime un besoin similaire au tien : https://stackoverflow.com/questions/...eckcredentials
    Vu que j'ai toujours galéré le peu de fois où j'ai utilisé LDAP, je te promets rien, mais visiblement pour implémenter dans ton Guard la logique du "ldap provider", ça pourra se faire assez simplement (un peu de config puis en injectant le service du composant, à voir à l'utilisation si c'est suffisant ou pas).

    Bonne lecture, si tu te lances dans le grand chantier, bon courage et surtout persévère mais n'hésite pas à poser des questions si besoin.

  7. #7
    Membre du Club
    Femme Profil pro
    Étudiant
    Inscrit en
    Mai 2017
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2017
    Messages : 87
    Points : 49
    Points
    49
    Par défaut
    Merci énormément pour ton aide et tes conseils! J'ai donc supprimé mon TokenAuthenticator, effectivement je me disais bien qu'il y avait un truc qui clochait avec l'histoire du token!
    Pour le point 2: Je vais suivre cette documentation, merci encore pour les explications et les liens. Je te tiens au courant évidemment, mais je pense vite bloquer et avoir des questions

    Edit:
    J'ai donc fais le make, ce qui m'a créé un SecurityController et un LoginFormAuthenticator. Pour le SecurityController j'ai repris ce qui était utilisé dans le SecurityController de FOSUserBundle.
    SecurityController
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    <?php
     
    namespace App\Controller;
     
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\HttpFoundation\Session\Session;
    use Symfony\Component\Routing\Annotation\Route;
    use Symfony\Component\Security\Core\Exception\AuthenticationException;
    use Symfony\Component\Security\Core\Security;
    use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
    use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
     
    class SecurityController extends AbstractController
    {
        private $tokenManager;
     
        public function __construct(CsrfTokenManagerInterface $tokenManager = null)
        {
            $this->tokenManager = $tokenManager;
        }
     
        /**
         * @Route("/login", name="app_login")
         */
        public function login(AuthenticationUtils $authenticationUtils, Request $request): Response
        {
     
            // get the login error if there is one
            $error = $authenticationUtils->getLastAuthenticationError();
            /** @var $session Session */
            $session = $request->getSession();
     
            $authErrorKey = Security::AUTHENTICATION_ERROR;
            $lastUsernameKey = Security::LAST_USERNAME;
     
            // get the error if any (works with forward and redirect -- see below)
            if ($request->attributes->has($authErrorKey)) {
                $error = $request->attributes->get($authErrorKey);
            } elseif (null !== $session && $session->has($authErrorKey)) {
                $error = $session->get($authErrorKey);
                $session->remove($authErrorKey);
            } else {
                $error = null;
            }
     
            if (!$error instanceof AuthenticationException) {
                $error = null; // The value does not come from the security component.
            }
     
            // last username entered by the user
            $lastUsername = $authenticationUtils->getLastUsername();
     
            $csrfToken = $this->tokenManager
                ? $this->tokenManager->getToken('authenticate')->getValue()
                : null;
     
            return $this->render('security/login.html.twig', [
                'last_username' => $lastUsername,
                'error' => $error,
                'csrf_token' => $csrfToken,
            ]);
        }
     
        /**
         * @Route("/logout", name="app_logout")
         */
        public function logout()
        {
            throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
        }
    }
    Et loginFormAuthenticator:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    <?php
     
    namespace App\Security;
     
    use App\Entity\User;
    use Doctrine\ORM\EntityManagerInterface;
    use Symfony\Component\HttpFoundation\RedirectResponse;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
    use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
    use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
    use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
    use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
    use Symfony\Component\Security\Core\Security;
    use Symfony\Component\Security\Core\User\UserInterface;
    use Symfony\Component\Security\Core\User\UserProviderInterface;
    use Symfony\Component\Security\Csrf\CsrfToken;
    use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
    use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
    use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
    use Symfony\Component\Security\Http\Util\TargetPathTrait;
     
    class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
    {
        use TargetPathTrait;
     
        public const LOGIN_ROUTE = 'app_login';
     
        private $entityManager;
        private $urlGenerator;
        private $csrfTokenManager;
        private $passwordEncoder;
     
        public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
        {
            $this->entityManager = $entityManager;
            $this->urlGenerator = $urlGenerator;
            $this->csrfTokenManager = $csrfTokenManager;
            $this->passwordEncoder = $passwordEncoder;
        }
     
        public function supports(Request $request)
        {
            return self::LOGIN_ROUTE === $request->attributes->get('_route')
                && $request->isMethod('POST');
        }
     
        public function getCredentials(Request $request)
        {
            $credentials = [
                'username' => $request->request->get('username'),
                'password' => $request->request->get('password'),
                'csrf_token' => $request->request->get('_csrf_token'),
            ];
            $request->getSession()->set(
                Security::LAST_USERNAME,
                $credentials['username']
            );
     
            return $credentials;
        }
     
        public function getUser($credentials, UserProviderInterface $userProvider)
        {
            $token = new CsrfToken('authenticate', $credentials['csrf_token']);
            if (!$this->csrfTokenManager->isTokenValid($token)) {
                throw new InvalidCsrfTokenException();
            }
     
            $user = $this->entityManager->getRepository(User::class)->findOneBy(['username' => $credentials['username']]);
     
            if (!$user) {
                // fail authentication with a custom error
                throw new CustomUserMessageAuthenticationException('Username could not be found.');
            }
     
            return $user;
        }
     
        public function checkCredentials($credentials, UserInterface $user)
        {
            return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
        }
     
        /**
         * Used to upgrade (rehash) the user's password automatically over time.
         */
        public function getPassword($credentials): ?string
        {
            return $credentials['password'];
        }
     
        public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
        {
            if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
                return new RedirectResponse($targetPath);
            }
     
            // For example : return new RedirectResponse($this->urlGenerator->generate('some_route'));
            return new RedirectResponse($this->urlGenerator->generate('fos_user_profile_show'));
        }
     
        protected function getLoginUrl()
        {
            return $this->urlGenerator->generate(self::LOGIN_ROUTE);
        }
    }
    Ce qui est étrange c'est que je n'ai rien implémenté pour le LDAP et l'identification LDAP fonctionne toujours et la BDD ne fonctionne pas. J'ai l'impression qu'il ne passe pas dans LoginFormAuthenticator alors que je l'ai bien défini dans mon security.yaml
    Code yaml : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
     main:
                guard:
                    authenticators:
                        - App\Security\LoginFormAuthenticator
                provider:       chain_provider
                pattern:            ^/
                context:            user
                anonymous:          true
                form_login_ldap:
                    service:        Symfony\Component\Ldap\Ldap
                    login_path:     /login
                    check_path:     /login_check
                    dn_string:      'DC=xxx, DC=lan'
                    query_string:   '(&(sAMAccountName={username}))'
                    use_forward:    false
                    always_use_default_target_path: true
                    default_target_path: /profile
                    use_referer:    true
                logout:
                    path:  /logout
                    target: /

  8. #8
    Membre confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juillet 2011
    Messages
    351
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2011
    Messages : 351
    Points : 582
    Points
    582
    Par défaut
    Citation Envoyé par Akame14 Voir le message
    Ce qui est étrange c'est que je n'ai rien implémenté pour le LDAP et l'identification LDAP fonctionne toujours et la BDD ne fonctionne pas. J'ai l'impression qu'il ne passe pas dans LoginFormAuthenticator alors que je l'ai bien défini dans mon security.yaml
    Dans une application minimaliste (squelette website), si on suit la doc du composant Security (make:user puis make:auth) on obtient les mêmes fichiers que toi, avec un security.yaml épuré mais suffisant pour déléguer l'authentification à toutes les requêtes gérées par le firewall "main" (si je ne dis pas de connerie) :
    Code yaml : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
     
    security:
        encoders:
            App\Entity\User:
                algorithm: auto
     
        # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
        providers:
            # used to reload user from session & other features (e.g. switch_user)
            app_user_provider:
                entity:
                    class: App\Entity\User
                    property: username
        firewalls:
            dev:
                pattern: ^/(_(profiler|wdt)|css|images|js)/
                security: false
            main:
                anonymous: lazy
                provider: app_user_provider
                guard:
                    authenticators:
                        - App\Security\LoginFormAuthenticator
                logout:
                    path: app_logout
                    # where to redirect after logout
                    # target: app_any_route
     
                # activate different ways to authenticate
                # https://symfony.com/doc/current/security.html#firewalls-authentication
     
                # https://symfony.com/doc/current/security/impersonating_user.html
                # switch_user: true
     
        # Easy way to control access for large sections of your site
        # Note: Only the *first* access control that matches will be used
        access_control:
            # - { path: ^/admin, roles: ROLE_ADMIN }
            # - { path: ^/profile, roles: ROLE_USER }

    Or dans ta config tu as encore des choses concernant LDAP (à partir de la ligne 9) et le paramétrage du provider "form_login_ldap" est vraisemblablement prioritaire face au ton Authenticator, si tu supprimes les éléments de config qui concernent LDAP ça devrait redonner la main à ton LoginFormAuthenticator. Pour vérifier s'il est bien exécuté une fois la config nettoyée, dans sa méthode supports() tu peux ajouter dd($request); (dump&die) et tu verras si cette méthode est exécutée à chaque requête (du firewall "main").

    Et si c'est bien le cas, tu peux attaquer le point 4 de mon précédent message en t'inspirant du lien Stackoverflow (et si besoin en revenant poser tes questions ici).

  9. #9
    Membre du Club
    Femme Profil pro
    Étudiant
    Inscrit en
    Mai 2017
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2017
    Messages : 87
    Points : 49
    Points
    49
    Par défaut
    Merci! Effectivement j'avais bien pensé à ça mais je n'étais pas sûre. J'ai donc fait les modifications en enlevant tout ce qui touchait à Symfony, et la connexion BDD marche! Maintenant plus qu'à refaire marcher la connexion LDAP
    Par contre, il y a quelque chose que je ne comprends pas. Dans la méthode supports si tout se passe bien on est sensé passer dans getCredentials automatiquement, c'est noté dans la doc, sauf que dans mon cas je ne passe pas par le getCredentials, du moins pas celui de mon LoginFormAuthenticator. Donc j'ai regardé, la condition de retour de ma méthode supports:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
     public function supports(Request $request)
        {
            //Si on retourne false rien ne se passe
            //Si on retourne true, appel à getCredentials()
            return self::LOGIN_ROUTE === $request->attributes->get('_route')
                && $request->isMethod('POST');
        }
    Et il se trouve qu'il renvoie faux! J'ai testé et c'est le
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $request->isMethod('POST')
    qui renvoie faux. Quand je regarde dans l'onglet "Réseau", le login est en GET et le login_check est en POST. Je ne sais pas ce que je dois faire, enlever cette condition? Ce qui est bizarre c'est que supports renvoie faux et pourtant l'utilisateur se connecte malgré tout via la BDD

  10. #10
    Membre confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juillet 2011
    Messages
    351
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2011
    Messages : 351
    Points : 582
    Points
    582
    Par défaut
    Citation Envoyé par Akame14 Voir le message
    Et il se trouve qu'il renvoie faux! J'ai testé et c'est le
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $request->isMethod('POST')
    qui renvoie faux. je ne sais pas si je peut juste ne pas le mettre. Ce qui est bizarre c'est que supports renvoie faux et pourtant l'utilisateur se connecte malgré tout via la BDD
    C'est curieux ça... Qu'est-ce que te retourne dd($request->getMethod()); ? Dans la balise <form> de ton formulaire de login, est-ce que tu as bien précisé method="post" ?
    Le soucis si tu enlèves cette condition, c'est que "supports" va retourner true dès qu'une requête arrive sur la route "app_login" et donc les méthodes suivantes du Guard seront exécutées jusqu'à obtenir une erreur d'authentification y compris quand on accède seulement au formulaire sans n'avoir encore rien saisi ni soumis.

  11. #11
    Membre du Club
    Femme Profil pro
    Étudiant
    Inscrit en
    Mai 2017
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2017
    Messages : 87
    Points : 49
    Points
    49
    Par défaut
    "dd($request->getMethod());" renvoie
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    LoginFormAuthenticator.php on line 51:
    "GET"
    Voici mon code pour le template:
    Code twig : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    {#
     
    This file is part of the Sonata package.
     
    (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
     
    For the full copyright and license information, please view the LICENSE
    file that was distributed with this source code.
     
    #}
     
    {% extends "layout.html.twig" %}
     
    {% block htmlTitle %}Identification - Intranet Algam{% endblock%}
     
    {% block content %}
        <article class="user-login row">
            <div class="col-sm-6">
                {% block sonata_user_login %}
                    <h1 class="user-login-title">{{ 'title_user_authentication'|trans({}, 'SonataUserBundle') }}</h1>
     
                    <div class="alert alert-info" role="alert">
                        <p>Utilisez vos identifiants de connexion habituels.</p>
                    </div>
     
                    {% block sonata_user_login_error %}
                        {% if error %}
                            <div class="alert alert-danger alert-error">
                                {{ error.messageKey|trans(error.messageData, 'security') }}
                            </div>
                        {% endif %}
                    {% endblock %}
     
                    {% block sonata_user_login_form %}
                        <form action="{{ path("fos_user_security_check") }}" method="post" role="form" class="form-horizontal">
                            <input type="hidden" name="_csrf_token" value="{{ csrf_token }}"/>
     
                            <div class="form-group">
                                <label for="username" class="col-sm-4 control-label">{{ 'security.login.username'|trans({}, 'SonataUserBundle') }}</label>
                                <div class="col-sm-8"><input type="text" class="form-control" id="username" name="_username" value="{{ last_username }}" required="required"/></div>
                            </div>
     
     
                            <div class="form-group control-group">
                                <label for="password" class="col-sm-4 control-label">{{ 'security.login.password'|trans({}, 'SonataUserBundle') }}</label>
                                <div class="col-sm-8"><input type="password" class="form-control" id="password" name="_password" required="required"/></div>
                            </div>
     
                            <div class="form-group">
                                <div class="col-sm-offset-4 col-sm-8">
                                    <div class="checkbox control-group">
                                        <label for="remember_me">
                                            <input type="checkbox" id="remember_me" name="_remember_me" value="on"/>
                                            {{ 'security.login.remember_me'|trans({}, 'FOSUserBundle') }}
                                        </label>
                                    </div>
                                </div>
                            </div>
     
                            <div class="form-group">
                                <div class="col-sm-offset-4 col-sm-8">
                                    <input type="submit" id="_submit" name="_submit" class="btn btn-primary" value="{{ 'security.login.submit'|trans({}, 'FOSUserBundle') }}"/>
                                </div>
                            </div>
                        </form>
                    {% endblock %}
                {% endblock %}
            </div>
        </article>
    {% endblock content %}
    La méthode utilisée est bien POST.
    Très bien donc il faut que je trouve un moyen de résoudre le problème :/ Je ne vois pas où la méthode devient GET alors que c'est bien POST dans le form pour le login...

  12. #12
    Membre confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juillet 2011
    Messages
    351
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2011
    Messages : 351
    Points : 582
    Points
    582
    Par défaut
    La méthode est bien "post" mais le contenu du formulaire est envoyé sur l'URL de la route "fos_user_security_check" et pas sur "app_login" donc quand la méthode "supports" est exécutée, self::LOGIN_ROUTE === $request->attributes->get('_route') retournera false.

  13. #13
    Membre du Club
    Femme Profil pro
    Étudiant
    Inscrit en
    Mai 2017
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2017
    Messages : 87
    Points : 49
    Points
    49
    Par défaut
    D'accord je comprends! Il faut donc que je change le path de l'action. Mais je n'aurai donc plus de security_check, est-ce grave? Est-ce que je dois appeler le controleur de FOSUser à un autre endroit?
    Je n'aurai plus de login_check, est-ce important? J'avoue ne pas trop comprendre à quoi cela correspond.

  14. #14
    Membre confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juillet 2011
    Messages
    351
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2011
    Messages : 351
    Points : 582
    Points
    582
    Par défaut
    Soit tu ne mets rien dans l'action (ça postera ton formulaire sur la même URL que là où il se trouve), soit tu peux remplacer "fos_user_security_check" par "app_login" (ce qui aboutira au même résultat que de ne rien mettre).

    Citation Envoyé par Akame14 Voir le message
    Mais je n'aurai donc plus de security_check, est-ce grave? Est-ce que je dois appeler le contrôleur de FOSUser à un autre endroit?
    Je n'aurai plus de login_check, est-ce important? J'avoue ne pas trop comprendre à quoi cela correspond.
    J'imagine que FOSUser embarquait des services "clés en main" pour gérer l'authentification etc, un peu à la manière des "built-in authentication providers" du composant Security, et qu'avec un peu de config on pouvait mettre en place "magiquement" l'authentification via un login_form, etc.

    Or dans ton cas tu veux justement faire en sorte que l'authentification soit gérée par ton Guard custom dans lequel tu implémente ta logique spécifique d'authentification (LDAP puis fallback vers BDD). Donc à mon avis tu peux supprimer les éléments qui font référence à FOSUser (voire même le bundle entier, sauf si tu t'en sers ailleurs que juste pour l'authentification). D'ailleurs à ce propos ce bundle n'est plus vraiment recommandé maintenant que Symfony dispose des outils pour mettre en place des fonctionnalités similaires sans avoir la lourdeur de personnalisation du bundle.

    Voir par exemple : https://jolicode.com/blog/do-not-use-fosuserbundle
    Ou : https://blog.netinfluence.ch/2019/05...fosuserbundle/

  15. #15
    Membre du Club
    Femme Profil pro
    Étudiant
    Inscrit en
    Mai 2017
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2017
    Messages : 87
    Points : 49
    Points
    49
    Par défaut
    Du coup j'ai remplacé par app_login mon path, et tout se passe comme il faut. J'ai mon LoginFormAuthenticator que je rempli petit à petit mais il y a un truc que je ne comprends pas. Le code de getUser:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public function getUser($credentials, UserProviderInterface $userProvider)
        {
            $token = new CsrfToken('authenticate', $credentials['csrf_token']);
            if (!$this->csrfTokenManager->isTokenValid($token)) {
                throw new InvalidCsrfTokenException();
            }
            $this->ldapUserProvider->loadUserByUsername($credentials['username']);
            $user = $this->entityManager->getRepository(User::class)->findOneBy(['username' => $credentials['username']]);
     
            if (!$user) {
                // fail authentication with a custom error
                throw new CustomUserMessageAuthenticationException('Username could not be found.');
            }
     
            return $user;
        }
    J'aurai voulu tester si l'utilisateur viens du LDAP alors on loadUserByUserName ce que j'ai voulu faire avec cette ligne:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
     $this->ldapUserProvider->loadUserByUsername($credentials['username']);
    En ajoutant un if ou je ne sais pas trop quoi... Mais dans tous les cas quand le code passe ici, il sort de la classe LoginFormAuthenticator et va dans la classe LdapUserProvider:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    <?php
     
     
    namespace App\Security;
     
     
    use Doctrine\ORM\EntityManagerInterface;
    use App\Entity\User;
    use Symfony\Component\Ldap\Entry;
    use Symfony\Component\Ldap\Exception\ConnectionException;
    use Symfony\Component\Ldap\Exception\ExceptionInterface;
    use Symfony\Component\Ldap\LdapInterface;
    use Symfony\Component\Ldap\Ldap;
    use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
    use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
    use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
    use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
    use Symfony\Component\Security\Core\User\UserInterface;
    use Symfony\Component\Security\Core\User\UserProviderInterface;
     
     
    class LdapUserProvider implements UserProviderInterface, PasswordUpgraderInterface
    {
        private $ldap;
        private $baseDn;
        private $searchDn;
        private $searchPassword;
        private $defaultRoles;
        private $uidKey;
        private $defaultSearch;
        private $passwordAttribute;
        private $extraFields;
        //New
        private $em;
     
        public function __construct(Ldap $ldap, string $baseDn, EntityManagerInterface $em , string $searchDn = null, string $searchPassword = null, array $defaultRoles = [], string $uidKey = null, string $filter = null, string $passwordAttribute = null, array $extraFields = [])
        {
            if (null === $uidKey) {
                $uidKey = 'sAMAccountName';
            }
     
            if (null === $filter) {
                $filter = '({uid_key}={username})';
            }
     
            $this->ldap = $ldap;
            $this->baseDn = $baseDn;
            $this->searchDn = $searchDn;
            $this->searchPassword = $searchPassword;
            $this->defaultRoles = $defaultRoles;
            $this->uidKey = $uidKey;
            $this->defaultSearch = str_replace('{uid_key}', $uidKey, $filter);
            $this->passwordAttribute = $passwordAttribute;
            $this->extraFields = $extraFields;
            $this->em = $em;
        }
     
        /**
         * {@inheritdoc}
         */
        public function loadUserByUsername($username)
        {
            try {
                $this->ldap->bind($this->searchDn, $this->searchPassword);
                $username = $this->ldap->escape($username, '', LdapInterface::ESCAPE_FILTER);
                $query = str_replace('{username}', $username, $this->defaultSearch);
                $search = $this->ldap->query($this->baseDn, $query);
            } catch (ConnectionException $e) {
                throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username), 0, $e);
            }
     
            $entries = $search->execute();
            $count = \count($entries);
     
            if (!$count) {
                throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
            }
     
            if ($count > 1) {
                throw new UsernameNotFoundException('More than one user found.');
            }
     
            return $this->loadUser($username, $entries[0]);
        }
     
    }
    Ce que j'aurai voulu c'est que si il n'y a pas d'user retourné alors on essaye avec la BDD avec cette ligne:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $user = $this->entityManager->getRepository(User::class)->findOneBy(['username' => $credentials['username']]);
    Sauf que dans le code, j'ai séparé ces deux lignes par un dd() et je n'y accède pas, ce qui laisse penser qu'on sort totalement de LoginFormAuthenticator et qu'on y retourne pas. Voilà je sais pas si c'est très clair mais je ne sais pas trop comment faire. Comme tu l'as dit, c'est la partie la plus dure !

    Edit:
    Je viens de voir ton message. Oui le bundle n'est plus recommandé mais je n'ai pas la main sur ce genre de choix Donc je dois continuer avec même si on a quasiment tout overrided dans ce bundle donc je pense qu'il ne va plus nous être utile!

  16. #16
    Membre confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juillet 2011
    Messages
    351
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2011
    Messages : 351
    Points : 582
    Points
    582
    Par défaut
    Bon en fait je reviens un peu à la solution de départ, car la solution proposée entre temps n'est pas clean à mon avis : c'est pas sain de mélanger les logiques dans le même Guard, on va implémenter de la logique dans des méthodes dont ce n'est pas le rôle et donc sans forcément avoir accès aux données nécessaires.

    Je pense donc que c'est préférable d'avoir deux Guards (un pour LDAP, l'autre pour la BDD). Ensuite dans ta config, il faut déclarer le nouveau Guard (attention à l'ordre qui peut avoir son importance) et surtout renseigner l'entrypoint, c'est à dire le Guard qui sera toujours exécuté en premier (cf. https://symfony.com/doc/4.4/security...ed-entry-point).

    Problème si tu ne fais que ça, tu verras que seul le premier Guard est exécuté en cas d'échec de l'authentification, car l'exception levée te redirigera automatiquement vers la page de login et y affichera l'erreur. Pour que ton deuxième Guard soit exécuté, il faut implémenter la méthode onAuthenticationFailure et simplement qu'elle retourne null, ainsi la requête initiale ne sera pas interrompue pour gérer l'erreur et continuera avec le second Guard (cf. le code qui gère ça dans le composant : https://github.com/symfony/symfony/b...ndler.php#L111).

    À mon avis tu devrais donc créer un second Guard (duplique l'actuel en renommant seulement la classe et le fichier pour l'instant), modifier la config et implémenter la méthode comme indiqué ci-dessus pour vérifier que le "fallback" fonctionne. Si c'est le cas, il te restera "plus" qu'à implémenter dans ton deuxième Guard la logique d'authentification LDAP.

  17. #17
    Membre du Club
    Femme Profil pro
    Étudiant
    Inscrit en
    Mai 2017
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2017
    Messages : 87
    Points : 49
    Points
    49
    Par défaut
    Donc, j'ai dupliqué mon LoginFormAuthenticator en LoginLdapFormAuthenticator. J'ai mis return null à OnAuthenticationFailure pour le LDAP. J'ai mis des die() pour voir le chemin qui se fait. J'ai ce résultat:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    LoginLdapFormAuthenticator.php on line 50:
    "supports LDAP"
     
    LoginFormAuthenticator.php on line 50:
    "supports BDD"
     
    LoginLdapFormAuthenticator.php on line 76:
    "getUser LDAP"
     
    LoginFormAuthenticator.php on line 76:
    "getUser BDD"
    Ca va un peu dans tous les sens... C'est normal?

  18. #18
    Membre confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juillet 2011
    Messages
    351
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2011
    Messages : 351
    Points : 582
    Points
    582
    Par défaut
    Sans le code devant les yeux difficile de comprendre ce qu'il se passe^^

    Il faudrait voir tes Guards et ta config de security.yaml, mais logiquement si tu as dupliqué le code d'un Guard "form login" fonctionnel en l'appelant différemment, si tu saisis un "login/password" valide seul le premier Guard sera exécuté (pas de "fallback" vers le second Guard) puisque l'authentification a réussi.

    Si tu soumets un "login/password" invalide, le premier Guard sera exécuté, appellera sa méthode "onAuthenticationFailure" qui retourne null, donc le second Guard prendra le relai avec les mêmes données, l'authentification échouera pour les mêmes raisons que dans le premier Guard, et soit ça exécutera sa méthode "onAuthenticationFailure", soit celle "par défaut" qui dans les deux cas aboutiront probablement à rediriger vers le formulaire de login avec affichage de l'erreur.

  19. #19
    Membre du Club
    Femme Profil pro
    Étudiant
    Inscrit en
    Mai 2017
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2017
    Messages : 87
    Points : 49
    Points
    49
    Par défaut
    D'accord je vais essayer de creuser. Le fallback doit marcher car j'ai mis le LDAP en premier et j'arrive à me connecter avec un utilisateur BDD. Je vais essayer de faire le FormAuthenticator pour le LDAP même si je vois pas très bien dans quel sens partir.
    Déjà j'arrive bien à récupérer mon utilisateur Ldap dans le LoginLdapForm, reste plus qu'à réussir à l'authentifier!

    Edit: En fait le seul problème c'est le checkCredentials, je ne vois pas comment vérifier que l'utilisateur a entré le bon mdp.. Je reçois le mdp que l'utilisateur a entré et je reçois l'objet User qui correspond donc à l'utilisateur LDAP. Sauf que le mot de passe n'est pas présent dans
    Ma classe LdapUserProvider:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    <?php
     
     
    namespace App\Security;
     
     
    use Doctrine\ORM\EntityManagerInterface;
    use App\Entity\User;
    use Symfony\Component\Ldap\Entry;
    use Symfony\Component\Ldap\Exception\ConnectionException;
    use Symfony\Component\Ldap\Exception\ExceptionInterface;
    use Symfony\Component\Ldap\LdapInterface;
    use Symfony\Component\Ldap\Ldap;
    use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
    use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
    use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
    use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
    use Symfony\Component\Security\Core\User\UserInterface;
    use Symfony\Component\Security\Core\User\UserProviderInterface;
     
     
    class LdapUserProvider implements UserProviderInterface, PasswordUpgraderInterface
    {
        private $ldap;
        private $baseDn;
        private $searchDn;
        private $searchPassword;
        private $defaultRoles;
        private $uidKey;
        private $defaultSearch;
        private $passwordAttribute;
        private $extraFields;
        //New
        private $em;
     
        public function __construct(Ldap $ldap, string $baseDn, EntityManagerInterface $em , string $searchDn = null, string $searchPassword = null, array $defaultRoles = [], string $uidKey = null, string $filter = null, string $passwordAttribute = null, array $extraFields = [])
        {
            if (null === $uidKey) {
                $uidKey = 'sAMAccountName';
            }
     
            if (null === $filter) {
                $filter = '({uid_key}={username})';
            }
     
            $this->ldap = $ldap;
            $this->baseDn = $baseDn;
            $this->searchDn = $searchDn;
            $this->searchPassword = $searchPassword;
            $this->defaultRoles = $defaultRoles;
            $this->uidKey = $uidKey;
            $this->defaultSearch = str_replace('{uid_key}', $uidKey, $filter);
            $this->passwordAttribute = $passwordAttribute;
            $this->extraFields = $extraFields;
            $this->em = $em;
        }
     
        /**
         * {@inheritdoc}
         */
        public function loadUserByUsername($username)
        {
            try {
                $this->ldap->bind($this->searchDn, $this->searchPassword);
                $username = $this->ldap->escape($username, '', LdapInterface::ESCAPE_FILTER);
                $query = str_replace('{username}', $username, $this->defaultSearch);
                $search = $this->ldap->query($this->baseDn, $query);
            } catch (ConnectionException $e) {
                throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username), 0, $e);
            }
     
            $entries = $search->execute();
            $count = \count($entries);
     
            if (!$count) {
                throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
            }
     
            if ($count > 1) {
                throw new UsernameNotFoundException('More than one user found.');
            }
     
            return $this->loadUser($username, $entries[0]);
        }
     
        /**
         * {@inheritdoc}
         */
        public function refreshUser(UserInterface $user)
        {
            if (!$user instanceof LdapUser || !$user instanceof User) {
                throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', \get_class($user)));
            }
     
            //New
            $userRepository = $this->em->getRepository("AppBundle:User");
            $user = $userRepository->findOneBy(array("username" => $user->getUsername()));
     
            if($user === null){
                throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
            }
     
            return new LdapUser($user->getEntry(), $user->getUsername(), $user->getPassword(), $user->getRoles(), $user->getExtraFields());
        }
     
        /**
         * {@inheritdoc}
         */
        public function upgradePassword(UserInterface $user, string $newEncodedPassword): void
        {
            if (!$user instanceof LdapUser) {
                throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', \get_class($user)));
            }
     
            if (null === $this->passwordAttribute) {
                return;
            }
     
            try {
                $user->getEntry()->setAttribute($this->passwordAttribute, [$newEncodedPassword]);
                $this->ldap->getEntryManager()->update($user->getEntry());
                $user->setPassword($newEncodedPassword);
            } catch (ExceptionInterface $e) {
                // ignore failed password upgrades
            }
        }
     
        /**
         * {@inheritdoc}
         */
        public function supportsClass($class)
        {
            return LdapUser::class === $class;
        }
     
        /**
         * Loads a user from an LDAP entry.
         *
         * @param $username
         * @param Entry $entry
         * @return UserInterface
         */
        protected function loadUser($username, Entry $entry)
        {
            $userRepository = $this->em->getRepository("App:User");
            //On récupère les infos de l'utilisateur qui se connecte
            $user = $userRepository->findOneBy(array("username" => $username));
     
            //Si l'utilisateur est null, donc pas présent en BDD mais OK niveau LDAP
            if ($user === null) {
                //Créé un User pour l'ajouter à la BDD une fois qu'on s'est assuré que c'était bien un utilisateur LDAP
                //Cas première connexion de l'utilisateur
                $user = new User();
                $user->setFirstname($entry->getAttribute("givenName")[0]);
                $user->setLastname($entry->getAttribute("sn")[0]);
                $user->setEmail($entry->getAttribute("mail")[0]);
                $user->setUsername($entry->getAttribute("uid")[0]);
                $user->setRoles($this->defaultRoles);
     
                $this->em->persist($user);
                $this->em->flush();
            } else {
                $this->em->flush();
            }
     
            return $user;
        }
     
        /**
         * Fetches the password from an LDAP entry.
         *
         * @param null|Entry $entry
         */
        private function getPassword(Entry $entry)
        {
            if (null === $this->passwordAttribute) {
                return;
            }
     
            if (!$entry->hasAttribute($this->passwordAttribute)) {
                throw new InvalidArgumentException(sprintf('Missing attribute "%s" for user "%s".', $this->passwordAttribute, $entry->getDn()));
            }
     
            $values = $entry->getAttribute($this->passwordAttribute);
     
            if (1 !== count($values)) {
                throw new InvalidArgumentException(sprintf('Attribute "%s" has multiple values.', $this->passwordAttribute));
            }
     
            return $values[0];
        }
     
        private function getAttributeValue(Entry $entry, string $attribute)
        {
            var_dump("getAttributeValue ".$attribute);
            if (!$entry->hasAttribute($attribute)) {
                throw new InvalidArgumentException(sprintf('Missing attribute "%s" for user "%s".', $attribute, $entry->getDn()));
            }
     
            $values = $entry->getAttribute($attribute);
     
            if (1 !== \count($values)) {
                throw new InvalidArgumentException(sprintf('Attribute "%s" has multiple values.', $attribute));
            }
     
            return $values[0];
        }
    }
    Est-ce que je devrais utiliser la méthode getPassword pour avoir le mot de passe qui viens du LDAP et ensuite comparer à celui que l'utilisateur à entré? Je ne sais pas si tu as déjà utilisé LDAP avec Symfony ^^

  20. #20
    Membre confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juillet 2011
    Messages
    351
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2011
    Messages : 351
    Points : 582
    Points
    582
    Par défaut
    Je connais pas très bien LDAP, j'ai toujours trouvé ça très galère à mettre en place et heureusement à chaque fois j'ai eu à le faire, j'avais des gens qui s'y connaissaient mieux que moi^^
    (donc ce que je vais en dire est peut-être faux ou à côté de la plaque)

    Quand tu dis que dans checkCredentials tu as bien ton objet User, c'est un App/Entity/User car s'il n'existe pas déjà dans la BDD tu le crée à la volée c'est bien ça ? (sans mot de passe pour l'instant ? ) Et getPassword tu fais références à la méthode de quelle classe ?

    Visiblement cette méthode $search = $this->ldap->query($this->baseDn, $query); accepte un troisième argument, un tableau d'options, qui est notamment utilisé quand la Query est instanciée puis exécutée (ex: selon la valeur de $options['scope'], l'exécution de la Query exécutera des fonctions LDAP différentes), qui elles mêmes utiliseront des valeurs présentes dans $options si elles existent).

    À ta place j'irai jouer avec dd() directement dans les fichiers du composant Security pour vérifier quelles sont les options par défaut utilisées (je pense que ce sera celles héritées de cette classe mais mieux vaut vérifier : https://github.com/symfony/ldap/blob...tractQuery.php). Et aussi pour voir à quoi ressemblent les ressources "brutes" retournées par les fonctions LDAP de PHP, histoire de réussir à récupérer le hash du mot de passe provenant de LDAP (visiblement depuis la 4.4 il y a la possibilité de demander des "extra fields" : https://symfony.com/doc/4.4/security...l#extra-fields).

    Sinon autre solution à envisager (peut-être en premier d'ailleurs) : https://www.php.net/manual/fr/function.ldap-compare.php (cf. les exemples)
    Mais je suis pas certain que cette méthode soit implémentée dans le composant, donc à voir si tu peux pas appeler (un peu à l'arrache) directement ldap_compare...

    Bon courage !

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. Réponses: 1
    Dernier message: 27/03/2020, 14h02
  2. [3.x] Utiliser guard Symfony avec multiple provider
    Par Wilhem31 dans le forum Symfony
    Réponses: 1
    Dernier message: 11/09/2018, 12h43
  3. [Securité] Différence entre Impersonation et Authentication?
    Par Laurent Dardenne dans le forum Windows
    Réponses: 6
    Dernier message: 13/08/2009, 12h32
  4. Body...guard
    Par Sylvain James dans le forum XMLRAD
    Réponses: 4
    Dernier message: 11/03/2005, 16h07
  5. Mise en place d'une solution Data Guard 9i R2
    Par user_oracle dans le forum Oracle
    Réponses: 4
    Dernier message: 16/02/2005, 11h12

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo