Files
flowdeck/docs/V6_SSO_SAML_Enterprise_Auth.md
T
bruno 0b251649e5
FlowDeck CI / lint (push) Successful in 1m11s
FlowDeck CI / test (push) Failing after 8m32s
FlowDeck CI / docker (push) Skipped
feat: v6.2.0 Web Clipper — extension navigateur (capture article/selection/bookmark/screenshot)
- Service app/services/web_clipper.py: sanitize HTML, html->blocks, extraction article, creation page workspace-aware, rate limit 50/h, device registration
- Router app/routers/web_clipper.py: POST /api/v2/web-clipper/clip, GET /status, POST /auth/verify, GET/DELETE /devices, GET /extensions (download page), auth via session ou Bearer (api_tokens / extension_devices)
- Migration 19: extension_devices + extension_clips (+ indexes)
- Extension Manifest V3: content.js (floating button, selection), background.js (clip + contextMenus), popup.html/js, clipper.css, icons
- Settings UI: onglet Extensions (liste devices, revoke, test clip, liens download), page /extensions
- Tests: 16 tests web_clipper (sanitize, blocks, article/bookmark/selection/screenshot, bearer, rate-limit, devices, extensions page)
- Bump version 6.1.0 -> 6.2.0
2026-09-19 23:26:03 -04:00

22 KiB
Raw Blame History

V6.0.0 — SSO / SAML : Enterprise Authentication

Statut : Conception détaillée — v6.0.0 Date : 2026-09-15 Route : feat/v6-sso-saml → develop → main Dépendances : v4.0.0 Accounts & Integrations (OAuth2 Gitea/GitHub existant)


1. Vision & objectifs

Ajouter le support SSO/SAML 2.0 pour permettre aux entreprises d'intégrer FlowDeck dans leur infrastructure d'authentification existante. Les utilisateurs d'une organisation peuvent se connecter via leur fournisseur d'identité (IdP) d'entreprise sans avoir de compte local séparé.

Objectifs

Critère Cible
Protocoles SAML 2.0 (principal), OIDC (complémentaire)
Fournisseurs supportés Azure AD, Okta, Google Workspace, OneLogin, Keycloak, Auth0
Temps de connexion SSO < 3 s (redirect + callback)
Gestion des utilisateurs Auto-provisioning à la première connexion
Déconnexion SLO (Single Logout) supporté
Fallback Login local toujours disponible (admin activé/désactivable)

2. Architecture SSO/SAML

2.1 Flux SAML 2.0

┌──────────────┐         ┌──────────────┐         ┌──────────────┐
│   Utilisateur │         │  FlowDeck    │         │    IdP       │
│  (navigateur) │         │  (SP)        │         │ (Azure AD /  │
│              │         │              │         │  Okta / etc.)│
└──────┬───────┘         └──────┬───────┘         └──────┬───────┘
       │                        │                         │
       │  1. Accède à FlowDeck │                         │
       │───────────────────────►│                         │
       │                        │                         │
       │  2. Redirige vers IdP │                         │
       │◄───────────────────────│                         │
       │                        │                         │
       │  3. SSO redirect      │                         │
       │───────────────────────►│────────────────────────►│
       │                        │                         │
       │  5. Callback avec     │                         │
       │     SAMLResponse     │◄────────────────────────│
       │                        │                         │
       │  6. POST /auth/saml/callback               │
       │───────────────────────►│                         │
       │                        │                         │
       │  7. Validate SAML     │                         │
       │     → Create/Update   │                         │
       │     user → Session    │                         │
       │◄───────────────────────│                         │
       │                        │                         │
       │  8. Authentifié       │                         │
       │◄───────────────────────│                         │

2.2 Composants côté serveur

┌───────────────────────────────────────────────────────────┐
│                  FASTAPI (Python)                           │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  auth/routers/sso.py                                 │  │
│  │  ├─ GET /auth/saml/login  — initie la requête       │  │
│  │  ├─ POST /auth/saml/callback — traite la réponse     │  │
│  │  ├─ GET /auth/saml/metadata — SP metadata endpoint   │  │
│  │  ├─ GET /auth/oidc/login — flux OIDC                 │  │
│  │  ├─ POST /auth/oidc/callback                           │  │
│  │  └─ POST /auth/sso/logout — SLO                      │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  auth/providers/saml_provider.py                     │  │
│  │  ├─ SAMLProvider — wrapper python3-saml              │  │
│  │  └─ validate_saml_response()                         │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  auth/providers/oidc_provider.py                     │  │
│  │  ├─ OIDCProvider — wrapper httpx + OIDC lib          │  │
│  │  └─ validate_oidc_token()                            │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  auth/session.py — SessionManager étendu              │  │
│  │  ├─ create_sso_session()                             │  │
│  │  ├─ handle_sso_user()                                │  │
│  │  └─ enforce_sso_restriction()                        │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  services/sso_provisioning.py                        │  │
│  │  ├─ auto_provision_user()                            │  │
│  │  ├─ sync_user_attributes()                           │  │
│  │  └─ map_sso_groups_to_workspaces()                   │  │
│  └─────────────────────────────────────────────────────┘  │
└───────────────────────────────────────────────────────────┘

