Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 57s
delegation.sx makes the loop's central rule concrete: check() introspects
the token first — inactive → {error, unauthenticated} (401), acl never
consulted — and only an authenticated subject's request is delegated to
acl, which returns permit/deny ({error, forbidden} = 403). 401 strictly
precedes 403. acl-on-sx (Datalog) is a different SX guest wired at the
integration layer, so the decider here is a labelled stub (permits when
Action in Scope); swap the pid and the boundary is unchanged. New
tests/delegation.sx. 185/185 — extensions backlog clear.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
103 lines
4.7 KiB
Plaintext
103 lines
4.7 KiB
Plaintext
;; identity/tests/delegation.sx — the identity -> acl boundary.
|
|
;; Authentication (identity) gates BEFORE authorization (acl): an inactive
|
|
;; token is unauthenticated (401) and acl is never consulted; only an
|
|
;; authenticated subject's request is delegated to acl for permit/deny.
|
|
|
|
(define id-deleg-test-count 0)
|
|
(define id-deleg-test-pass 0)
|
|
(define id-deleg-test-fails (list))
|
|
|
|
(define
|
|
id-deleg-test
|
|
(fn
|
|
(name actual expected)
|
|
(set! id-deleg-test-count (+ id-deleg-test-count 1))
|
|
(if
|
|
(= actual expected)
|
|
(set! id-deleg-test-pass (+ id-deleg-test-pass 1))
|
|
(append! id-deleg-test-fails {:name name :expected expected :actual actual}))))
|
|
|
|
(define idl-ev erlang-eval-ast)
|
|
(define idlnm (fn (v) (get v :name)))
|
|
|
|
(identity-load-delegation!)
|
|
|
|
;; Shared prelude: a token registry, a stub acl, and a token granting
|
|
;; [read, write] to alice, all bound.
|
|
(define
|
|
idl-setup
|
|
"R = identity_tokens:start(),\n A = identity_delegation:stub_acl(),\n {ok, T} = identity_tokens:issue(R, alice, web, [read, write])")
|
|
|
|
;; ── authenticated + acl permits ──────────────────────────────────
|
|
|
|
(id-deleg-test
|
|
"an authenticated, permitted request returns the subject"
|
|
(idlnm
|
|
(idl-ev
|
|
(str
|
|
idl-setup
|
|
", case identity_delegation:check(R, A, T, read, doc1) of\n {ok, S} -> S;\n {error, W} -> W\n end")))
|
|
"alice")
|
|
|
|
;; ── authenticated + acl denies → 403 ─────────────────────────────
|
|
|
|
(id-deleg-test
|
|
"an authenticated but unpermitted request is forbidden"
|
|
(idlnm
|
|
(idl-ev
|
|
"R = identity_tokens:start(),\n A = identity_delegation:stub_acl(),\n {ok, T} = identity_tokens:issue(R, alice, web, [read]),\n case identity_delegation:check(R, A, T, write, doc1) of\n {ok, _} -> permitted;\n {error, W} -> W\n end"))
|
|
"forbidden")
|
|
|
|
;; ── unauthenticated → 401, acl never consulted ───────────────────
|
|
|
|
(id-deleg-test
|
|
"a revoked token is unauthenticated, not forbidden"
|
|
(idlnm
|
|
(idl-ev
|
|
(str
|
|
idl-setup
|
|
", identity_tokens:revoke(R, T),\n case identity_delegation:check(R, A, T, read, doc1) of\n {ok, _} -> permitted;\n {error, W} -> W\n end")))
|
|
"unauthenticated")
|
|
|
|
(id-deleg-test
|
|
"an unknown token is unauthenticated"
|
|
(idlnm
|
|
(idl-ev
|
|
"R = identity_tokens:start(),\n A = identity_delegation:stub_acl(),\n Bogus = make_ref(),\n case identity_delegation:check(R, A, Bogus, read, doc1) of\n {ok, _} -> permitted;\n {error, W} -> W\n end"))
|
|
"unauthenticated")
|
|
|
|
(id-deleg-test
|
|
"an expired token is unauthenticated"
|
|
(idlnm
|
|
(idl-ev
|
|
"R = identity_tokens:start(),\n A = identity_delegation:stub_acl(),\n {ok, T} = identity_tokens:issue(R, alice, web, [read], 100),\n identity_tokens:advance(R, 100),\n case identity_delegation:check(R, A, T, read, doc1) of\n {ok, _} -> permitted;\n {error, W} -> W\n end"))
|
|
"unauthenticated")
|
|
|
|
;; ── 401 takes precedence over 403 (identity gates first) ─────────
|
|
|
|
(id-deleg-test
|
|
"a revoked token with no matching scope is still unauthenticated"
|
|
(idlnm
|
|
(idl-ev
|
|
"R = identity_tokens:start(),\n A = identity_delegation:stub_acl(),\n {ok, T} = identity_tokens:issue(R, alice, web, [admin]),\n identity_tokens:revoke(R, T),\n case identity_delegation:check(R, A, T, read, doc1) of\n {ok, _} -> permitted;\n {error, W} -> W\n end"))
|
|
"unauthenticated")
|
|
|
|
;; ── acl is what decides for an authenticated subject ─────────────
|
|
|
|
(id-deleg-test
|
|
"the same subject is permitted one action and denied another"
|
|
(idl-ev
|
|
"R = identity_tokens:start(),\n A = identity_delegation:stub_acl(),\n {ok, T} = identity_tokens:issue(R, alice, web, [read]),\n Allowed = case identity_delegation:check(R, A, T, read, doc1) of\n {ok, _} -> 1; {error, _} -> 0 end,\n Denied = case identity_delegation:check(R, A, T, write, doc1) of\n {ok, _} -> 1; {error, _} -> 0 end,\n Allowed - Denied")
|
|
1)
|
|
|
|
(id-deleg-test
|
|
"identity does not widen permission beyond the token scope"
|
|
(idlnm
|
|
(idl-ev
|
|
"R = identity_tokens:start(),\n A = identity_delegation:stub_acl(),\n {ok, T} = identity_tokens:issue(R, alice, web, [read, write]),\n case identity_delegation:check(R, A, T, delete, doc1) of\n {ok, _} -> permitted;\n {error, W} -> W\n end"))
|
|
"forbidden")
|
|
|
|
(define
|
|
id-deleg-test-summary
|
|
(str "delegation " id-deleg-test-pass "/" id-deleg-test-count))
|