Zend FR

Consultez la FAQ sur le ZF avant de poster une question

Vous n'êtes pas identifié.

#1 28-05-2012 16:43:28

bakura
Administrateur
Date d'inscription: 30-01-2010
Messages: 353

Système d'ACL avec Zend Framework 2

Bonjour à tous ;-),

Je viens de terminer mon système d'ACL pour mon site avec ZF 2, et je vous partage le code. N'hésitez pas à me signaler toute remarque (je n'ai pas encore eu le temps de le tester à fond).

Les contraintes que j'avais pour mon système :

* Pouvoir charger les ACL depuis un fichier de configuration.
* Pour facilement ajouter des assertions.
* Ne pas créer l'arbre des autorisations pour toutes les routes, mais uniquement pour la route demandée.
* Pouvoir choisir la page de redirection en cas de refus d'accès à une page.

D'autre part, mon système utilise le nouveau composant ServiceManager, qui remplace avantageusement (en termes de performances) Zend\Di, ainsi que les évènements et les modules.

Je ne ferai pas d'explications trop longues, mais si vous avez des questions, n'hésitez pas.

Code:

/**
         * @param ModuleManager $moduleManager
         */
        public function init(ModuleManager $moduleManager)
        {
            $events = $moduleManager->events();
            $sharedEvents = $events->getSharedManager();

            // Attache l'évènement d'ACL
            $sharedEvents->attach('Zend\Mvc\Controller\ActionController', MvcEvent::EVENT_DISPATCH, function($e)                                                                        {
                $serviceManager = $e->getApplication()->getServiceManager();
                $acl = $serviceManager->get('Common\Acl\Acl');
                $acl->dispatch($e);
            }, 100);
        }

Ce code est à placer dans l'un de vos modules (chez moi, dans un module "Common" qui regroupe toutes les fonctionnalités génériques que je réutilise dans tous mes projets).

Ce petit bout de code se contente d'ajouter un listener à l'évènement MvcEvent::EVENT_DISPATCH (qui est lancé dès qu'une requête est dispatchée), avec une priorité suffisamment élevée pour passer avant les autres (on souhaite effectivement autoriser/refuser l'accès le plus tôt possible).

A noter que l'objet Common\Acl\Acl est récupéré via le service manager. Pour plus d'informations : http://blanchon-vincent.developpez.com/ … fabriques/

Ma fabrique est définie dans le fichier module.config.php du module concerné :

Code:

return array(
        // Service manager
        'service_manager' => array(
            'factories' => array(
                'Common\Acl\Acl' => function($serviceManager) {
                    $authenticationService = $serviceManager->get('Zend\Authentication\AuthenticationService');
                    $acl = new Acl($serviceManager, $authenticationService);

                    return $acl;
                }

A noter qu'il aurait été possible de ne pas déclarer de fabrique et de laisser le composant d'injecteur de dépendance injecter les dépendances automatiquement, toutefois cela n'est pas conseillé en terme de performances, c'est d'ailleurs pourquoi ce composant a été introduit dans la bêta 4.

Mon objet ACL prend deux paramètres :

- le service manager, qui nous permettra de récupérer la config de TOUS les modules (par exemple, si vous disposez d'un module "user" et "blog", vous pouvez définir tous les ACL liés au module "user" dans le fichier de config du module User, et les ACL liés au module "blog" dans le fichier de config du module Blog, le servicemanager permettant de récupérer le tout sous forme d'un unique tableau).

- un objet AuthenticationService qui nous permet de récupérer l'utilisateur connecté.

Et enfin, le gros du boulot, l'objet Acl :

Code:

namespace Common\Acl;

    use Zend\Acl\Acl as BaseAcl,
        Zend\Authentication\AuthenticationService,
        Zend\Mvc\MvcEvent,
        Zend\Mvc\Router\RouteMatch,
        Zend\ServiceManager\ServiceManager;


    class Acl extends BaseAcl
    {
        /**
         * Rôle par défaut
         */
        const DEFAULT_ROLE = 'guest';

        /**
         * @var \Zend\ServiceManager\ServiceManager
         */
        protected $serviceManager;

        /**
         * @var \Zend\Authentication\AuthenticationService
         */
        protected $authenticationService;

        /**
         * Route vers laquelle on redirige si les accès ne sont pas valides
         *
         * @var string
         */
        protected $redirectTo = 'home';


        /**
         * @param \Zend\ServiceManager\ServiceManager $serviceManager
         * @param \Zend\Authentication\AuthenticationService $authenticationService
         */
        public function __construct(ServiceManager $serviceManager, AuthenticationService $authenticationService)
        {
            $this->serviceManager = $serviceManager;
            $this->authenticationService = $authenticationService;

            $this->initRoles();
        }

        /**
         * @param \Zend\Mvc\MvcEvent $e
         */
        public function dispatch(MvcEvent $e)
        {
            $this->build($e->getRouteMatch());

            // Récupération de l'utilisateur courant
            $role = $this->authenticationService->getIdentity();
            if ($role == null) {
                $role = self::DEFAULT_ROLE;
            }

            // Si l'utilisateur n'est pas autorisé, on le redirige vers la page par défaut, ou une autre page si elle a été spécifiée
            // dans le fichier de configuration
            $matchedRouteName = $e->getRouteMatch()->getMatchedRouteName();

            if (!$this->isAllowed($role, $matchedRouteName)) {
                $this->redirect($e, $this->redirectTo);
            }
        }

        /**
         * @param \Zend\ServiceManager\ServiceManager $serviceManager
         * @return Acl
         */
        public function setServiceManager(ServiceManager $serviceManager)
        {
            $this->serviceManager = $serviceManager;

            return $this;
        }

        /**
         * Initialise les rôles à partir du fichier de configuration
         */
        private function initRoles()
        {
            $configuration = $this->serviceManager->get('Configuration');

            if (isset($configuration['acl']['roles'])) {
                $roles = $configuration['acl']['roles'];

                foreach($roles as $role => $parents) {
                    $this->addRole($role, $parents);
                }
            }
        }

        /**
         * @param \Zend\Mvc\Router\RouteMatch $routeMatch
         * @return mixed
         */
        private function build(RouteMatch $routeMatch)
        {
            // Récupération de la configuration
            $configuration = $this->serviceManager->get('Configuration');
            $matchedRouteName = $routeMatch->getMatchedRouteName();

            if (isset($configuration['acl']['resources'])) {
                $resources = $configuration['acl']['resources'];
                $routeParts = explode('/', $routeMatch->getMatchedRouteName());
                $parentPart = null;

                foreach($routeParts as $routePart) {
                    $this->addResource($routePart, $parentPart);

                    // Par défaut, on interdit l'accès à toute ressource dont l'ACL n'a pas été défini
                    if (!isset($resources[$routePart])) {
                        $this->deny(self::DEFAULT_ROLE, $routePart);

                        return;
                    }

                    $resources = $resources[$routePart];

                    if (isset($resources['allow'])) {
                        $allow = $resources['allow'];
                        $assertion = $this->buildAssertion($allow);

                        $this->allow($allow['roles'], $routePart, null, $assertion);
                    }

                    if (isset($resources['deny'])) {
                        $deny = $resources['deny'];
                        $assertion = $this->buildAssertion($deny);

                        $this->deny($deny['roles'], $routePart, null, $assertion);
                    }

                    if (isset($resources['redirect_to'])) {
                        $this->redirectTo = $resources['redirect_to'];
                    }

                    // Y a-t-il des enfants ?
                    if (!isset($resources['child_resources'])) {
                        break;
                    }

                    $resources = $resources['child_resources'];
                    $parentPart = $routePart;
                }

                // On définit la route "complète" comme enfant de la dernière ressource afin d'hériter de ses autorisations
                if (count($routeParts) > 1) {
                    $this->addResource($matchedRouteName, end($routeParts));
                }
            }
        }

        /**
         * @param array $resourcePart
         * @return null
         */
        private function buildAssertion(array $resourcePart)
        {
            if (isset($resourcePart['assertion'])) {
                return new $resourcePart['assertion']();
            }

            return null;
        }

        /**
         * @param \Zend\Mvc\MvcEvent $e
         * @param string $route
         */
        private function redirect(MvcEvent $e, $route)
        {
            $url = $e->getRouter()->assemble(array(), array('name' => $route));
            $response = $e->getResponse();
            $response->headers()->addHeaderLine('Location', $url);
            $response->setStatusCode(302);
            $response->sendHeaders();

            exit;
        }
    }

Tout d'abord la fonction initRoles, qui permet de voir comment fonctionne la configuration (on verra ça plus loin) : elle récupère du fichier de configuration les rôles définis dans l'application, et les ajoute à l'ACL (puisque mon objet hérite de Zend\Acl\Acl).

La fonction dispatch récupère l'utilisateur actuellement connecté, et vérifie s'il a accès à la ressource. Si oui, rien ne se passe, si non, il est redirigé vers une page (la page d'accueil par défaut, mais il est possible de changer ce comportement dans la configuration).

Je passe sur la fonction build qui m'a pris un peu de temps à écrire, sachez juste qu'elle permet de construire l'autorisation à partir de la configuration.

Voyons maintenant comment définir la configuration.

Voici un exemple pour mon module principal :

Code:

// Gestion des autorisations
        'acl' => array(
            'roles' => array(
                'guest' => null,
                'a' => 'guest',
                'b' => 'guest'
            ),
            'resources' => array(
                'home' => array(
                    'allow' => array(
                        'roles' => array('guest', 'a', 'b')
                    )
                )
            )
        ),

La clé 'acl' contient deux sous-clés : rôles pour définir les différents rôles et leur relation de parenté, et resources. Chaque ressource est identifié par le nom de la route correspondante (il aurait d'ailleurs été possible d'écrire les ACL directement dans les définitions des routes, mais je préfère séparer les responsabilités).

Ici, ma route 'home' contient un tableau 'allow', qui contient lui-même un tableau 'rôles' permettant de définir qui a accès à cette route. A noter que l'on aurait pu écrire array('guest') car 'a' et 'b' hérite de 'guest'.

Comme pour les routes, il est possible de définir des resources enfant :

Code:

'page1' => array(
                    'child_resources' => array(
                        'subpage1' => array(
                            'allow' => array(
                                'roles' => array('guest')
                            ),
                            'deny' => array(
                                'roles' => array('a', 'b')
                            )
                        )
                    )

Encore une fois, il doit y avoir une correspondance parfaite entre le nom des routes et les noms donnés pour l'ACL. Ainsi, il existe une route nommée page1/subpage1. Dans ce cas, les utilisateurs auront accès à la route 'page1/subpage1' tandis que les utilisateurs 'a' et 'b' n'y auront pas accès, et seront redirigés par défaut vers la page d'accueil (note : il ne faut pas confondre les noms de route du couple contrôleur/action. Ainsi la route page1/subpage1 peut très bien redirieger vers le contrôleur "Index", et l'action "toto", si c'est comme ça que vous l'avez spécifié dans votre fichier de configuration).

Si on souhaite les rediriger vers une autre page, il suffit de rajouter la clé 'redirect_to', avec un nom de route :

Code:

'page1' => array(
                    'child_resources' => array(
                        'subpage1' => array(
                            'allow' => array(
                                'roles' => array('guest')
                            ),
                            'deny' => array(
                                'roles' => array('a', 'b')
                            ),
                            'redirect_to' => 'uneRoute'
                        )
                    )

Ici, les utilisateurs a et b seront redirigés vers la route 'uneRoute'.

Il est également possible de spécifier une assertion, un objet permettant de spécifier une condition à l'accès ou au refus. Par exemple, on peut décider d'autoriser la page à tous les utilisateurs, SAUF à ceux dont l'IP est banni.

Il suffit pour cela d'ajouter la clé 'assertion' :

Code:

'home' => array(
                    'allow' => array(
                        'roles' => array('guest', 'a', 'b'),
                        'assertion' => 'Common\Acl\Assertion\IPTest'
                    )
                )

Ou la valeur de la clé 'assertion' est le FQCN de la classe.


Si vous avez des questions sur le fonctionnement interne ou l'utilisation, n'hésitez pas !

Hors ligne

 

#2 29-05-2012 10:15:37

Orkin
Administrateur
Lieu: Paris
Date d'inscription: 09-12-2011
Messages: 1261

Re: Système d'ACL avec Zend Framework 2

Salut, merci pour ce retour. Je pense que ça me servira smile.

Par contre j'ai un truc qui me chagrine, au niveau de ton authentificationService tu ne garde que le rôle de l'utilisateur ? Puisque tu transmets $role à la méthode isAllowed et je n'ai pas vu de redéfinition de cette méthode.

Hors ligne

 

#3 29-05-2012 12:33:08

bakura
Administrateur
Date d'inscription: 30-01-2010
Messages: 353

Re: Système d'ACL avec Zend Framework 2

Je crois que je n'ai pas compris ta question.

Mon AuthenticationService est un objet normal de type Zend\Authentication\AuthenticationService. Sauf que je lui ait donné un adapter perso qui va chercher l'utilisateur en base.

En session je ne stocke que l'identifiant, et quand je fais un $authenticationService->getIdentity(), il ira chercher dans la base de données l'utilisateur correspondant, l'objet retourné implémentant l'interface RoleInterface.

Donc si aucun utilisateur n'est loggué, getIdentity retournera null et le rôle sera 'guest'.

Hors ligne

 

#4 29-05-2012 14:18:49

Orkin
Administrateur
Lieu: Paris
Date d'inscription: 09-12-2011
Messages: 1261

Re: Système d'ACL avec Zend Framework 2

En fait la confusion vient du fait que tu as un adapter perso smile.

En session je ne stocke que l'identifiant mais lorsque je fais $authenticationService->getIdentity() il me renvoi uniquement l'id et c'est à moi ensuite d'aller récupérer l'utilisateur en base. Du coup j'ai "compris" que tu stockais le role en session et ça me paraissait un peu incohérent ^^.

Je pense que ça pourrait être intéressant que tu complètes ton article justement en abordant cette partie là. Détailler un peu plus comment tu récupères l'utilisateur et l'utilisation d'une classe implémentant l'interface RoleInterface. De cette façon on pourrait bien percevoir le cheminement complet : de la récupération en base jusqu'à l'accès à la page.

Hors ligne

 

#5 29-05-2012 15:50:56

bakura
Administrateur
Date d'inscription: 30-01-2010
Messages: 353

Re: Système d'ACL avec Zend Framework 2

Ok, je te mets ça si j'oublie pas en rentrant ce soir smile.

Hors ligne

 

#6 14-06-2012 15:16:33

Orkin
Administrateur
Lieu: Paris
Date d'inscription: 09-12-2011
Messages: 1261

Re: Système d'ACL avec Zend Framework 2

Je crois que tu as oublié :p !

Hors ligne

 

#7 15-06-2012 09:53:38

bakura
Administrateur
Date d'inscription: 30-01-2010
Messages: 353

Re: Système d'ACL avec Zend Framework 2

Effectivement ^^.

Voici l'objet DbStorage :

Code:

namespace Common\Authentication\Storage;

    use Doctrine\ORM\EntityManager,
        Zend\Authentication\Storage\StorageInterface;


    /**
     * Cette classe implémente l'interface StorageInterface et permet de stocker le résultat d'une authentification dans la base de données
     */
    class Db implements StorageInterface
    {
        /**
         * @var \Doctrine\ORM\EntityManager
         */
        protected $em;

        /**
         * @var \Zend\Authentication\Storage\StorageInterface
         */
        protected $storage;

        /**
         * @var string
         */
        protected $identityClassName;


        /**
         * @param \Doctrine\ORM\EntityManager $em
         * @param \Zend\Authentication\Storage\StorageInterface $storage
         * @param $identityClassName
         */
        public function __construct(EntityManager $em, StorageInterface $storage, $identityClassName)
        {
            $this->em = $em;
            $this->storage = $storage;
            $this->identityClassName = $identityClassName;
        }

        /**
         * @return bool
         */
        public function isEmpty()
        {
            return $this->storage->isEmpty();
        }

        /**
         * @return mixed|null|object
         */
        public function read()
        {
            $identity = $this->storage->read();

            if (is_int($identity) || is_scalar($identity)) {
                $identity = $this->em->getRepository($this->identityClassName)
                                     ->find($identity);
            }
            else {
                $identity = null;
            }

            return $identity;
        }

        /**
         * @param $contents
         */
        public function write($contents)
        {
            $this->storage->write($contents);
        }

        /**
         *
         */
        public function clear()
        {
            $this->storage->clear();
        }
    }

Il s'agit ni plus ni moins d'une implémentation de l'interface StorageInterface. Cette implémentation contient elle même un autre StorageInterface (dans mon cas, une session toute bête, contenant uniquement l'identifiant).

Hors ligne

 

#8 15-06-2012 10:23:11

Orkin
Administrateur
Lieu: Paris
Date d'inscription: 09-12-2011
Messages: 1261

Re: Système d'ACL avec Zend Framework 2

Merci bien smile

Hors ligne

 

#9 25-06-2012 21:33:06

booradley
Membre
Date d'inscription: 10-01-2009
Messages: 163

Re: Système d'ACL avec Zend Framework 2

Salut Bakura,
J'avais décidé moi aussi d'implémenter mon propre système mais comme il existe déjà le module "officiel" ZfcAcl et le module ZfcUserAcl qui va lui aussi être prochainement migré dans ZF-Commons, je pense qu'il est préférable de faire de l'intégration de modules ZfcUser,ZfcUserDoctrineORM,ZfcAcl et ZfcUserAcl.

https://github.com/ZF-Commons/ZfcAcl
https://github.com/bjyoungblood/ZfcUserAcl

Quoiqu'il en soit bravo pour tout boulot et surtout d'avoir pris le temps de nous le partager.
Merci!!

Hors ligne

 

#10 14-01-2013 00:44:25

jmleroux
Membre
Lieu: Nantes
Date d'inscription: 14-11-2012
Messages: 32
Site web

Re: Système d'ACL avec Zend Framework 2

Bonsoir,
Il existe aussi un module pour utiliser un système RBAC au lieu des ACLs.
J'ai commencé à jouer avec et il est pas mal, même si perfectible.

https://github.com/ZF-Commons/ZfcRbac

Hors ligne

 

#11 14-01-2013 09:47:11

bakura
Administrateur
Date d'inscription: 30-01-2010
Messages: 353

Re: Système d'ACL avec Zend Framework 2

Salut,

Je sais, j'y ait contribué à ZfcRbac smile. Mon code n'est plus valide de toute façon.

Hors ligne

 

#12 14-01-2013 09:53:55

jmleroux
Membre
Lieu: Nantes
Date d'inscription: 14-11-2012
Messages: 32
Site web

Re: Système d'ACL avec Zend Framework 2

J'ai vu wink
et j'ai moi aussi lancé plusieurs pull requests, mais le maintainer n'est pas hyper réactif... smile

Hors ligne

 

#13 15-01-2013 10:23:16

bakura
Administrateur
Date d'inscription: 30-01-2010
Messages: 353

Re: Système d'ACL avec Zend Framework 2

J'ai vu wink
et j'ai moi aussi lancé plusieurs pull requests, mais le maintainer n'est pas hyper réactif... sad

Je dois t'avouer que ça nous inquiète un peu. Ca fait deux mois que Spiffy ne s'est pas connecté sur aucun channel IRC. On lui a envoyé un mail pour savoir ce qu'il faisait, car c'est un peu bizarre (ok, il a eu un bébé et il a un boulot assez chronophage mais il était quand même quasiment tout le temps là sur IRC, donc là c'est un peu étrange...).

Hors ligne

 

#14 15-01-2013 12:02:44

jmleroux
Membre
Lieu: Nantes
Date d'inscription: 14-11-2012
Messages: 32
Site web

Re: Système d'ACL avec Zend Framework 2

Et ça ne se fait pas d'avoir un autre maintainer en backup ?

Hors ligne

 

#15 15-01-2013 13:25:13

bakura
Administrateur
Date d'inscription: 30-01-2010
Messages: 353

Re: Système d'ACL avec Zend Framework 2

Ca dépend... En l'occurrence son répo est sur ZF-Commons, donc au pire si on a pas de nouvelles de lui d'ici 1 ou 2 mois (auquel cas ça m'ennuierais un peu, ça voudrait dire qu'il s'est passé un truc pas cool hmm), je demanderai à Matthew qu'il me donne l'accès.

Si les Repo avaient été sur son compte perso bah... on aurait du le forker et faire du fork le "nouveau" répo officiel.

Hors ligne

 

#16 15-01-2013 14:48:35

jmleroux
Membre
Lieu: Nantes
Date d'inscription: 14-11-2012
Messages: 32
Site web

Re: Système d'ACL avec Zend Framework 2

OK, et ben je vais continuer à guetter ce projet et essayer de me mettre à IRC.

Puisqu'on en parle, est-ce qu'une demande de tag à sa place dans les "issues" de github ?

En effet, pour pouvoir utiliser un module en prod, il faudrait que sa release soit tagguée afin de pouvoir avoir des sources stables.

Hors ligne

 

#17 15-01-2013 17:12:08

bakura
Administrateur
Date d'inscription: 30-01-2010
Messages: 353

Re: Système d'ACL avec Zend Framework 2

Oui. Ca aurait sa place smile.

Hors ligne

 

#18 22-01-2013 09:57:26

jmleroux
Membre
Lieu: Nantes
Date d'inscription: 14-11-2012
Messages: 32
Site web

Re: Système d'ACL avec Zend Framework 2

Je viens de tomber sur BjyAuthorize

Ca m'a l'air pas mal du tout et on dirait qu'il a intégré les fonctionnalités de ZfcRbac.

Bizarre que ce ne soit pas intégré aux modules communs Zfc, surtout qu'il annule et remplace ZfcAcl.

Hors ligne

 

#19 22-01-2013 13:06:07

bakura
Administrateur
Date d'inscription: 30-01-2010
Messages: 353

Re: Système d'ACL avec Zend Framework 2

ZfcAcl n'est plus supporté. Il y a eu quelques soucis avec Zfc, c'est un truc assez rigide et finalement je crois qu'il n'y aura plus de gros modules intégrés à Zfc.

Hors ligne

 

#20 22-01-2013 15:15:55

jmleroux
Membre
Lieu: Nantes
Date d'inscription: 14-11-2012
Messages: 32
Site web

Re: Système d'ACL avec Zend Framework 2

Et quel serait ton choix comme module de frewall basé sur RBAC ?

ZfcRbac ou BjyAuthorize

Hors ligne

 

#21 23-01-2013 15:44:32

bakura
Administrateur
Date d'inscription: 30-01-2010
Messages: 353

Re: Système d'ACL avec Zend Framework 2

Bah BjyAuthorize est basé sur Zend\Permissions\Acl, ZfcRbac est basé sur Zend\Permissions\Rbac.

Niveau code, Zend\Permissions\Rbac a été écrit après et est donc plus "moderne" (il utilise plusieurs fonctionnalités de PHP 5.3), après c'est une différence de modèle surtout. Faut que tu voie, suivant ton application, quel modèle est le mieux adapté.

Hors ligne

 

#22 09-02-2013 19:10:53

irumi
Membre
Date d'inscription: 29-04-2011
Messages: 47

Re: Système d'ACL avec Zend Framework 2

Merci pour ce code. Même s'il n'est plus valide actuellement les idées sont la.

Petite question, vous avez l'aire de connaitre un peut BjyAuthorize. Faut il absolument utiliser ZfcUser pour pouvoir l'utiliser? (et donc la même structure de base de donnée ....)

Merci d'avance

Hors ligne

 

#23 11-02-2013 22:58:52

bakura
Administrateur
Date d'inscription: 30-01-2010
Messages: 353

Re: Système d'ACL avec Zend Framework 2

Non je n'ai jamais utilisé BjyAuthorize mais j'en ait eu de bons retours.

Hors ligne

 

#24 04-03-2013 16:24:44

alien7
Membre
Date d'inscription: 29-04-2007
Messages: 447

Re: Système d'ACL avec Zend Framework 2

Ca serait bien un petit cookbook de zfcrbac, car là je n'arrive pas à l'utiliser en l'état.
J'ai regardé le module BjyAuthorize , ce module propose le schema pour la table user_role, et il requiert le module zfcuser. Zfcrbac ne propose rien, c'est à nous de d'implémenter le reste ?

Merci


ZF 2.3 - Twitter Bootstrap 3.2
Local: Ubuntu  -> Apache2 2.4 - MariaDB 10 - PHP 5.6

Hors ligne

 

#25 24-11-2015 17:14:14

Bruno2
Nouveau membre
Lieu: Paris
Date d'inscription: 24-11-2015
Messages: 1
Site web

Re: Système d'ACL avec Zend Framework 2

Merci pour ce code !


Je travail dans le milieu du web. A coté j'aime pratiquer toutes sortes d'activités physique à la salle de sport smile

Hors ligne

 

Pied de page des forums

Propulsé par PunBB
© Copyright 2002–2005 Rickard Andersson
Traduction par punbb.fr

Graphisme réalisé par l'agence Rodolphe Eveilleau
Développement par Kitpages