Partage
  • Partager sur Facebook
  • Partager sur Twitter

[Symfony 5] [LDAP] Modifier rôle après connexion

    27 juillet 2020 à 11:18:12

    Bonjour,

    Au sein de mon projet Symfony, j'ai déployé le module de connexion LDAP (Tuto ici : https://symfony.com/doc/current/security/ldap.html)

    La connexion fonctionne parfaitement. Cependant, chaque utilisateur qui se connecte possède le rôle "ROLE_USER". Cependant, j'aimerais pouvoir ajouter un rôle (ROLE_SUPER_ADMIN en l’occurrence) si la personne appartient à un groupe.

    J'ai essayé d'utiliser des fonctions liés à l'user dans symofny (addRole() etc) mais rien ne fonctionne.

    Par conséquent, savez-vous comment faire afin d'ajouter un rôle à une personne après que celle ci se soit authentifiée et connectée ?

    Merci de votre aide :) 

    # app/config/services.yaml
    
    # This file is the entry point to configure your own services.
    # Files in the packages/ subdirectory configure your dependencies.
    
    # Put parameters here that don't need to change on each machine where the app is deployed
    # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
    parameters:
    
    services:
        # default configuration for services in *this* file
        _defaults:
            autowire: true      # Automatically injects dependencies in your services.
            autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
    
        Symfony\Component\Ldap\Ldap:
            arguments: ['@Symfony\Component\Ldap\Adapter\ExtLdap\Adapter']
        Symfony\Component\Ldap\Adapter\ExtLdap\Adapter:
            arguments:
                -   host: ldap.example.com
                    port: 389
                    #encryption: tls
                    options:
                        protocol_version: 3
                        referrals: false
    
        # makes classes in src/ available to be used as services
        # this creates a service per class whose id is the fully-qualified class name
        App\:
            resource: '../src/*'
            exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
    
        # controllers are imported separately to make sure services can be injected
        # as action arguments even if you don't extend any base controller class
        App\Controller\:
            resource: '../src/Controller'
            tags: ['controller.service_arguments']
    
        # add more service definitions when explicit configuration is needed
        # please note that last definitions always *replace* previous ones
    # app/config/packages/security.yaml
    
    security:
        # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
        providers:
            my_ldap:
                ldap:
                    service: Symfony\Component\Ldap\Ldap
                    base_dn: dc=example,dc=com
                    search_dn: cn=username,ou=Administration,dc=example,dc=com
                    search_password: userPassword
                    default_roles: ROLE_USER
                    
        firewalls:
            dev:
                pattern: ^/(_(profiler|wdt)|css|images|js)/
                security: false
            main:
                anonymous: ~
                 
                form_login_ldap:
                    login_path: login
                    check_path: login
                    service: Symfony\Component\Ldap\Ldap
                    dn_string: ou=aGroupWhereAreMyUsers,dc=example,dc=com
                    query_string: '(samaccountname={username})'
                    search_dn: cn=username,ou=Administration,dc=example,dc=com
                    search_password: userPassword
                
                logout:
                    path: app_logout
            
        access_control:
            - { path: ^/$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
            - { path: ^/info$, roles: ROLE_USER }
    //app/src/Controller/defaultController.php
    
    <?php
    
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\Routing\Annotation\Route;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
     
    
    class DefaultController extends AbstractController
    {
        /**
         * @Route("/info", name="default")
         */
        public function index()
        {
            return $this->render('default/index.html.twig', [
                'controller_name' => 'DefaultController',
                'user' => $this->getUser(),
            ]);
        }
    
        /**
         * @Route("/", name="login")
         */
        public function loginAction(Request $request, AuthenticationUtils $authUtils): Response
        {
            // get the login error if there is one
            $error = $authUtils->getLastAuthenticationError();
            
            // last username entered by the user
            $lastUsername = $authUtils->getLastUsername();
    
            if(!is_null($this->getUser()) && in_array('CN=userHasToBeInThisGroupToAccessToTheWebsite,OU=Administration,DC=example,DC=com', $this->getUser()->getEntry()->getAttributes()['memberOf'])){
    
                if(in_array('CN=groupWhereUsersWhoAreInAreAdmins,OU=Administration,DC=example,DC=com', $this->getUser()->getEntry()->getAttributes()['memberOf'])){
                    //Insert the code here to add "ROLE_SUPER_ADMIN" to the user before he goes on the dashboard
                }
    
                return $this->redirectToRoute('default');
            }
    
    
            return $this->render('security/login.html.twig', array(
                'last_username' => $lastUsername,
                'error'         => $error,
            ));
        }
    
        /**
         * @Route("/logout", name="app_logout")
         */
        public function logout()
        {
            // controller can be blank: it will never be executed!
            throw new \Exception('Don\'t forget to activate logout in security.yaml');
        }
    
    }
    {# app/templates/security/login.html.twig #}
    
    {% extends 'base.html.twig' %}
     
    {% block body %}
        {% if error %}
            <div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
        {% endif %}
         
        <form method="post">
            <label for="username">Username:</label>
            <input type="text" id="username" name="_username" value="{{ last_username }}" required />
         
            <label for="password">Password:</label>
            <input type="password" id="password" name="_password" required />
    
            <input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}" />
         
            <button type="submit">login</button>
        </form>
    
        <a href='/logout'> Logout </a>
    {% endblock %}






    • Partager sur Facebook
    • Partager sur Twitter
      30 août 2021 à 12:30:11

      Bonjour à toi, je vois que ton sujet n'a pas eu beaucoup de succès ^^

      je me permets de t'écrire, car c'est plutôt moi qui ai des questions envers toi.

      Désolé de ne pas pouvoir répondre à ton sujet.

      Alors, je dois mettre en place une authentification via ldap pour mon entreprise où je suis en stage. Je travaille avec Symfony et en version locale.

      J'ai suivi la doc Symfony pour la configuration des fichiers services et security.yaml. 

      Cela dit : 

      1) Comment configurer son formulaire LdapAuthenticatorForm ?

      2 ) Comment savoir si sa config' est bonne et surtout comment TESTER LA CONNEXION AFIN DE SAVOIR SI NOTRE CONFIG EST BONNE ?

      C'est surtout le second point qui me pose souci, je n'ai aucune idée de comment faire.

      J'espère que tu liras ce message.

      Julien

      • Partager sur Facebook
      • Partager sur Twitter
      Cat's eyes
        1 février 2023 à 15:31:23

        @Neokles Est ce que tu as réussi à utiliser les roles dans le LDAP ?

        Si quelqu'un à la solution je veux bien qu'il m'explique comment est ce qu'il as fait.

        Ma connexion au LDAP marche de la même façon que ce que Neokles à montré dans ses captures d'écran.

        • Partager sur Facebook
        • Partager sur Twitter
          1 février 2023 à 16:54:56

          Bonjour,

          Je suis sur le sujet des rôles mais ce n'est pas dans Symfony qu'il faut le faire mais dans le ldap en ajoutant des groupes et en ajoutant l'overlay memberof pour avoir un attribut du user ldap.

          Et ensuite comme indiqué dans la doc Symfony il y a ce bout de code lors du chargement de l'utilisateur à partir du ldap.

          if ($entry->getAttribute("memberOf")) {
                      foreach ($entry->getAttribute("memberOf") as $LdapGroupDn)
                      {
                          $results[]= "ROLE_".ldap_explode_dn($LdapGroupDn,1)[0];
                      }
                  }

          En espérant que cela puisse servir.

          A+

          • Partager sur Facebook
          • Partager sur Twitter
            2 février 2023 à 15:43:48

            @monkey3d 

            Salut, Merci pour ta réponse.

            J'ai bien des groupes dans mon Ldap, lorsque je récupère un utilisateur, il a bien un attribut memberOf avec tout ses groupes.

            Où as tu trouvé ce bout de code et où faut t'il le mettre pour récupérer les rôles dans l'attribut memberOf d'un utilisateur du LDAP ?

            Est ce que tu as créer une nouvelle classe LdapUserProvider comme dans ce tuto : https://wiki.jordan-lenuff.com/Technique/Symfony/Connexion_LDAP_ou_AD pour rajouter la récupération et l'ajout des rôles ou tu as utilisé la classe LdapUserProvider ?

            (Dans la classe LdapUserProvider du bundle Ldap de Symfony, il n'y a pas la récupération des rôles. https://github.com/symfony/symfony/blob/6.3/src/Symfony/Component/Ldap/Security/LdapUserProvider.php)

            • Partager sur Facebook
            • Partager sur Twitter
              3 juillet 2023 à 11:02:59

              Comment 'set' le default_roles d'un LdapUSer fourni par le provider LDAP ? Ce en fonction de son login (ou autre éval) ?

              Bonjour,

              Ce post et POC ne s'inscrit pas tout à fait dans celui :[Symfony 5] [LDAP] Modifier rôle après connexion
              mais plutôt dans :  [Symfony 5/6] : Définir les rôles au moment de la connexion d'un user LDAP

              Au début je pensais aussi à "changer" les rôles une fois connecté ... compliqué dans les faits (à cause des badges/passeport/tokens, ...).

              Ce POC démontre dans ce cas qu'il est plus simple de FIXER les rôles en amont, au moment du "ldap user providing" , que de devoir retoucher/reconstruire les tokens, etc. par la suite.

              Je vous propose donc ceci (attention je suis en Symfony 6 - LDAP avec des entrées PosixAccount référencés par leurs uid).

              J'ai un formulaire de login security/login.html.twig appelé (render) depuis le SecurityLoginController (routes : login et logout)
              La route login appelle le formulaire qui gère les champs : _username, _password et _csrf_token

              C'est une insertion custom amont,  pour valuer d'une autre manière le default_roles fourni par défaut,
              ce, via un service 'user ldap provider' maison :

              1) Définir notre service customisé dans services.yaml

                  # Notre propre service 'user provider' depuis notre ldap (sa classe) + arguments de son constructeur
                  my_custom_ldap_user_provider:
                      class: App\Security\MyCustomLdapUserProvider
                      arguments:                                                  # service injection via '@....', ou string injection                                    
                          -    '@Symfony\Component\Ldap\Ldap'                     # service Ldap
                          -    dc=........votre DN racine...........dc=fr                   # base_dn de votre annuaire LDAP
                          -    null                                               # searchDn (login pour le bind ldap, si nécessaire pour accéder/chercher)
                          -    null                                               # searchPassword (password du bind ldap)
                          -    uid                                                # uid_key
                          -    (&(objectClass=posixAccount)(uid={username}))      # filter

              Il faut évidemment avoir créé aussi les 2 services (Ldap + Adapter) : https://symfony.com/doc/6.0/security/ldap.html#configuring-the-ldap-client

              2) Faire référence à notre service  my_custom_ldap_user_provider dans : security.yaml

                (et donc SUPPRIMER/COMMENTER le provider ldap classique :   https://symfony.com/doc/6.0/security/ldap.html#security-ldap-user-provider

               
              providers:
              my_ldap:
              # Available options are "chain", "entity", "id: <alias du service>", "ldap", "memory"
              id: my_custom_ldap_user_provider

              # my_ldap:
              #     ldap:
              #         service: Symfony\Component\Ldap\Ldap
              # ..........

              et ajouter si besoin une hiérarchie (optionnel):

              role_hierarchy:
              ROLE_ADMIN: ROLE_USER_MANAGER
              ROLE_USER_MANAGER: ROLE_USER

              3) enfin, créer le service/la classe: MyCustomLdapUserProvider  (inspirée + version simplifiée de celle 'vendor' :  LdapUserProvider : pas des gestion du password, ni des extraFields )
                    cf.  les classes fournies par Symfony : vendor\symfony\ldap\Security\LdapUserProvider.php
                    +  vendor\symfony\security-core\User\UserProviderInterface.php
                    +  vendor\symfony\ldap\Security\LdapUser.php
              =>
              src\Security\MyCustomLdapUserProvider.php

              <?php

              namespaceApp\Security;

              use Symfony\Component\Ldap\Ldap;
              use Symfony\Component\Ldap\Entry;
              use Symfony\Component\Ldap\Security\LdapUser;
              use Symfony\Component\Security\Core\User\UserInterface;
              use Symfony\Component\Ldap\Exception\ConnectionException;
              use Symfony\Component\Security\Core\User\UserProviderInterface;
              use Symfony\Component\Security\Core\Exception\UserNotFoundException;
              use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
              use Symfony\Component\Security\Core\Exception\UnsupportedUserException;

              classMyCustomLdapUserProviderimplementsUserProviderInterface#, PasswordUpgraderInterface pas implémenté ici
              {
              private$ldap;
              privatestring$baseDn;
              private ?string$searchDn;
              private ?string$searchPassword;
              private ?string$uidKey;
              privatestring$defaultSearch;

              // cf. services.yaml / valeurs des parametres passés
              publicfunction__construct(Ldap$ldap, string$baseDn, string$searchDn = null, string$searchPassword = null, string$uidKey = null, string$filter = null)
                  {
              if (null === $uidKey) {
              # $uidKey = 'sAMAccountName';
              $uidKey = 'uid';
                      }

              if (null === $filter) {
              # $filter = '({uid_key}={user_identifier})';
              $filter = '(&(objectClass=posixAccount)(uid={username}))';
              // attention aux bugs entre formulaires/modules/makers de symfony 5.4 à 6.0 {username} et Symfony 6.2 : {user_identifier} , voire les deux !!! (cf. str_replace(['{username}', '{user_identifier} plus bas)
                      }

              $this->ldap = $ldap;
              $this->baseDn = $baseDn;
              $this->searchDn = $searchDn;
              $this->searchPassword = $searchPassword;
              $this->uidKey = $uidKey;
              $this->defaultSearch = str_replace('{uid_key}', $uidKey, $filter);
                  }

              /*
                   * Loads the user for the given user identifier (e.g. username or email). This method must throw UserNotFoundException if the user is not found.
                   */
              publicfunctionloadUserByIdentifier(string$identifier): UserInterface
                  {
              try {
              $this->ldap->bind($this->searchDn, $this->searchPassword);
              $identifier = $this->ldap->escape($identifier, '', Ldap::ESCAPE_FILTER);
              $query = str_replace(['{username}', '{user_identifier}'], $identifier, $this->defaultSearch);
              $search = $this->ldap->query($this->baseDn, $query, ['filter' => '*']);
                      } catch (ConnectionException$e) {
              $e = newUserNotFoundException(sprintf('User "%s" not found.', $identifier), 0, $e);
              $e->setUserIdentifier($identifier);

              throw$e;
                      }

              $entries = $search->execute();
              $count = \count($entries);

              if (!$count) {
              $e = newUserNotFoundException(sprintf('User "%s" not found.', $identifier));
              $e->setUserIdentifier($identifier);

              throw$e;
                      }

              if ($count > 1) {
              $e = newUserNotFoundException('More than one user found.');
              $e->setUserIdentifier($identifier);

              throw$e;
                      }

              $entry = $entries[0];

              try {
              if (null !== $this->uidKey) {
              $identifier = $this->getAttributeValue($entry, $this->uidKey);
                          }
                      } catch (InvalidArgumentException$e) {
                      }

              return$this->loadUser($identifier, $entry);
                  }

              /*
                   * Refreshes the user.
                   *
                   * It is up to the implementation to decide if the user data should be totally reloaded (e.g. from the database),
                   * or if the UserInterface object can just be merged into some internal array of users / identity map.
                   *
                   */
              publicfunctionrefreshUser(UserInterface$user): UserInterface
                  {
              $password = null;
              $extraFields = [];

              if (!$user instanceof LdapUser) {
              thrownewUnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_debug_type($user)));
                      }

              //dd($user->getEntry()->getAttribute('mail')[0]);
              $roles = $this->getRolesOfUserIdentifier($user->getUserIdentifier());

              // return new LdapUser($user->getEntry(), $user->getUserIdentifier(), $user->getPassword(), $user->getRoles(), $user->getExtraFields());
              returnnewLdapUser($user->getEntry(), $user->getUserIdentifier(), $password, $roles, $extraFields);
                  }

              /**
                   * Loads a user from an LDAP entry. (private, implemented from the protected one)
                   */
              privatefunctionloadUser(string$identifier, Entry$entry): UserInterface
                  {
              $password = null;
              $extraFields = [];

              $roles = $this->getRolesOfUserIdentifier($identifier);
              returnnewLdapUser($entry, $identifier, $password, $roles, $extraFields);
                  }

              privatefunctiongetAttributeValue(Entry$entry, string$attribute)
                  {
              if (!$entry->hasAttribute($attribute)) {
              thrownewInvalidArgumentException(sprintf('Missing attribute "%s" for user "%s".', $attribute, $entry->getDn()));
                      }

              $values = $entry->getAttribute($attribute);

              if (1 !== \count($values)) {
              thrownewInvalidArgumentException(sprintf('Attribute "%s" has multiple values.', $attribute));
                      }

              return$values[0];
                  }

              /*
                   * Whether this provider supports the given user class.
                   */
              publicfunctionsupportsClass(string$class): bool
                  {

              returnLdapUser::class === $class;
                  }

              // Management SPECIFIQUE des rôles dans notre Appli
              privatefunctiongetRolesOfUserIdentifier(string$identifier): array
                  {
              if ($identifier == 'administrateur001') {
              $roles = ['ROLE_ADMIN'];
                      } elseif ($identifier == 'utilisateur001') {
              $roles = ['ROLE_GROUP_MANAGER'];
                      } else {
              $roles = ['ROLE_USER']; // rôle par défaut
                      };
              return$roles;
                  }
              }


              Pour ce POC, tout ce passe dans la dernière fonction du provider :

              privatefunctiongetRolesOfUserIdentifier(string$identifier): array {.....}
              ... évidemment, vous devrez changer le code de cette fonction pour aller chercher les infos discriminantes ailleurs (en BDD SQL, ou pour évaluer les rôles depuis le contenu du LDAP lui-même, ...), car ici, codé en dur, c'est juste pour un POC le plus simple possible  ;-)
              Christian

              -
              Edité par Christian@ijl 3 juillet 2023 à 11:22:29

              • Partager sur Facebook
              • Partager sur Twitter

              [Symfony 5] [LDAP] Modifier rôle après connexion

              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
              × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
              • Editeur
              • Markdown