Projekt BISO 3 - Handbuch
[inhalt]

Canton of Geneva: OIDC/GINA User Provisioning

German Version

This page describes the Geneva implementation of the OIDC auto-provisioning. The general framework is documented at OIDC Auto-Provisioning (Single Sign-On). Implemented in backend/logic/GeOidcUserProvisioner.php.

Context

The identity provider GINA authenticates the Geneva users. However, GINA delivers to BISO only the list of application roles (no fine-grained permissions) – and not in the front-channel claims, but via a userinfo endpoint. BISO fetches the roles there and maps them to fully configured BISO users.

Identity and Roles

The login/userinfo provides:

The roles are mapped as full strings (including the DIP.BISO. prefix); the config values accordingly carry the full role names. The generic role DIP.BISO.UTILISATEUR is ignored (on its own it grants no access).

Example of a userinfo response:

{
  "sub": "BRESSONJ",
  "firstName": "Julien",
  "lastName": "Bresson",
  "email": "julien.bresson@etat.ge.ch",
  "roles": [
    "DIP.BISO.ADMINISTRATEUR-APPLICATION",
    "DIP.BISO.GESTIONNAIRE-SECRETAIRE",
    "DIP.BISO.UTILISATEUR"
  ]
}

Field Mapping (GINA → User)

The French form labels of the BISO user form correspond to the following user fields (in Geneva, Regionalstelle is labeled Prestation):

Form Label (FR) User Field
Personne person_id (linked Person: last name/first name/e-mail)
Prestation institution_id (an institution)
Groupe comptes rendus protokoll_gruppe_id (a group)
Groupe Propriétaire owner_group_id (a group)
Autorisation du groupe = Écrire owner_group_initial_mode = 6 (write)
Autorisation d'accès par d'autres = Lire world_initial_mode = 4 (read)
Langue = Français sprache_id = French
Conseiller/ère ist_berater
Secrétariat sekretariat
Chef d'atelier workshopleiter
Planificateur d'atelier workshopplaner
Modifier des modules et des prestations produkte
Modifier des personnes et des institutions stammdaten
Montrer Meta-Infos show_meta_infos
Créer des utilisateurs darf_benutzer_anlegen

Profiles

Common to all profiles: language = French, owner_group_initial_mode = 6 (write), world_initial_mode = 4 (read), linked Person. In addition, for each seat the person is recorded as an active employee (Mitarbeiter) of the prestation/institution (find-or-create, deactivated ones are reactivated) — the person field in the user form only shows employees of the institution.

Profile (Application Role) Users/Seats Groups Roles/Permissions Prestation / Groupe comptes rendus / Groupe Propriétaire
ADMINISTRATEUR-APPLICATION 1 all groups all role flags; all permissions except darf_benutzer_anlegen OSP
GESTIONNAIRE-SECRETAIRE 1 OSP + the 8 service groups Conseiller, Secrétariat, Chef d'atelier, Planificateur; permissions produkte/stammdaten/show_meta_infos OSP
GESTIONNAIRE-CONSEILLER one user per service OSP + service group Conseiller, Chef d'atelier, Planificateur (no Secrétariat); permissions produkte/stammdaten/show_meta_infos the respective service

Combination: The profiles are additive — one OSP seat plus one Conseiller seat per service. ADMINISTRATEUR-APPLICATION and GESTIONNAIRE-SECRETAIRE share the OSP seat (both point to the OSP institution; the admin profile is the superset and wins there).

Multiple Services: One User per Service

A GESTIONNAIRE-CONSEILLER with several PRESTATION-* roles gets a dedicated user (seat) per service on the same login. On login, BISO then shows the existing multi-user selection window in which the user picks the desired service (distinguished by the institution/prestation). See Seat Concept.

Synchronization and Deactivation

On every login (when oidc.sync_existing = true):

Configuration

1. Apache (mod_auth_openidc)

mod_auth_openidc must perform the handshake and pass the access token on to PHP (so that OIDC_access_token is available for the userinfo call):

OIDCPassClaimsAs both
OIDCPassAccessToken On

Template: docker/dev/apache-000-oidc.conf.

2. config.php – Basics

