Skip to content

datagouv/roles.data

Repository files navigation

Roles.data

Run Integration Tests Create and deploy a new release

API de gestion des droits utilisateurs pour les outils du pôle DATA. Pour en savoir plus, découvrez la présentation.

Table des matières

Installation

Prérequis

  • Python 3.13+
  • uv - Gestionnaire de dépendances
  • PostgreSQL 15.7
  • Docker & Docker Compose

Installation rapide

# Cloner le dépôt
git clone https://github.com/datagouv/roles.data.git
cd roles.data

# Installer les dépendances
uv sync

# Lancer les conteneurs de base de données
make docker_local

# Initialiser & migrer la base de données
make db_init

# Lancer l'application
make start

NB : en mode developpement rapide, l'application n’est pas dockerisée. Seuls les containers de la base de donnée le sont.

Configuration docker

Pour tester la configuration docker complète de l'application :

make docker

La commande lance les containers :

  • nginx (cf ./nginx.conf)
  • app
  • postgres-local
  • postgres-test
  • smtp-local

Ce mode permet de tester la conf nginx, le dockerfile et la logique de migration.

Cette commande est systématiquement testée dans la CI par la Github Action docker-config-test

Base de données

environnements

La variable DB_ENV est utilisée pour distinguer les différents environnements :

  • local : developpement local (seedé)
  • test : CI (seedé)
  • dev : intégration (seedé)
  • prod environnement de production

local

# lancer les DB pour les environnements local et test
docker-compose-up

# se connecter
psql -h localhost -p 5432 -U d-roles -d d-roles

# executer les migrations et la seed
make db_init

Scripts de provisionnement de la base de données

