feat(scanner): scanner entrypoints + generic jobs carry RLS identity; SeedDB harness fallback
The full suite enumerated the remaining triage class: tests drive the scanner directly (ScanFolders(context.Background())) and read seeded media via Queries with bare contexts — every governed read raised. - MediaScanner.ScanFolders self-declares its configured admin (SetAdminID — set by main AND the test helpers): a scan is server maintenance, product-correct regardless of caller. - ScopedDB gains a Fallback identity, exposed as NewSeedDB for the TEST HARNESS ONLY: setup.DB falls back to rls.SeedIdentity when the context carries none, so direct seeding/verification helpers work uniformly instead of per-site context threading. rls.SeedIdentity is the shared constant (no library_visibility rows => full visibility). - generic job creation (jobs.go) stamps the requesting admin into the job Context, same as the scan endpoint. The sync queue processor stays identity-free: sync_queue is not an RLS-governed table.
This commit is contained in:
1 parent
385a5d9dba
commit
bb2cc26e9d
9 files changed
+72
-35
No files matched your search
@@ -26,6 +26,7 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"bookhoard/internal/rls"
|
||||
@@ -35,9 +36,11 @@ import (
|
||||
var _ DBTX = (*ScopedDB)(nil)
|
||||
|
||||
// ScopedDB implements DBTX over a pool, declaring the acting user for
|
||||
// the RLS policies on every statement.
|
||||
// the RLS policies on every statement. Fallback (test harness ONLY —
|
||||
// see NewSeedDB) supplies an identity when the context carries none.
|
||||
type ScopedDB struct {
|
||||
Pool *pgxpool.Pool
|
||||
Pool *pgxpool.Pool
|
||||
Fallback *pgtype.UUID
|
||||
}
|
||||
|
||||
// NewScopedDB is the constructor used at the single Queries
|
||||
@@ -46,6 +49,15 @@ func NewScopedDB(pool *pgxpool.Pool) *ScopedDB {
|
||||
return &ScopedDB{Pool: pool}
|
||||
}
|
||||
|
||||
// NewSeedDB returns a ScopedDB for TEST-HARNESS seeding/verification:
|
||||
// when the context carries no identity it falls back to
|
||||
// rls.SeedIdentity, so direct media reads/writes from helpers work
|
||||
// without impersonating a real user. Never use in product wiring.
|
||||
func NewSeedDB(pool *pgxpool.Pool) *ScopedDB {
|
||||
seed := rls.SeedIdentity
|
||||
return &ScopedDB{Pool: pool, Fallback: &seed}
|
||||
}
|
||||
|
||||
const zeroUUID = "00000000-0000-0000-0000-000000000000"
|
||||
|
||||
// begin opens the per-statement transaction. If the context carries a
|
||||
@@ -62,7 +74,11 @@ func (s *ScopedDB) begin(ctx context.Context) (pgx.Tx, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if uid, ok := rls.FromContext(ctx); ok && uid.Valid && uid.String() != zeroUUID {
|
||||
uid, ok := rls.FromContext(ctx)
|
||||
if (!ok || !uid.Valid || uid.String() == zeroUUID) && s.Fallback != nil {
|
||||
uid, ok = *s.Fallback, true
|
||||
}
|
||||
if ok && uid.Valid && uid.String() != zeroUUID {
|
||||
if err := rls.DeclareUser(ctx, tx, uid); err != nil {
|
||||
_ = tx.Rollback(ctx)
|
||||
return nil, fmt.Errorf("rls: declare user: %w", err)
|
||||
|
||||
Reference in new issue
Block a user