Config::set('oidc.auto_provision', true);  // enables the provisioning (remote_user_auth not needed)
Config::set('oidc.sync_existing', true);
Config::set('oidc.userinfo_endpoint', 'https://ssorec.geneveid.ch/ginasso/oauth2/userinfo');
Config::set('oidc.roles_claim', 'roles');

3. config.php – Group/Institution Mapping (by ID)

Roles are mapped to BISO groups and institutions by ID. The IDs come from the GE production database. Every service and OSP must exist there both as a group and as an institution.

The IDs required for this are produced by the following two queries. The anzahl_benutzer column shows how many users reference the entry — this helps to identify the entries actually in use. Both queries are also available as runnable scripts in the sql/ folder.

Groups (for osp_gruppe_id and the per-service gruppe_id):

SELECT g.id, g.name, count(bg.benutzer_id) AS anzahl_benutzer
FROM gruppe g
    LEFT JOIN benutzer_gruppe bg ON bg.gruppe_id = g.id
GROUP BY g.id, g.name
ORDER BY g.name;

Institutions / prestations (for osp_institution_id and the per-service institution_id; the ist_bsb filter narrows down to the OSP prestations — without it, all schools appear as well):

SELECT i.id, i.name, i.aktiv,
       count(b.id) FILTER (WHERE b.deleted IS NULL AND b.aktiv) AS anzahl_benutzer
FROM v_institution i
    LEFT JOIN benutzer b ON b.institution_id = i.id
WHERE i.ist_bsb
GROUP BY i.id, i.name, i.aktiv
ORDER BY i.name;
Config::set('oidc.ge.osp_gruppe_id', 0);       // OSP: group
Config::set('oidc.ge.osp_institution_id', 0);  // OSP: institution (prestation)

Config::set('oidc.ge.services', [
    // '<full ROLE, UPPERCASE>' => ['gruppe_id' => <int>, 'institution_id' => <int>],
    'DIP.BISO.PRESTATION-CYCLE-D-ORIENTATION' => ['gruppe_id' => 0, 'institution_id' => 0],
    'DIP.BISO.PRESTATION-ACCESSII'            => ['gruppe_id' => 0, 'institution_id' => 0],
    'DIP.BISO.PRESTATION-PREQUALIFIANT'       => ['gruppe_id' => 0, 'institution_id' => 0],
    'DIP.BISO.PRESTATION-CFP-COMMERCE'        => ['gruppe_id' => 0, 'institution_id' => 0],
    'DIP.BISO.PRESTATION-ECG'                 => ['gruppe_id' => 0, 'institution_id' => 0],
    'DIP.BISO.PRESTATION-COLLEGE'             => ['gruppe_id' => 0, 'institution_id' => 0],
    'DIP.BISO.ENSEIGNEMENT-SPECIALISE'        => ['gruppe_id' => 0, 'institution_id' => 0],
    'DIP.BISO.PRESTATION-ETUDIANTS'           => ['gruppe_id' => 0, 'institution_id' => 0],
    'DIP.BISO.PRESTATION-TOUT-PUBLIC'         => ['gruppe_id' => 0, 'institution_id' => 0],
    'DIP.BISO.PRESTATION-VIAMIA'              => ['gruppe_id' => 0, 'institution_id' => 0],
    'DIP.BISO.PRESTATION-CHAMP-DOLLON'        => ['gruppe_id' => 0, 'institution_id' => 0],
    'DIP.BISO.PRESTATION-FEMME-ET-EMPLOI'     => ['gruppe_id' => 0, 'institution_id' => 0],
    'DIP.BISO.PRESTATION-TREMPLIN-JEUNES'     => ['gruppe_id' => 0, 'institution_id' => 0],
    'DIP.BISO.PRESTATION-EVASCOL'             => ['gruppe_id' => 0, 'institution_id' => 0],
    'DIP.BISO.PRESTATION-PRO-APPRENTIS'       => ['gruppe_id' => 0, 'institution_id' => 0],
]);

// The 8 service roles whose groups the SECRETAIRE profile is a member of:
Config::set('oidc.ge.secretaire_service_roles', [
    'DIP.BISO.PRESTATION-ETUDIANTS', 'DIP.BISO.PRESTATION-TOUT-PUBLIC', 'DIP.BISO.PRESTATION-VIAMIA',
    'DIP.BISO.PRESTATION-CHAMP-DOLLON', 'DIP.BISO.PRESTATION-FEMME-ET-EMPLOI',
    'DIP.BISO.PRESTATION-TREMPLIN-JEUNES', 'DIP.BISO.PRESTATION-EVASCOL', 'DIP.BISO.PRESTATION-PRO-APPRENTIS',
]);

