fix(oidc): wire DefaultPermissions/DefaultLibraryIDs into JIT provisioning (bookshelf-r5dix) #1401
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "bd-bookshelf-r5dix"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
internal/app/app.go's GetOIDCConfig closure built appwire.OIDCRuntimeConfig with an explicit field list that dropped
rc.DefaultPermissionsandrc.DefaultLibraryIDs. Both fields exist on the source (settings.OIDCRuntimeConfig) and target (appwire.OIDCRuntimeConfig) types and are fully consumed downstream (internal/users/wire.go->applyJITDefaults), but they never reached the production struct literal — so admin-configured OIDC JIT default permissions/default library access round-tripped through the DB but were silently dropped and never applied to newly auto-provisioned OIDC users (they got nil permissions/nil library access instead). Same bug class as the #1395 SessionDuration BLOCKER.Fix
internal/app/app.go: addDefaultPermissions: rc.DefaultPermissionsandDefaultLibraryIDs: rc.DefaultLibraryIDsto the struct literal.e2e/api/journey_9_oidc_login_test.go: add a regression step mirroring the #1395 SessionDuration guard.internal/appis untested wiring per project convention, so the only way to exercise the realGetOIDCConfigclosure is booting the realapp.Newserver end-to-end. The new step configuresdefault_permissions/default_library_idsviaPUT /settings/oidc, logs in a fresh OIDC user with no matching group mapping, and asserts the new user actually received the configured permission and library access. This step fails against the unfixed app.go.Test plan
go build ./...andgo build -tags e2e ./e2e/...compile cleanmake test— all unit tests passmake e2e)Closes bead bookshelf-r5dix on merge.
Code review for PR #1401 (bd-bookshelf-r5dix)
Scope checked: correctness of the
GetOIDCConfigclosure fix ininternal/app/app.go, completeness of the field-copy (no other silently-dropped fields), and whether the new e2e step ine2e/api/journey_9_oidc_login_test.gogenuinely proves the regression against the realapp.Newwiring.Findings:
internal/app/app.go:373-374— addsDefaultPermissions: rc.DefaultPermissionsandDefaultLibraryIDs: rc.DefaultLibraryIDsto the struct literal. Diffed bothinternal/settings/oidc_settings.goandinternal/appwire/appwire.goOIDCRuntimeConfigdefinitions field-by-field against the literal inapp.go— all 17 fields (Enabled…RPInitiatedLogout) are now copied 1:1. No other field is silently dropped in the same literal.e2e/api/journey_9_oidc_login_test.go:887-960(new Step 9b) — appended inside the existingOrdered"Journey-9"Describe(not a new top-level container), reusing the sharedBeforeAll-booted real app/DB/fake-IdP from the rest of the file — complies with theOrdered-journey policy (no newBeforeEach(ResetDB), no isolated single-endpointDescribe). The step:/settings/oidcwithDefaultPermissions: ["permission_upload"]andDefaultLibraryIDs: [libraryID], matching the same PUT pattern already used earlier in the file (Step 9, for the analogousSessionDurationregression) — consistent with precedent, and this endpoint shape is proven by the file's own earlier green step.oidcFullFlow) for a brand-new subject with"groups": [](no group mapping in play, isolating the default-permissions/libraries path specifically).user_permissions.permission_upload,user_library_mapping) rather than an in-memory stub — this exercises the actualGetOIDCConfigclosure end-to-end. If either field were dropped from the literal again,rc.DefaultPermissions/rc.DefaultLibraryIDswould never reachappwire.OIDCRuntimeConfig, provisioning would apply no defaults, and this test would fail (hasUploadfalse /libraryCount0) — a genuine regression guard, not a tautology.internal/appis untested wiring per project convention; only booting realapp.Newexercises the literal).Multi-
Expect-per-Itin the new step is allowed under the e2e journey policy's relaxation for a single action spanning multiple observations — no violation.CI is green (
successon the head SHA) and the PR ismergeable: true.No correctness, security, scope, or convention issues found. This is a minimal, complete, well-targeted fix with a regression test that would have caught the original bug.
REVIEW VERDICT: 0 blocker, 0 major, 0 minor
Security review of PR #1401 (bd-bookshelf-r5dix) — read-only diff review.
Summary
Minimal 2-line wiring fix in
internal/app/app.go(GetOIDCConfigclosure): addsDefaultPermissions: rc.DefaultPermissionsandDefaultLibraryIDs: rc.DefaultLibraryIDsto theappwire.OIDCRuntimeConfigstruct literal, plus a Journey-9 e2e test asserting the fix end-to-end. Same class of bug as the earlierSessionDurationfield-drop regression — a field silently missing from an explicit struct literal.Verification performed
settings.GetOIDCConfig(DB) →app.goGetOIDCConfigclosure (the fix) →internal/users/wire.gogetOIDCConfigadapter →OIDCConfigpassed intoapplyJITDefaults(internal/users/oidc_service.go). Confirmedwire.goalready correctly mappedrc.DefaultPermissions/rc.DefaultLibraryIDs— the only break was the missing fields inapp.go's literal, now fixed.applyJITDefaults(pre-existing, untouched by this diff) explicitly stripspermission_adminbefore building the upsert map: So admin cannot be auto-granted via JIT defaults even if an operator configures it — no privilege-escalation path introduced by this fix.PUT /settings/oidc(whereDefaultPermissions/DefaultLibraryIDsare configured) isadminRequired-gated (internal/settings/routes.go) — only an admin can set these defaults; a non-admin cannot self-grant broader defaults for future JIT users.provisionOIDCUser→applyDefaults(ctx, userID)), never sourced from request/claims data — no cross-user injection vector.DefaultLibraryIDsare admin-configured values applied viasetLibraries(ctx, userID, cfg.DefaultLibraryIDs), not attacker-influenced; the new user's library grants are limited to exactly the admin-configured set, not a wildcard.journey_9_oidc_login_test.go) configures a single non-admin permission (permission_upload) and one library, then asserts via direct DB read that exactly that permission and exactly that library landed on the new user — a reasonable regression guard scoped to the same failure mode as theSessionDurationbug (config silently dropped by an explicit-field struct literal in untested wiring code,internal/appis exempt from unit tests per project convention, so an e2e boot of the realapp.Newclosure is the only way to catch this).style=, no external fetch, no token/secret handling in the changed lines.Findings
No blockers, majors, or minors found. This is a narrowly-scoped, well-justified regression fix with an appropriate test.
REVIEW VERDICT: 0 blocker, 0 major, 0 minor