Les scripts appliqués à la base de donnée sont executés dans cet ordre :

  • schema.sql - creation du schema (uniquement les environnements local, test)
  • create.sql - création de la base de données
  • migrations/* - migrations successives
  • seed.sql - données de tests (uniquement les environnements local, test, dev)

Migrations

Ajouter un fichier db/migrations/{YYYYMMDD}_{description}.sql avec le SQL nécessaire pour la migration

Seed

Mettre a jour le fichier seeds (selon l'environnement) dans db/seeds/{environnement}/seed.sql

Tests

Les tests d'intégration tournent sur pytest. La DB postgres-test est une DB différent de la DB de dev, pré-stubbé et isolée.

# démarrer la DB
make docker_local

# test de migrations/seed
# make db_init

# lancer les tests
make test

Déploiements

L'application est déployée sur différents environnements :

Les déploiement se font via un message de commit formaté de la manière suivante : [ENV:VERSION].

# deploy on roles.dev.data.gouv.fr
make deploy_dev

# deploy on roles.data.gouv.fr
make deploy_prod

NB : ces commandes déploient la branche main uniquement.

Conventions de code

Pre-commit

uv add pre-commit
pre-commit install --install-hooks

Formatting et linting

Ce projet utilise Ruff pour le formatage et le linting :

make lint

Contribuer

Cf documentation contributeur

Comprendre l'architecture

Le parcours utilisateur

flowchart TD
    U((Utilisateur))
    U-->D
    subgraph Dinum
        subgraph Datapass:
            D[1. Nouvelle demande]-->|Validation|C[2. Habilitation]
            D-->|Refus|X[Demande refusée]
        end
        subgraph Roles.data:
            C-->|3. Création d’un groupe avec les droits associés| R[(Listes des groupes)]
        end
        subgraph ProConnect:
            L[Login]
        end
    end
    C-->|4. Envoi d’un mail a l'utilisateur l’invitant a se connecter| U
    U-->|5. Clic sur le lien contenu dans le mail|A
    A<-->|6. Se ProConnecte|L
    subgraph Fournisseur de Service
        A[Accès au service]
        A<-->|7. Récupération des droits de l’utilisateur|R
    end
Loading

Le schéma relationnel de la base de données

Le schéma ci-dessous représente la structure de la base de données (selon les migrations, ce schéma peut différer légèrement de la structure réelle) :

erDiagram
    organisations ||--o{ groups : "contains"
    organisations {
        int id PK
        char_14 siret UK "UNIQUE, format: 14 digits"
        varchar_255 name "nullable"
        timestamptz created_at
        timestamptz updated_at
    }

    users ||--o{ group_user_relations : "belongs_to"
    users {
        int id PK
        varchar_255 email UK "UNIQUE"
        varchar_255 sub_pro_connect "UNIQUE when not NULL"
        boolean is_verified "default: false"
        timestamptz created_at
        timestamptz updated_at
    }

    roles ||--o{ group_user_relations : "defines"
    roles {
        int id PK
        varchar_255 role_name UK "UNIQUE"
        boolean is_admin "default: false"
        timestamptz created_at
        timestamptz updated_at
    }

    group_user_relations {
        int id PK
        int group_id FK
        int user_id FK
        int role_id FK
        timestamptz created_at
        timestamptz updated_at
    }

    groups ||--o{ "parent_child_relations (pas utilisée)" : "parent"
    groups ||--o{ "parent_child_relations (pas utilisée)" : "child"
    groups ||--o{ group_user_relations : "has"
    groups ||--o{ group_service_provider_relations : "has"
    groups {
        int id PK
        int orga_id FK
        varchar_255 name
        timestamptz created_at
        timestamptz updated_at
    }

    "parent_child_relations (pas utilisée)" {
        int id PK
        int parent_group_id FK
        int child_group_id FK
        boolean inherit_scopes "default: false"
        timestamptz created_at
        timestamptz updated_at
    }


    service_providers ||--o{ group_service_provider_relations : "provides"
    service_providers ||--o{ service_accounts : "has"
    service_providers {
        int id PK
        varchar_255 name
        varchar_500 url "nullable, must be http(s)"
        timestamptz created_at
        timestamptz updated_at
    }

    group_service_provider_relations {
        int id PK
        int service_provider_id FK
        int group_id FK
        text scopes "default: empty string"
        text contract_description "default: empty string"
        varchar_500 contract_url "nullable, must be http(s)"
        timestamptz created_at
        timestamptz updated_at
    }

    service_accounts {
        int id PK
        int service_provider_id FK
        boolean is_active "default: false"
        varchar_255 name "UNIQUE per service_provider_id"
        text hashed_password
        timestamptz created_at
        timestamptz updated_at
    }

    audit_logs {
        int id PK
        int service_provider_id "no FK"
        int service_account_id "no FK"
        varchar_50 action_type "CREATE, UPDATE, DELETE, etc."
        varchar_50 resource_type "user, group, organisation, etc."
        int resource_id "nullable"
        jsonb new_values "nullable"
        varchar_255 acting_user_sub "nullable"
        timestamptz created_at
    }
Loading

Notes importantes :

  • Datapass (id=999) est le seul fournisseur de service hardcodé
  • group_service_provider_relations : association many-to-many entre groupes, et fournisseurs de service, qui porte les droits(scopes)
  • group_user_relations : association many-to-many entre groupes, utilisateurs et rôles
  • audit_logs n'utilise pas de clés étrangères pour conserver l'historique même après suppression de la ressource
  • parent_child_relations permet de créer une hiérarchie de groupes (la table existe mais n’est pas actuellement utilisée)

Architecture technique

graph TB
    subgraph "FastAPI Application"
        subgraph "Authentification"
            RSAuth[ResourceServer Credentials<br/> OAuth2 ProConnect token<br/>Fournisseur de service connectés a ProConnect uniquement]
            OAuth[OAuth2 Client Credentials<br/>JWT Tokens<br/>API externes]
            PCAuth[ProConnect OAuth2<br/>Session Cookies<br/>Interface Web]
            DPAuth[Datapass HMAC<br/>Signature<br/>Webhooks]
        end

        subgraph "Routers - Couche HTTP"
            RAPI[API Routers<br/>users, groups, roles, scopes]
            RWebhook[Webhook Router<br/>datapass]
            RWebUI[Web UI Routers<br/>admin, activation]
        end

        subgraph "Dependency Injection"
            CTX[Context<br/>service_provider_id<br/>service_account_id<br/>acting_user_sub]
            DBConn[db_conn<br/>swappable pour tests]
            Logger[LogsRepository]
            Repositories[Instanciation des repositories métiers]
            Services[Instanciation des services métiers]
        end


        subgraph "Services - Logique métier"
            ServicesMetiers[Utilisation des services instantiés lors de l’injection de dépendances]
        end

        subgraph "Repositories - appels externes"
            RepositoriesMetiers[Utilisation des repositories instantiés lors de l’injection de dépendances]
             end
    end

    subgraph "Base de données"
        DB[(PostgreSQL)]
    end

    OAuth -->CTX
    RSAuth -->CTX
    PCAuth -->CTX
    DPAuth -->CTX

    OAuth-->RAPI
    RSAuth-->RAPI
    PCAuth-->RWebUI
    DPAuth-->RWebhook

    CTX --> Logger
    DBConn --> Repositories
    Logger--> Repositories
    Repositories --> Services
    Services -->|Injection des instances| RAPI & RWebUI & RWebhook

    RAPI & RWebUI & RWebhook --> ServicesMetiers
    ServicesMetiers --> RepositoriesMetiers

    RepositoriesMetiers -->|Using db_conn| DB
    RepositoriesMetiers-->|log écriture using LogsRepository| DB
Loading

Patterns d'authentification :

  • OAuth2 Client Credentials : Service accounts avec JWT pour les API externes
  • ResourceServer Credentials : Utilisation de roles par API en mode resource server
  • ProConnect OAuth2 : Authentication utilisateur via OpenID Connect pour l'interface web
  • Datapass HMAC : Vérification de signature pour les webhooks entrants

Injection de dépendances :

  • Context : Extrait des credentials d'authentification, contient service_provider_id, service_account_id, acting_user_sub
  • DB Connection : Session de base de données (db_session), swappable pour les tests (permet d'utiliser une DB de test isolée)
  • LogsService : Injecté avec le context pour tracer les actions dans audit_logs

Architecture en couches :

  • Routers : Gestion des requêtes HTTP, validation des entrées, sérialisation des réponses
  • Services : Logique métier, orchestration entre repositories, gestion des emails
  • Repositories : Requêtes SQL directes, transactions, logging des actions via LogsService

Resource server

Le resource server permet a un Fournisseur de service de récupérer des resources, par API avec le seul jeton ProConnect (passé en header authorization).

Le resource server (dans notre cas, l'API de roles sur ses endpoints /resource-server) demande a ProConnect une vérification de l'access_token que pro connect a créé pour l'utilisateur, dans le cadre du fournisseur de service (eg. annuaire des entreprises).

flowchart TD
    U((Utilisateur))
    U-->Login
    Login -->|Redirect| LPC
    LPC-->|Redirect| Callback

        
    subgraph Fournisseur de Service
        Login[Page de connexion]
        Callback[Création de la session utilisateur - stockage des token ProConnect]
        Callback --> Connected[L’utilisateur est connecté et poursuis sa navigation]
        Connected--> GetRS[L’utilisateur a besoin d'une resource]

    end


    GetRS-->|Transmet l’access_token| RS[Besoin d’une resource]
    RS-->|Transmet l’access_token| II
    II -->|Retourne le sub, client_id et autres claims| RS
    RS -->|Resource demandée| GetRS
    subgraph Resource Server
        RS[API]
    end

    subgraph ProConnect
        LPC[ProConnexion]
        II[Vérification du token]
    end
Loading

Cas pratique : resource serveur sur Annuaire des Entreprises + Roles + ProConnect.

flowchart TD
    U((Utilisateur))
    U-->Login
    Login -->|Redirect| LPC
    LPC-->|Redirect| Callback

        
    subgraph "Fournisseur de Service : Annuaire des Entreprises"
        Login[Connexion]
        Callback[Callback de connexion et de création de session utilisateur]
    end


    Callback-->|"API REST - Authorization : Bearer {access_token_pro_connect}"| RS

    subgraph "Resource Server : roles.data"
        RS["GET /resource-server/group"]
        RS --> TokenValidation[Vérification du token]
        TokenValidation-->Client_IDInDB[Le client_id est associé a un service provider dans la base]
        TokenValidation-->Client_IDNotInDB[Le client_id n'existe pas dans la base]-->|403 Unauthrorized| Callback
        
        Client_IDInDB --> SubInDB[Le sub est dans la base, associé à l'email de l'utilisateur]
        Client_IDInDB --> SubNotInDB[Le sub n'est pas dans la base]
        
        SubNotInDB --> GetuserEmail[Récupération du mail utilisateur]
        UserInDB--> SubEmailPairing[Le sub est associé à l'email de l'utilisateur dans la base]
        GetuserEmail --> UserInDB[L'email de l'utilisateur a déjà été ajouté à un groupe]
        GetuserEmail --> UserNotInDB[L'email n'est pas dans la base] -->|404 Not Found| Callback
        
        GetGroupBySub[Récupération des groupes de l’utilisateur avec le sub et le service_provider]
        SubEmailPairing-->GetGroupBySub
        SubInDB-->GetGroupBySub
        
        GetGroupBySub-->Callback
    end
    
    TokenValidation-->|access_token_pro_connect| II
    II-->|client_id, sub| TokenValidation
    GetuserEmail-->|access_token_pro_connect| UI
    UI-->|email| GetuserEmail

    subgraph ProConnect
        LPC[ProConnexion]
        II[Route /introspect vérification du token]
        UI[Route /userinfo]
    end
Loading

Avec ce pattern, on fait l'économie d'un irritant majeur des Saas modernes : la validation du mail. L'ajout d'un utilisateur ce fait ainsi :

  • ajout d'un utilisateur à un groupe par son email
  • lors de la première connexion récupération du sub par l'access token et du mail par la route info
  • sauvegarde du couple mail <> sub
  • une fois le sub sauvegardé, utilisation du sub pour récuperer les groupes de l'utilisateur.

Plusieurs avantages :

  • pas de mail d'activation
  • permet de'identifier l'utilisateur uniquement avec son sub, contenu dans l'access_token
  • cinématique très light, pas d'accès en base nécessaire pour décoder l'access_token dans la route /introspect.

About

Gestion des droits des uilisateurs des outils du pôle DATA

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published

Contributors 8