4. Log Channel

Dedicated oidc log channel as described in the general framework, see OIDC Auto-Provisioning – Logging.

Importing Users from an Excel List

Users can be created up front from the GINA role list, without every person having to log in once first:

php backend/tools/ge_oidc_user_import.php <file.xlsx>            # dry run (default)
php backend/tools/ge_oidc_user_import.php <file.xlsx> --do-it    # writes

Login name: by default the column Identifiant becomes login.benutzername; use --login-column=<header> to change it. What matters is that the column carries the same value GINA sends as sub — and that is the Identifiant (BRESSONJ), not the email address. If the column misses the sub, the imported user is not recognized at the first SSO login and a second account is created. To verify, the dry run reports for both the chosen and the alternative column how many values already exist as logins; the column with the overlap is the right one.

Letter case: nothing in the login path normalizes it — extractUsername() only trims, findLogin() compares exactly, and the unique index on login.benutzername is case-sensitive. The login name must therefore match the IdP's sub character for character. The script warns, naming the row, when a value differs from an existing login only in case — that would create a second account. Nothing is corrected automatically: letter case belongs in the source sheet.

The script does not map the roles itself: it builds the claims GINA would deliver from each row and hands them to the existing GeOidcUserProvisioner. Seats, groups, flags and the linked Person / Mitarbeiter are therefore created exactly as they would be by an SSO login. The userinfo call is switched off for the run (the roles come from the file) and oidc.sync_existing is forced on.

Expected file layout (sheet Collaborateurs-GINA-BISO): a band row with the cells GINA and — further right — BISO, below it a header row with Identifiant, Nom, Prénom, Adresse email. Every column between GINA and BISO is a role and must match a profile role or a key of oidc.ge.services. The marker is x. The optional column Nb de sous-profils attendus dans BISO is checked against the computed seat count.

Before writing anything the script validates the whole file and aborts on any problem: unknown role column (with a suggestion), duplicate Identifiant, missing identity, row without a profile role. A service configured but missing a column only yields a warning.

For every person the run reports the detected profile and service roles, the resulting Prestations (id and name) and whether an OSP seat is created. An OSP seat comes only from ADMINISTRATEUR-APPLICATION or GESTIONNAIRE-SECRETAIRE; GESTIONNAIRE-CONSEILLER grants the OSP group, but no OSP Prestation.

Because the import has to produce the same result as a real GINA login, the script also warns when a row carries ADMINISTRATEUR-APPLICATION but not all service columns are marked. GINA assigns all service roles to an administrator; if the same row also carries GESTIONNAIRE-CONSEILLER, the first SSO login therefore creates one counsellor account per service. The warning names both numbers.

The dry run wraps everything in one transaction that is rolled back at the end. With --do-it each person is committed individually — a failure on one person leaves the already processed ones in place. A second run creates nothing new, it only re-synchronizes. Users not in the list are never touched.

This requires a complete configuration: oidc.ge.services must contain all services, and groups and institutions must exist (see sql/).

GINA Role List

Profile roles: ADMINISTRATEUR-APPLICATION, GESTIONNAIRE-SECRETAIRE, GESTIONNAIRE-CONSEILLER (each with the prefix DIP.BISO.).

Service roles (PRESTATION-*): PRESTATION-CYCLE-D-ORIENTATION, PRESTATION-ACCESSII, PRESTATION-PREQUALIFIANT, PRESTATION-CFP-COMMERCE, PRESTATION-ECG, PRESTATION-COLLEGE, ENSEIGNEMENT-SPECIALISE, PRESTATION-ETUDIANTS, PRESTATION-TOUT-PUBLIC, PRESTATION-VIAMIA, PRESTATION-CHAMP-DOLLON, PRESTATION-FEMME-ET-EMPLOI, PRESTATION-TREMPLIN-JEUNES, PRESTATION-EVASCOL, PRESTATION-PRO-APPRENTIS.

Troubleshooting