Implement the manager authentication plugin interface
- Dominant language
- Python
- Stars
- 670
- Forks
- 183
- Avg merge
- 15h 13m
- Merged PRs (30d)
- 368
Description
## Problem
The manager's authentication plugins — hook and webapp alike — are handed the manager's internals and drive them with raw SQL or ORM sessions. There is no plugin-facing contract, so every authentication integration reimplements manager behaviour and drifts as the manager changes.
Confirmed by a survey of the manager hook and webapp plugin sources:
- `RootContext` no longer exists in the manager, yet 15 plugin files still import it and read `request.app["_root.context"]`.
- Hook plugins always receive `context=None` because `PluginsInput.init_context` is never populated.
- **RBAC bypass**: four authentication integrations insert into `users`, `groups` and `association_groups_users` directly, so the accounts they provision on first sign-in are absent from the RBAC graph.
- **Broken secrets**: those same integrations insert a plaintext `secret_key`, but `keypairs.secret_key` is a `SecretColumn`.
- **Diverging security policy**: the password hashing parameters are handed to plugins instead of being applied by the manager, and integrations hardcode conflicting values — one hashes on user creation with a different algorithm than it verifies with.
- **Split plugin configuration**: a plugin's hook and webapp halves are given configuration from separate etcd namespaces, so each half reads the raw etcd tree to reach its sibling's settings. In one case the entry-point name and the configuration key diverge, so the injected configuration is never used at all.
- **Resource duplication**: one integration opens its own DB pool, etcd client and Valkey client in `init()`, because the manager exposes none of them.
- **Silent breakage**: webapp handlers still read the authenticated caller with dictionary subscripts, but the manager now populates a frozen dataclass, so those handlers raise at runtime.
## Goal
Authentication plugins operate through versioned interfaces only. No manager context object, no DB handle, no direct model or repository import from plugin code.
## Authorize flow
The manager owns the login flow end to end. A plugin participates at exactly two points: it says which account a request is for, and it is told the outcome.
1. The manager turns the incoming request into a transport-neutral request value and hands it to the plugin.
1. The plugin verifies whatever credential the request carries and answers with the key identifying the account. It may instead decline, meaning the request carries no credential it handles — the manager then falls back to password authentication.
1. The manager resolves that key to an account and applies the account status checks. The plugin never queries the database.
1. The manager reports the outcome back to the plugin. On a miss the plugin may provision the account, and the manager retries the lookup a bounded number of times.
1. Everything downstream — RBAC registration, password hashing, secret encryption, session issuance — stays on the manager side and is never exposed to the plugin.
The security responsibility sits in step 2. The manager trusts the key it is given and does not verify the credential itself, so the contract has to state this even though the step reads like a lookup.
**At most one plugin** may be loaded in the group. This is a new entry-point group rather than a change to the existing `AUTHORIZE` hook: today the built-in credential plugin loads by default and `allowed_plugins` defaults to loading everything discovered, so any deployment with an SSO integration already runs two `AUTHORIZE` plugins. The existing hook keeps working, and integrations move to the new group one at a time.
Signatures, type names and the retry semantics are specified in the sub-issues, not here.
## Scope
- **Identity resolution** — the flow above, and its manager-side lookup, retry and status handling.
- **Account provisioning on first sign-in** — idempotent user creation with the default keypair, RBAC scope registration and encrypted secrets; keypair and SSH-key repair; container id and TOTP field updates.
- **Project resolution and membership** — resolve a project by name, create one idempotently, bind and unbind a user, enforce sole membership, list a user's projects.
- **Plugin state store** — persistent and TTL-backed storage namespaced per plugin, replacing direct etcd writes to the root namespace and hand-rolled Valkey clients.
- **Periodic tasks** — key-set refresh for token verification.
- **Webapp surface** — declarative route registration, a typed authenticated caller, framework-neutral responses, and a public API for auth parameter parsing. Separate from the identity flow above, and delivered as its own issue.
- **Unified plugin configuration** — one configuration namespace per plugin, shared by all of its components.
The full interface catalogue, with the operation each method replaces, is maintained in the comments on this epic. It was compiled from all manager plugins and therefore covers a wider surface than this epic.
## Hook events to add
- Logout and forced login-session invalidation — `AuthService.logout()` dispatches nothing, and the single-logout path is unimplemented. One integration works around this by invalidating login sessions as a side effect inside its `AUTHORIZE` handler.
- User creation on the admin and SSO provisioning paths — `PRE_SIGNUP` / `POST_SIGNUP` cover self-signup only.
- Project creation and membership changes — no hook exists.
## Out of scope
- **Two-factor challenge flows** — these stay on the existing `POST_AUTHORIZE` hook, which can return a response. The flow above has no challenge outcome.
- **Session mutation** — reading a session and injecting vfolder mounts, environment variables or bootstrap scripts. Needed by two integrations, and blocked on `POST_ENQUEUE_SESSION` being a `notify` that discards its return value.
- **VFolder provisioning** — creating user and project folders and listing accessible folders. One integration performs this **inside its** `AUTHORIZE` **handler**, so it sits in the authentication flow even though the operation is storage. It is deliberately excluded here and must be tracked separately.
- **Scheduling predicates** — the dead `PREDICATE` and `PRE_START_SESSION` registrations, which plugins declare but the manager never dispatches.
- Agent, storage and watcher plugin groups; accelerator plugins (`backendai_accelerator_v21`).
- Retired integrations, and out-of-process plugin runners such as a periodic external-directory sync script.
JIRA Issue: BA-7502
Contributor guide
Research direction
Start with the interface catalogue in the epic comments and the manager hook and webapp plugin sources described in the issue, including PluginsInput.init_context and AuthService.logout(). Trace the AUTHORIZE and POST_AUTHORIZE entry points and the manager-side provisioning and project operations. Done means authentication integrations use only the versioned interfaces, with manager-owned lookup, provisioning, security handling, configuration, and state storage.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- authentication, backend, backend-api-design
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100