3. Configuration SAML

3.1 Nouveau table sso_config

CREATE TABLE sso_config (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    workspace_id INTEGER REFERENCES workspaces(id) ON DELETE CASCADE,
    -- NULL = workspace-level; specific workspace; global if NULL+global flag
    provider_type TEXT NOT NULL,
    -- 'saml', 'oidc'
    entity_id TEXT NOT NULL,
    -- SAML: Entity ID of the IdP (e.g., "http://www.microsoft.com/...")
    sso_url TEXT NOT NULL,
    -- SAML: IdP SSO URL (HTTP-POST binding)
    slo_url TEXT,
    -- SAML: IdP Single Logout URL
    x509_certificate TEXT NOT NULL,
    -- SAML: IdP signing certificate (PEM format)
    -- OIDC: issuer URL
    issuer_url TEXT,
    -- OIDC: OIDC issuer identifier
    client_id TEXT,
    -- OIDC: client_id
    client_secret TEXT,
    -- OIDC: client_secret (encrypted at rest)
    scope TEXT DEFAULT 'openid profile email',
    -- OIDC: scopes requested
    attribute_mapping TEXT NOT NULL DEFAULT '{}',
    -- JSON: {"email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", ...}
    -- Maps SAML/OIDC attributes to FlowDeck user fields
    auto_provision BOOLEAN NOT NULL DEFAULT 1,
    -- Auto-create user on first SSO login
    default_workspace_id INTEGER,
    -- Workspace to assign new SSO users
    groups_mapping TEXT DEFAULT '[]',
    -- JSON: [{"sso_group": "Admins", "workspace_role": "admin"}, ...]
    active BOOLEAN NOT NULL DEFAULT 1,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    created_by INTEGER REFERENCES users(id)
);

3.2 Mapping des attributs

Par défaut pour SAML (mappings standard OASIS) :

{
  "login": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
  "email": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
  "full_name": "urn:oasis:names:tc:SAML:attribute:displayName",
  "avatar_url": "urn:oasis:names:tc:SAML:attribute:thumbnail"
}

Par défaut pour OIDC :

{
  "login": "sub",
  "email": "email",
  "full_name": "name",
  "avatar_url": "picture"
}

3.3 Configuration .env (exemples)

# SAML — Azure AD
SSO_PROVIDER=saml
SSO_ENTITY_ID=https://sts.windows.net/{tenant-id}/
SSO_SSO_URL=https://login.microsoftonline.com/{tenant-id}/saml2
SSO_SLO_URL=https://login.microsoftonline.com/{tenant-id}/saml2/logout
SSO_X509_CERT="-----BEGIN CERTIFICATE-----\n..."
SSO_ATTRIBUTE_MAPPING={"login":"nameid","email":"email","full_name":"name"}
SSO_AUTO_PROVISION=true
SSO_DEFAULT_WORKSPACE_ID=1
SSO_GROUPS_MAPPING=[{"sso_group":"FlowDeck Admins","workspace_role":"admin"}]

# OIDC — Google Workspace
SSO_PROVIDER=oidc
SSO_ISSUER_URL=https://accounts.google.com
SSO_CLIENT_ID=xxx.apps.googleusercontent.com
SSO_CLIENT_SECRET=yyy
SSO_SCOPE=openid profile email

4. Endpoints API

4.1 Routeur app/routers/sso.py

