Projekt BISO 3 - Handbuch
[inhalt]

OIDC Auto-Provisionierung (Single-Sign-On)

English Version

Dieser Eintrag beschreibt das allgemeine OIDC-Auto-Provisioning von BISO. Die kantonsspezifische Genfer Umsetzung (GINA) inkl. der vollständigen Konfiguration ist unter Kanton Genf: OIDC/GINA-Benutzerprovisionierung dokumentiert.

Zweck

Beim Single-Sign-On (SSO) authentifiziert ein externer Identity-Provider (IdP) den Benutzer. BISO soll den Benutzer dann automatisch anlegen und bei jedem Login synchronisieren (Identität, Gruppen, Rollen/Rechte), sodass kein separater BISO-Login-Screen und keine manuelle Benutzerpflege nötig sind. Das Verhalten ist als allgemeines Framework implementiert und wird pro Kunde über eine Delegate-Klasse konkretisiert.

Ablauf

  1. Apache mod_auth_openidc beendet den OIDC-Handshake (siehe docker/dev/apache-000-oidc.conf) und übergibt die Claims des Benutzers als $_SERVER-Variablen (OIDC_CLAIM_*, nach interner Weiterleitung auch REDIRECT_OIDC_CLAIM_*) sowie das Access-Token (OIDC_access_token).
  2. Beim ersten BISO-Request ohne Session ruft AuthService::authCheck() die Methode remoteUserAuth() auf (sobald oidc.auto_provision = true – oder remote_user_auth = true für den separaten REMOTE_USER-/Kerberos-Pfad).
  3. Ist oidc.auto_provision = true, dann:
    • wird ein OidcClaims-Objekt aus $_SERVER gebaut,
    • liefert der DI-Container die OidcUserProvisioner-Instanz — der BisoDIProvider wählt anhand des Params kunde die kundenspezifische Subklasse (z.B. GeOidcUserProvisioner), sonst die Basisklasse,
    • ruft provision($claims) auf. Diese Methode reichert – falls oidc.userinfo_endpoint gesetzt ist – die Claims zuerst über den userinfo-Endpoint an (Rollen + saubere Identität), findet oder erstellt danach Login + Benutzer und bildet die Rollen auf BISO-Gruppen/Flags ab,
    • etabliert die Session über setupSession() (bzw. setupSessionPartial(), wenn der Login mehrere Benutzer/Seats hat – dann zeigt die SPA das bestehende Mehrfach-Benutzer-Auswahlfenster).
  4. Die SPA lädt anschliessend mit gefüllter SESSION.benutzer direkt ins Hauptpanel – ohne separaten Login-Screen.

Kernklassen und Hooks

Klasse Aufgabe
backend/logic/OidcClaims.php Normalisiert die OIDC_CLAIM_*-Variablen; get()/getList(); accessToken() liefert das Bearer-Token; merge() fügt userinfo-Claims hinzu.
backend/logic/OidcUserInfoClient.php Kleiner Guzzle-Client: GET userinfo mit Authorization: Bearer <token>, JSON-Parsing.
backend/logic/OidcUserProvisioner.php Basis-Delegate mit dem generischen Find-or-Create-/Sync-Ablauf und überschreibbaren Hooks.
backend/service/AuthService.php remoteUserAuth() – Einstiegspunkt, Session-Aufbau.

Überschreibbare Hooks auf OidcUserProvisioner (pro Kunde):

Das "Seat"-Konzept (ein Benutzer je Rolle/Service)

Ein Login kann in BISO mehrere Benutzer besitzen. Das Framework nutzt dies für das Konzept eines Seats: pro Rolle/Service kann ein eigener Benutzer mit eigener Institution, eigenen Gruppen und Default-Werten angelegt werden.

Rollen über den userinfo-Endpoint

Manche IdP liefern die Rollen nicht in den Front-Channel-Claims. In diesem Fall holt enrichClaimsFromUserInfo() sie per HTTP:

Fehler-Policy: Schlägt der userinfo-Aufruf fehl, bleibt ein bestehender Benutzer unangetastet (Login gelingt mit den aktuellen Rechten – nie „Rechte wegwischen" bei einer Störung), während ein neuer Login abgelehnt wird (Rollen nicht bestimmbar).

Synchronisation und Deaktivierung

Bei jedem Login (wenn oidc.sync_existing = true):

oidc.sync_existing = false bedeutet „nur anlegen": bestehende Benutzer werden nach dem ersten Login nicht mehr verändert. Neue Benutzer werden immer voll provisioniert.

Erweiterung pro Kunde

  1. Param::set('kunde', 'xy').
  2. backend/logic/XyOidcUserProvisioner.php erstellen:
    class XyOidcUserProvisioner extends OidcUserProvisioner {
        protected function mapRolesToGroups(OidcClaims $claims): array { /* ... */ }
        protected function mapRolesToFlags(OidcClaims $claims): array { /* ... */ }
    }
    
  3. Im BisoDIProvider einen case für den Kunden ergänzen:
    case Param::KUNDE_XY:
        return new XyOidcUserProvisioner();
    
  4. In der config.php dieser Installation oidc.auto_provision = true (und ggf. oidc.userinfo_endpoint) setzen.

Ein vollständiges Beispiel ist die Genfer Umsetzung, siehe Kanton Genf: OIDC/GINA-Benutzerprovisionierung.

Konfiguration (allgemein)

In der config.php der jeweiligen Installation:

Config::set('oidc.auto_provision', true);  // aktiviert OIDC-Auto-Provisioning (Find-or-Create aus den Claims)
Config::set('oidc.sync_existing', true);   // bestehende Benutzer bei jedem Login re-synchronisieren
Config::set('oidc.debug_claims', false);   // normalisierte Claims protokollieren (nur Dev/Test)
Config::set('oidc.roles_claim', 'roles');  // Name des Claims/userinfo-Felds mit den Rollen

// Optional: Rollen aus einem userinfo-Endpoint holen (wenn nicht in den Claims):
Config::set('oidc.userinfo_endpoint', ''); // z.B. 'https://.../oauth2/userinfo'; leer = deaktiviert

oidc.auto_provision = true genügt, um den Provisioning-Pfad zu aktivieren. remote_user_auth gehört zu einem separaten SSO-Mechanismus (REMOTE_USER/Kerberos über Config remote_user) und ist für OIDC nicht erforderlich.

Der aktive Kunde wird wie üblich über Param::set('kunde', '<kunde>') gewählt; darüber liefert der BisoDIProvider die passende OidcUserProvisioner-Instanz.

Logging

Das Provisioning protokolliert in einen eigenen oidc-Log-Kanal (Start, angelegte Login/Benutzer, Sync vs. Create-only, gemappte Gruppen/Flags, Fehler, etablierte Session). Eigene Datei über einen oidc-Eintrag in gaia.logging (config.php):

'oidc' => [
    'type' => 'file',
    'level' => 'DEBUG',
    'logfile' => Config::get('tmpdir') . "/biso-oidc.log",
],

Fehlt der oidc-Kanal, landen die Meldungen automatisch im main-Log.

Siehe auch: Login, Benutzer, Berater, Institutionen, Mitarbeiter und Prüfen von Rollen.