;; identity/tests/par.sx — pushed authorization requests (PAR, RFC 9126): ;; lodge the authorization params up front under a single-use request_uri, ;; then redeem it into the normal consent flow. The binding (client, ;; redirect, PKCE) carried by the pushed request is enforced at exchange. (define id-par-test-count 0) (define id-par-test-pass 0) (define id-par-test-fails (list)) (define id-par-test (fn (name actual expected) (set! id-par-test-count (+ id-par-test-count 1)) (if (= actual expected) (set! id-par-test-pass (+ id-par-test-pass 1)) (append! id-par-test-fails {:name name :expected expected :actual actual})))) (define idp-ev erlang-eval-ast) (define idpnm (fn (v) (get v :name))) (identity-load-oauth!) ;; ── pushed request redeems into consent ────────────────────────── (id-par-test "authorize_pushed on a fresh request_uri asks for consent" (idpnm (idp-ev "O = identity_oauth:start(),\n {ok, Ru} = identity_oauth:push_authorization_request(O, web, uri1, read, alice, v),\n case identity_oauth:authorize_pushed(O, Ru) of\n {consent_required, _} -> consent_required;\n {error, W} -> W\n end")) "consent_required") ;; ── full PAR flow ──────────────────────────────────────────────── (id-par-test "the full PAR flow yields a working token" (idpnm (idp-ev "O = identity_oauth:start(),\n {ok, Ru} = identity_oauth:push_authorization_request(O, web, uri1, read, alice, v),\n {consent_required, Rq} = identity_oauth:authorize_pushed(O, Ru),\n {code, Cd} = identity_oauth:consent(O, Rq, allow),\n {ok, A, _R} = identity_oauth:exchange(O, Cd, web, uri1, v),\n case identity_oauth:introspect(O, A) of\n {active, _, _, _} -> active;\n {inactive} -> inactive\n end")) "active") (id-par-test "the PAR token carries the pushed subject" (idpnm (idp-ev "O = identity_oauth:start(),\n {ok, Ru} = identity_oauth:push_authorization_request(O, web, uri1, read, alice, v),\n {consent_required, Rq} = identity_oauth:authorize_pushed(O, Ru),\n {code, Cd} = identity_oauth:consent(O, Rq, allow),\n {ok, A, _R} = identity_oauth:exchange(O, Cd, web, uri1, v),\n case identity_oauth:introspect(O, A) of\n {active, Subject, _, _} -> Subject\n end")) "alice") ;; ── request_uri is single-use ──────────────────────────────────── (id-par-test "a request_uri cannot be redeemed twice" (idpnm (idp-ev "O = identity_oauth:start(),\n {ok, Ru} = identity_oauth:push_authorization_request(O, web, uri1, read, alice, v),\n identity_oauth:authorize_pushed(O, Ru),\n case identity_oauth:authorize_pushed(O, Ru) of\n {consent_required, _} -> reused;\n {error, W} -> W\n end")) "invalid_request_uri") (id-par-test "an unknown request_uri is rejected" (idpnm (idp-ev "O = identity_oauth:start(),\n Bogus = make_ref(),\n case identity_oauth:authorize_pushed(O, Bogus) of\n {consent_required, _} -> ok;\n {error, W} -> W\n end")) "invalid_request_uri") ;; ── the pushed binding is still enforced at exchange ───────────── (id-par-test "a PAR-issued code still enforces PKCE" (idpnm (idp-ev "O = identity_oauth:start(),\n {ok, Ru} = identity_oauth:push_authorization_request(O, web, uri1, read, alice, v),\n {consent_required, Rq} = identity_oauth:authorize_pushed(O, Ru),\n {code, Cd} = identity_oauth:consent(O, Rq, allow),\n case identity_oauth:exchange(O, Cd, web, uri1, wrongverif) of\n {ok, _, _} -> ok;\n {error, W} -> W\n end")) "invalid_grant") (id-par-test "a PAR-issued code still enforces client binding" (idpnm (idp-ev "O = identity_oauth:start(),\n {ok, Ru} = identity_oauth:push_authorization_request(O, web, uri1, read, alice, v),\n {consent_required, Rq} = identity_oauth:authorize_pushed(O, Ru),\n {code, Cd} = identity_oauth:consent(O, Rq, allow),\n case identity_oauth:exchange(O, Cd, attacker, uri1, v) of\n {ok, _, _} -> ok;\n {error, W} -> W\n end")) "invalid_grant") (define id-par-test-summary (str "par " id-par-test-pass "/" id-par-test-count))