GET  /auth/saml/login           — Redirige vers l'IdP SAML
POST /auth/saml/callback         — Traite le SAMLResponse, crée la session
GET  /auth/saml/metadata         — Retourne le SP metadata XML (pour configurer l'IdP)
POST /auth/saml/logout           — Initie le SLO (Single Logout)
GET  /auth/oidc/login            — Redirige vers l'IdP OIDC
POST /auth/oidc/callback         — Traite le token OIDC, crée la session
POST /auth/oidc/logout           — SLO OIDC
POST /api/v2/sso/config          — Créer/configurer SSO (admin, scope admin)
GET  /api/v2/sso/config          — Lire la config SSO courante
PUT  /api/v2/sso/config          — Mettre à jour la config
DELETE /api/v2/sso/config        — Supprimer la config SSO (désactiver SSO)
GET  /api/v2/sso/workspaces      — Lister les workspaces avec SSO actif
POST /api/v2/sso/sync            — Forcer la synchro des groupes/attributs

4.2 Service app/services/sso_provisioning.py

class SSOService:
    """Provisioning et gestion des utilisateurs SSO."""

    def handle_sso_login(self, sso_data: dict, provider_type: str) -> dict:
        """Traite un login SSO : trouve/crée l'utilisateur, crée la session."""
        # 1. Extraire les attributs selon le provider
        # 2. Chercher l'utilisateur par email/login SSO
        # 3. Si trouvé → mise à jour des attributs
        # 4. Si non trouvé et auto_provision → création
        # 5. Assigner le workspace par défaut ou le premier disponible
        # 6. Vérifier les groupes SSO → rôle workspace
        # 7. Créer la session

    def sync_sso_groups(self, user_id: int, sso_groups: list[str]) -> None:
        """Sync les groupes SSO vers les rôles workspace."""
        # Comparer sso_groups avec groups_mapping
        # Mettre à jour workspace_members.role

    def enforce_sso_restriction(self, workspace_id: int) -> bool:
        """Vérifie si le workspace est en mode SSO-only."""
        # Si config.sso_only = True → interdire le login local

5. Sécurité

5.1 Validation des assertions SAML

Validation Détail
Signature Vérifier la signature avec le certificat IdP
NotBefore / NotOnOrAfter Rejeter les assertions expirées
Audience Vérifier que l'audience match l'entity_id du SP
Destination Vérifier que le destination match notre callback URL
Issuer Vérifier que l'émetteur est le IdP attendu
InResponseTo Prévenir le replay attack

5.2 Protection supplémentaire

  • CSRF sur le callback : le RelayState contient un token CSRF validé
  • Rate limiting sur les endpoints SSO : max 5 tentatives/minute
  • Logging audit : chaque login SSO est enregistré (user, provider, IP, timestamp)
  • Session fixation : nouvelle session créée après chaque SSO login
  • Encryption : client_secret chiffré avec app_secret_key avant stockage

5.3 Table sso_login_history

CREATE TABLE sso_login_history (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
    provider_type TEXT NOT NULL,
    provider_name TEXT NOT NULL,
    sso_identifier TEXT,     -- email ou subject du SSO
    ip_address TEXT,
    user_agent TEXT,
    success BOOLEAN NOT NULL,
    error_message TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_sso_history_user ON sso_login_history(user_id, created_at);

6. Interface utilisateur

6.1 Login page — ajout SSO buttons

<!-- Extension de login.html — boutons SSO -->
<div class="sso-providers" x-show="ssoProviders.length > 0">
  <div class="sso-divider">ou continuez avec</div>
  <template x-for="provider in ssoProviders">
    <button @click="ssoLogin(provider.type)" class="sso-btn">
      <span x-text="provider.icon"></span>
      <span x-text="provider.name"></span>
    </button>
  </template>
</div>

6.2 Settings — Configuration SSO (admin)

Nouvelle section dans settings.html → onglet "SSO / Enterprise" :

┌──────────────────────────────────────────────────────┐
│ SSO / Enterprise Authentication                        │
│───────────────────────────────────────────────────────│
│ Provider: [▼ SAML / OIDC]                              │
│                                                        │
│ SAML Configuration:                                    │
│ ├── Entity ID: [___________________________]           │
│ ├── SSO URL:   [___________________________]           │
│ ├── SLO URL:   [___________________________]           │
│ ├── Certificate: [text area - PEM]                     │
│ └── Attribute Mapping: [JSON editor]                    │
│                                                        │
│ OIDC Configuration:                                    │
│ ├── Issuer URL:  [_________________________]           │
│ ├── Client ID:   [_________________________]           │
│ ├── Client Secret: [_______________________]           │
│ └── Scope:       [openid profile email]                │
│                                                        │
│ User Provisioning:                                     │
│ ├── Auto-provision new users [✓]                        │
│ ├── Default workspace: [dropdown]                       │
│ └── Group mapping: [table editor]                       │
│                                                        │
│ ┌─ Group Mappings ───────────────────────────────────┐ │
│ │ SSO Group          │ FlowDeck Role  │ Workspace    │ │
│ │ FlowDeck Admins    │ admin          │ Main         │ │
│ │ FlowDeck Members   │ editor         │ Main         │ │
│ └────────────────────┴────────────────┴──────────────┘ │
│                                                        │
│ [Save SSO Configuration]  [Disable SSO]                │
│ Status: ✅ Active — 12 users provisioned via SSO       │
└──────────────────────────────────────────────────────┘

6.3 Métadonnées SP pour l'IdP

Endpoint GET /auth/saml/metadata retourne le XML SAML metadata :

<EntityDescriptor entityID="https://flowdeck.local/saml/metadata">
  <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
    <KeyDescriptor use="signing">
      <KeyInfo>...</KeyInfo>
    </KeyDescriptor>
    <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
      Location="https://flowdeck.local/auth/saml/callback" />
    <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
      Location="https://flowdeck.local/auth/saml/logout" />
  </SPSSODescriptor>
</EntityDescriptor>

7. Intégration avec le système existant

7.1 Comportement selon le type de compte

SSO ACTIVÉ + local auth ENABLED:
  → Boutons SSO visibles sur la login page
  → Login local disponible (optionnel, admin-toggle)
  → Les utilisateurs SSO et locaux coexistent

SSO ACTIVÉ + local auth DISABLED:
  → Seuls les boutons SSO sont affichés
  → Login local caché / 403 pour /auth/local-login
  → Admin du workspace peut garder le sien

SSO ACTIVÉ + compte local existant:
  → Email match → fusion automatique des sessions
  → Email ne match pas → login local séparé (deux comptes)

7.2 Interaction avec le PermissionManager

Le PermissionManager existant (app/services/permission_manager.py) est étendu :

class PermissionManager:
    # Méthodes existantes conservées
    # + Nouvelles méthodes SSO :

    def is_sso_only_workspace(self, workspace_id: int) -> bool:
        """Vérifie si le workspace exige SSO."""

    def get_sso_roles(self, user_id: int, workspace_id: int) -> list[str]:
        """Retourne les rôles issus des groupes SSO."""

    def sync_sso_permissions(self, user_id: int, sso_groups: list[str], workspace_id: int):
        """Met à jour les rôles basés sur les groupes SSO."""

Le cookie flowdeck_session existant est utilisé tel quel. Une colonne supplémentaire dans la table users distingue les utilisateurs SSO :

ALTER TABLE users ADD COLUMN auth_method TEXT DEFAULT 'local';
-- Valeurs: 'local', 'gitea', 'github', 'saml', 'oidc'

8. Tests

Test Description Outil
SAML login flow Redirection complète IdP → callback → session Playwright + mock IdP
SAML metadata Endpoint retourne XML valide Unit test
SAML assertion validation Signature invalide → rejet Unit test
SAML replay attack Même assertion utilisée 2× → rejet Unit test
OIDC login flow Authorization code → token → session Playwright + mock OIDC
Auto-provision Nouveau user SSO → création auto Unit test
Group mapping Groupes SSO → rôles workspace Unit test
SSO disable Désactiver SSO → tous les users restants gardent accès Unit test
SLO Logout → redirection IdP SLO Playwright
Mixed auth Login SSO + login local dans le même workspace Integration test

9. Checklist d'implémentation

  1. auth/providers/saml_provider.py — wrapper python3-saml ou pysaml2
  2. auth/providers/oidc_provider.py — wrapper OIDC (authlib ou httpx)
  3. auth/routers/sso.py — endpoints SSO/OIDC
  4. Migration sso_config + sso_login_history + colonne auth_method sur users
  5. services/sso_provisioning.py — auto-provision + group mapping
  6. Extension settings.html — UI admin SSO
  7. Extension login.html — boutons SSO
  8. SP metadata endpoint (/auth/saml/metadata)
  9. Security — validation assertions, rate limiting, audit log
  10. Tests — tous les scénarios SSO
  11. Documentation utilisateur — /help section SSO setup
  12. Dépendance — python3-saml ou pysaml2, authlib dans requirements.txt

10. Dépendances Python

# requirements.txt additions pour v6.0.0 SSO
python3-saml>=1.16.0       # SAML 2.0 SP
authlib>=1.3.0             # OIDC client
cryptography>=42.0         # Signature/encryption
xmlsec>=1.3.0              # XML signature validation (optionnel)

11. Références