T10.4 D13 - provision on first sign-in without trampling existing accounts
Criteria 2 and 4 pull against each other: create accounts that do not exist, never touch the role of accounts that do. Two helpers in app.py keep the two cases apart so the role-preserving branch cannot be edited by accident. Matching uses sAMAccountName OR the directory's mail, per the Aug 21 decision. A bind can only carry one identifier, so the bind is sAMAccountName@prime.local, but matching an existing local row tries both - existing accounts were typed by hand with manage_users.py and some are short logon names while others are email addresses. auth.find_user already compares case-insensitively against username AND email, so two calls cover four columns. Verified against a throwaway SQLite database: existing admin -> role still 'admin' locally-set full_name -> preserved, not overwritten by the directory empty email -> filled from the directory local username is email -> matched by mail, project_admin kept no local row -> created at project_user, is_active, audit row ProjectMember rows -> 0 second sign-in -> same row, no duplicate, 3 users total A JIT account deliberately gets NO project access. The wave file said to honour the auto_add_projects machinery so a new account "lands in the right projects"; that was wrong about the flag, which is evaluated when a PROJECT is created to mark who joins every new job and cannot retroactively add an account to jobs that already exist. There is no correct default, so least privilege applies and the wave file's done-when has been corrected rather than quietly satisfied. The consequence is a UX cliff worth knowing about: a successful sign-in into an empty app until an admin grants access. That is why provisioning writes an AuditLog row and a log line instead of happening silently. is_active is checked after provisioning (a new account defaults active) and after the role branch (a disabled admin is still refused). Local is_active overrides the directory on purpose: disabling here revokes access to this app without touching the domain account. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -157,10 +157,19 @@ afterwards.
|
||||
`auth.find_user` (already case-insensitive across username **and** email). If it exists,
|
||||
update only `last_login_at` and — if empty locally — `full_name` and `email` from the
|
||||
directory. **Never write `role`.** If it does not exist, create it at
|
||||
`ROLE_PROJECT_USER` with `full_name`/`email` from the directory, honouring the existing
|
||||
`auto_add_projects` machinery so a JIT account lands in the right projects. Note the
|
||||
flush-order warning in the `models.py` docstring: `create_user` in `app.py` already handles
|
||||
this correctly for account + `ProjectMember` rows in one flush — follow it.
|
||||
`ROLE_PROJECT_USER` with `full_name`/`email` from the directory.
|
||||
|
||||
**A JIT account gets NO project access, and that is deliberate.** An earlier draft of this
|
||||
task said to honour the `auto_add_projects` machinery so a new account "lands in the right
|
||||
projects" — that was wrong about how the flag works. `auto_add_projects` is evaluated when a
|
||||
**project** is created (`app.py:245`), marking accounts that should join every *new* job; it
|
||||
cannot retroactively add a new account to existing ones. There is no correct default, so
|
||||
least privilege applies: the account exists, can sign in, and sees nothing until someone
|
||||
grants access. That is a real UX cliff — a successful sign-in into an empty app — so it has
|
||||
to be visible to admins rather than silent, which is what the `AuditLog` row is for.
|
||||
|
||||
Note the flush-order warning in the `models.py` docstring: `create_user` in `app.py` handles
|
||||
account-then-membership correctly in one flush — follow it if you add rows.
|
||||
|
||||
Write an `AuditLog` row for each JIT creation. An account appearing without an administrator
|
||||
creating it is exactly the kind of event that record exists for.
|
||||
@@ -171,7 +180,8 @@ creating it is exactly the kind of event that record exists for.
|
||||
- [ ] `full_name` and `email` are populated from the directory on creation
|
||||
- [ ] an existing `admin` signing in is still `admin` afterwards — asserted, not assumed
|
||||
- [ ] an existing account with a locally-set `full_name` does not have it overwritten
|
||||
- [ ] a JIT account with `auto_add_projects` peers gets its `ProjectMember` rows
|
||||
- [ ] a JIT account has NO `ProjectMember` rows and sees no projects
|
||||
- [ ] the new account appears in the Admin console user list so access can be granted
|
||||
- [ ] each JIT creation writes an `AuditLog` row
|
||||
- [ ] a failed bind creates **no** row
|
||||
- [ ] a bind that succeeds but fails the group check creates **no** row
|
||||
|
||||
Reference in New Issue
Block a user