--- title: "ADR-0005: AAI → Waldur User Synchronization" --- # ADR-0005: AAI → Waldur User Synchronization **Status:** Accepted **Date:** 2026-05-13 **Deciders:** TBD **Relates to:** [ADR-0003 (Waldur Proposal Workflow)](/adr/0003-waldur-proposal-workflow) — users must exist in Waldur before they can be added to projects and orders. ## Context Users register and authenticate via the Pharos AAI service (Keycloak-based OIDC provider at `staging.aai.pharos.grnet.gr`). Waldur (`dev.waldur.pharos.grnet.gr`) is used for resource allocation — managing projects, orders, and memberships. Currently, Waldur has no awareness of AAI-registered users until they interact with Waldur directly. This means a Waldur admin **cannot** add a user to a project or order unless that user already exists in Waldur. We need a mechanism to propagate AAI user registrations into Waldur so that users are available for membership assignment immediately. ### Requirements 1. When a new user registers on the AAI service, they should be created in Waldur automatically. 2. Waldur admins must be able to add these users as members to projects and orders. 3. User attributes (name, email, organization, affiliations) should be kept in sync. 4. The mechanism should be idempotent — re-syncing an existing user must not cause errors or data loss. ## Decision Drivers - **Operational simplicity** — minimize new moving parts. - **Latency** — how quickly after AAI registration does the user appear in Waldur? - **Reliability** — what happens if Waldur is temporarily unavailable? - **Maintainability** — leverage existing infrastructure where possible. ## Considered Options ### Option A: Waldur Identity Bridge API (push-based) Waldur exposes `POST /api/auth/identity-bridge/` — a dedicated endpoint for external identity sources to push user records. It accepts username, email, name, organization, and other attributes, and performs idempotent create-or-update. The caller needs staff or "identity manager" role with the source registered in `managed_isds`. A sync service would: 1. Query the AAI Keycloak Admin API for users (periodically or via event listener). 2. Push each user to Waldur via the Identity Bridge endpoint. **Pros:** - Purpose-built Waldur API for this exact use case. - Idempotent — safe to re-run. - Attribute source tracking built in (`attribute_sources` per field). - Users appear in Waldur without needing to log into Waldur. **Cons:** - Requires Keycloak Admin API access or an event listener on the AAI side. - Requires an identity manager account in Waldur with the source in `managed_isds`. - We must build and operate the sync service. ### Option B: Waldur OIDC provider integration (login-triggered) Configure Waldur to use the AAI as an OIDC provider directly. Waldur's `waldur_auth_social` module auto-creates users on first OIDC login via `create_or_update_oauth_user()`. **Pros:** - No custom sync service needed. - Users are created with full OIDC attribute mapping. **Cons:** - Users only exist in Waldur **after they log into Waldur** — does not solve the core problem. - Waldur admin cannot pre-assign users to projects before their first login. ### Option C: Remote eduTEAMS-style pull (periodic) Waldur has built-in background tasks (`pull_remote_eduteams_users`) that periodically fetch user data from a remote userinfo API. This could potentially be pointed at the AAI. **Pros:** - Built into Waldur — no external service to build. - Periodic sync with configurable interval. **Cons:** - Designed specifically for eduTEAMS API shape — may not match the AAI's Keycloak API. - Requires the AAI to expose a compatible userinfo endpoint per user. - Less control over sync logic and error handling. ### Option D: Sync-on-login via Themelio auth callback (Recommended) Themelio is already in the authentication loop — the NextAuth `signIn` callback fires on every AAI login. This is the natural trigger for Waldur sync, requiring no separate sync service. **Flow:** 1. A project lead submits a project request in Themelio (project name, resources, member emails). 2. Themelio emails the Waldur admin with the project name and member list. 3. The Waldur admin creates the project in Waldur and adds a pending invitation for each member email. 4. Waldur sends a customised invitation email to each member linking to Themelio (not Waldur's UI). 5. The member clicks the link, authenticates via AAI (Keycloak), and lands on Themelio. 6. Inside the NextAuth `signIn` callback, Themelio performs three steps server-side using a Waldur staff token: - `POST /api/auth/identity-bridge/` — creates or updates the user in Waldur (idempotent). - `GET /api/invitations/?email={email}&state=pending` — retrieves matching pending invitations. - `POST /api/invitations/{uuid}/accept/` — accepts each invitation on the user's behalf. 7. The user is now a Waldur project member. They never see or log into Waldur. **Pros:** - No separate sync service or Kubernetes CronJob to build and operate. - Real-time — membership is assigned at first login, not on a polling interval. - Themelio already owns user lifecycle (GitLab provisioning); Waldur sync fits the same pattern. - Identity Bridge call is idempotent — safe to re-run on every login. - Users never interact with Waldur directly. **Cons:** - Waldur admin must still manually create the project and invitations (accepted v1 trade-off). - Membership is only assigned when the user first logs into Themelio — not at invitation time. - Requires a Waldur staff token stored server-side in Themelio; in Ansible this token is generated by `waldur-offering` and stored in `Secret/themelio-waldur-credentials`. - Step 2 notification trigger depends on an upstream proposal/approval flow that is not yet designed (see Open Questions). ## Related Problem: Inviting Unregistered Users The sync mechanism described above covers users who have **already registered** on the AAI. A related problem is: how does an admin invite someone who **hasn't registered anywhere yet** to a Waldur project or order? ### The chicken-and-egg problem The identity chain is: **AAI (Keycloak) → Themelio web → GitLab → Waldur**. AAI is the source of truth for identity. If someone is invited via Waldur but hasn't registered on AAI, they can't actually log into anything — they'd exist in Waldur but nowhere else, which is a broken state. ### Proposed invitation flow ``` Waldur admin invites user by email → User receives email with link to Themelio (not Waldur) → Themelio redirects to AAI for registration / login → User registers on AAI (Keycloak) → Redirected back to Themelio → User accepts Terms & Conditions on Themelio → Account provisioned (GitLab + Waldur via AAI→Waldur sync) → Waldur matches pending invitation by email → Membership auto-assigned ``` ### Where does invitation intent live? The invite carries intent: "this person should become a member of project X when they show up." Two options for where that intent is stored: **Invitation Option 1: Waldur holds the pending invite.** Admin creates an email-based invitation in Waldur (`InvitationViewSet`). After the user registers via AAI and gets synced to Waldur, Waldur matches the pending invitation by email and auto-assigns membership. Waldur already has this invitation infrastructure. - Pro: No invitation tracking to build — reuse Waldur's existing system. - Pro: Waldur is already the system that manages project membership. - Con: The invitation email must link to Themelio (not Waldur's default), which may require Waldur configuration or a redirect layer. - **Unknown**: Does Waldur's invitation system auto-resolve pending invites when a user is created via the Identity Bridge API, or only when the user logs in via OIDC? **Invitation Option 2: Themelio holds the pending invite.** We build invitation tracking in Themelio. After registration + ToC acceptance, Themelio calls the Waldur API to assign membership. - Pro: Full control over the invitation UX and flow. - Pro: Invitation email naturally links to Themelio. - Con: Duplicates invitation logic that Waldur already has. - Con: More surface area to build and maintain. ### Key constraint AAI must be the **first registration step** regardless of which option is chosen. The ToC acceptance should happen on Themelio since that's the user-facing product. The invitation mechanism (Waldur or Themelio) determines where the pending membership intent is stored and resolved. ## Open Questions (to be answered by engineers) > **These are dark areas that must be resolved before a decision can be made.** ### Q1: AAI Keycloak Admin API access Can we obtain service-account credentials for the Keycloak Admin API at `staging.aai.pharos.grnet.gr`? Specifically, can we call `GET /admin/realms/{realm}/users` to list registered users and their attributes? What scopes/roles are required, and who provisions them? ### Q2: Keycloak event listeners / webhooks Does the AAI Keycloak instance support registering custom event listeners (SPI) or webhook-based notifications for user registration events? If so, what is the delivery mechanism (HTTP callback, Kafka, etc.) and who manages the listener configuration? ### Q3: Waldur Identity Manager setup The Identity Bridge API requires a Waldur account with staff or "identity manager" role and the source registered in `managed_isds`. Has this been configured on `dev.waldur.pharos.grnet.gr`? What source identifier should we use (e.g., `isd:pharos-aai`)? ### Q4: User attribute mapping Which AAI (Keycloak) user attributes should be propagated to Waldur? At minimum: username, email, first/last name. Are there additional attributes (organization, affiliations, `eduperson_assurance`, `schacPersonalUniqueID`) that Waldur needs for project/order membership decisions? ### Q5: Sync hosting and operational ownership Where should the sync process run? - Extend `gitlab-root-actor` (already does Waldur ↔ GitLab sync) - New standalone service / Kubernetes CronJob - Waldur-side configuration (if Option C is viable) Who owns and monitors the sync process in production? ### Q6: Failure and retry semantics If Waldur is temporarily unavailable during a sync run, what is the expected behavior? Should we queue failed pushes and retry, or is it acceptable to catch up on the next periodic run? Is there an SLA for how quickly a new AAI user must be visible in Waldur? ### Q7: User deactivation / deletion If a user is deactivated or deleted in the AAI, should that be propagated to Waldur? What is the expected behavior — soft-disable, hard-delete, or no action? ### Q8: Waldur invitation auto-resolution via Identity Bridge **Resolved:** Waldur does **not** auto-resolve pending invitations on Identity Bridge user creation. Invitations are only accepted when the invitee logs in to Waldur via OIDC. There is no post-save signal handler that triggers acceptance on user creation. **Consequence:** Themelio must accept invitations programmatically using a Waldur staff token (`POST /api/invitations/{uuid}/accept/`) inside the NextAuth `signIn` callback. In Ansible this token is generated by `waldur-offering` and stored in `Secret/themelio-waldur-credentials`. This is confirmed viable — a staff account can accept invitations on the invitee's behalf. See Option D. ### Q9: Waldur invitation email customization Can Waldur's invitation emails be configured to link to Themelio (e.g., `https://themelio.example.com/invite?token=...`) instead of Waldur's own UI? If not, do we need a redirect layer or a custom invitation email template? ### Q10: ToC acceptance flow Where should Terms & Conditions acceptance be recorded — AAI (Keycloak), Themelio, or both? Is there an existing ToC acceptance mechanism in the AAI registration flow, or does this need to be built into Themelio's onboarding? ### Q11: Step 2 notification trigger What upstream event notifies the Waldur admin to create the project and invitations? Is there an existing proposal/approval flow (see [ADR-0003](/adr/0003-waldur-proposal-workflow)), or does the notification fire immediately on project form submission? Who approves resource allocation before the admin is notified? ### Q12: Waldur invitation expiry What is the default expiry window for Waldur pending invitations? If a user takes longer than that to log into Themelio, the invitation will have expired and step 6 of Option D will find nothing to accept. Who is responsible for re-sending expired invitations, and should Themelio surface a warning to the user in this case? ## Decision Option D (sync-on-login via Themelio auth callback) is the recommended approach for v1. It requires no new services, leverages the existing NextAuth `signIn` callback, and keeps Waldur invisible to end users. The Waldur admin creation step (step 3) is an accepted manual trade-off for v1. Final decision pending resolution of Q1, Q3, Q11, and Q12. ## Consequences TBD — will be filled in once a decision is made.