mirror of
https://github.com/penpot/penpot.git
synced 2026-11-05 09:30:38 +00:00
* ✨ Restrict management RPC methods to their shared keys Management methods now declare the shared-key ids allowed to call them with ::rpc/shared-keys. A wrapper in wrap-management checks the key id from the request before opening a transaction, so a valid key outside the declared set answers 403 shared-key-not-allowed. Methods without the metadata keep accepting any configured key. Scope upload-tempfile to exporter and media-processor, file analysis to nexus, durable jobs to exporter, and the Nitrate methods to admin-console. The test helper injects a declared key for the allowed path, and a coverage test fails when a management method is missing or declares the wrong set. Closes #12730 AI-assisted-by: deepseek-v4.1-flash * ♻️ Harden shared-key authorization scope and tests Rename the per-method metadata to ::rpc/allowed-shared-keys so it reads as the allowed key ids, not the secrets. Fix the management test helper to resolve the key from the supplied request and to keep an explicit nil, which prevents a rejection test from silently using an allowed key. Strengthen coverage: assert every management namespace is scanned, check all four job methods reject a key they do not declare, verify a rejected complete-job leaves the row and its events untouched, and exercise both keys allowed on upload-tempfile. Log denied keys with the method name and key id, without the secret, and update the audit-log memory for push-audit-events. Closes #12730 AI-assisted-by: deepseek-v4.1-flash
1048 lines
43 KiB
Clojure
1048 lines
43 KiB
Clojure
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
;;
|
|
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
|
|
|
(ns backend-tests.rpc-test
|
|
(:require
|
|
[app.common.exceptions :as ex]
|
|
[app.common.logging :as l]
|
|
[app.common.schema :as sm]
|
|
[app.common.time :as ct]
|
|
[app.common.uuid :as uuid]
|
|
[app.http :as http]
|
|
[app.http.access-token :as actoken]
|
|
[app.http.session :as session]
|
|
[app.rpc :as rpc]
|
|
[app.rpc.cond :as cond]
|
|
[app.rpc.helpers :as rph]
|
|
[app.util.inet :as inet]
|
|
[backend-tests.helpers :as th]
|
|
[clojure.test :as t]
|
|
[yetti.response :as yres]))
|
|
|
|
(defn- echo-handler
|
|
"Handler that returns the data map it receives, for inspection."
|
|
[data]
|
|
data)
|
|
|
|
(defn- make-handler
|
|
"Creates an RPC handler with a single :test-method backed by echo-handler.
|
|
Extra methods are merged in; each value is [metadata handler-fn]."
|
|
([] (make-handler {}))
|
|
([extra-methods]
|
|
(let [methods (merge {:test-method [{} echo-handler]}
|
|
extra-methods)]
|
|
(rpc/make-rpc-handler methods))))
|
|
|
|
(defn- make-request
|
|
"Builds a dummy request. Extra qualified-keyword keys are merged onto it."
|
|
[& {:keys [method path-params headers extras]
|
|
:or {method :post
|
|
path-params {:method-name "test-method"}
|
|
headers {}
|
|
extras {}}}]
|
|
(-> (th/make-dummy-request {:method method
|
|
:headers headers})
|
|
(assoc :path-params path-params
|
|
:params {})
|
|
(merge extras)))
|
|
|
|
(defn- call
|
|
"Invokes handler with a built request, returns the full response map."
|
|
([handler opts]
|
|
(handler (make-request opts)))
|
|
([handler]
|
|
(call handler {})))
|
|
|
|
(defn- call-body
|
|
"Invokes handler with a built request, returns ::yres/body from response."
|
|
([handler opts]
|
|
(::yres/body (call handler opts)))
|
|
([handler]
|
|
(::yres/body (call handler))))
|
|
|
|
(defn- call-ex
|
|
"Invokes handler, catches and returns the exception."
|
|
([handler opts]
|
|
(try (call handler opts) (catch Exception e e)))
|
|
([handler]
|
|
(call-ex handler {})))
|
|
|
|
(def fixed-time (ct/now))
|
|
(def fixed-uuid (uuid/custom 11111111 22222222))
|
|
|
|
;; --- BASIC HANDLER DISPATCH
|
|
|
|
(t/deftest handler-receives-handler-name
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler)
|
|
data (call-body handler)]
|
|
(t/is (= "test-method" (::rpc/handler-name data))))))
|
|
|
|
(t/deftest handler-receives-ip-addr
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "10.0.0.1")]
|
|
(let [handler (make-handler)
|
|
data (call-body handler)]
|
|
(t/is (= "10.0.0.1" (::rpc/ip-addr data))))))
|
|
|
|
(t/deftest handler-receives-request-at
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler)
|
|
data (call-body handler)]
|
|
(t/is (= fixed-time (::rpc/request-at data))))))
|
|
|
|
(t/deftest handler-receives-request-id
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler)
|
|
data (call-body handler)]
|
|
(t/is (= fixed-uuid (::rpc/request-id data))))))
|
|
|
|
(t/deftest unknown-handler-name-raises-not-found
|
|
(let [handler (make-handler)
|
|
ex (call-ex handler {:path-params {:method-name "unknown"}})]
|
|
(t/is (some? ex))
|
|
(t/is (th/ex-of-type? ex :not-found))))
|
|
|
|
;; --- HANDLER-NAME EDGE CASES
|
|
|
|
(t/deftest nil-path-params-falls-to-default-handler
|
|
(let [handler (make-handler)
|
|
request (make-request {:path-params nil})
|
|
ex (try (handler request) (catch Exception e e))]
|
|
(t/is (some? ex))
|
|
(t/is (th/ex-of-type? ex :not-found))))
|
|
|
|
(t/deftest missing-method-name-falls-to-default-handler
|
|
(let [handler (make-handler)
|
|
request (make-request {:path-params {}})
|
|
ex (try (handler request) (catch Exception e e))]
|
|
(t/is (some? ex))
|
|
(t/is (th/ex-of-type? ex :not-found))))
|
|
|
|
(t/deftest empty-method-name-falls-to-default-handler
|
|
(let [handler (make-handler)
|
|
request (make-request {:path-params {:method-name ""}})
|
|
ex (try (handler request) (catch Exception e e))]
|
|
(t/is (some? ex))
|
|
(t/is (th/ex-of-type? ex :not-found))))
|
|
|
|
(t/deftest get-with-exact-get-prefix-no-suffix-allowed
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler {:get- [{} echo-handler]})
|
|
data (call-body handler
|
|
{:method :get
|
|
:path-params {:method-name "get-"}})]
|
|
(t/is (= "get-" (::rpc/handler-name data))))))
|
|
|
|
;; --- AUTH: SESSION
|
|
|
|
(t/deftest session-auth-profile-id
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [pid (uuid/next)
|
|
handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::session/profile-id pid}})]
|
|
(t/is (= pid (::rpc/profile-id data))))))
|
|
|
|
(t/deftest session-auth-type
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [pid (uuid/next)
|
|
handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::session/profile-id pid}})]
|
|
(t/is (= :session (::rpc/auth-type data))))))
|
|
|
|
(t/deftest session-auth-no-token-perms
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [pid (uuid/next)
|
|
handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::session/profile-id pid}})]
|
|
(t/is (not (contains? data ::rpc/token-perms))))))
|
|
|
|
(t/deftest nil-session-profile-id-not-used
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::session/profile-id nil}})]
|
|
(t/is (not (contains? data ::rpc/profile-id)))
|
|
(t/is (not (contains? data ::rpc/auth-type))))))
|
|
|
|
;; --- AUTH: TOKEN
|
|
|
|
(t/deftest token-auth-profile-id
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [pid (uuid/next)
|
|
handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::actoken/profile-id pid}})]
|
|
(t/is (= pid (::rpc/profile-id data))))))
|
|
|
|
(t/deftest token-auth-type
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [pid (uuid/next)
|
|
handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::actoken/profile-id pid}})]
|
|
(t/is (= :token (::rpc/auth-type data))))))
|
|
|
|
(t/deftest token-auth-with-perms
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [pid (uuid/next)
|
|
handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::actoken/profile-id pid
|
|
::actoken/perms #{"view" "edit"}}})]
|
|
(t/is (= #{"view" "edit"} (::rpc/token-perms data))))))
|
|
|
|
(t/deftest token-auth-perms-default-to-empty-set
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [pid (uuid/next)
|
|
handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::actoken/profile-id pid}})]
|
|
(t/is (= #{} (::rpc/token-perms data))))))
|
|
|
|
(t/deftest token-auth-perms-coerced-from-vector
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [pid (uuid/next)
|
|
handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::actoken/profile-id pid
|
|
::actoken/perms ["view" "edit"]}})]
|
|
(t/is (set? (::rpc/token-perms data)))
|
|
(t/is (= #{"view" "edit"} (::rpc/token-perms data))))))
|
|
|
|
(t/deftest token-auth-perms-coerced-from-list
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [pid (uuid/next)
|
|
handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::actoken/profile-id pid
|
|
::actoken/perms (list "view" "edit")}})]
|
|
(t/is (set? (::rpc/token-perms data)))
|
|
(t/is (= #{"view" "edit"} (::rpc/token-perms data))))))
|
|
|
|
(t/deftest token-auth-with-nil-perms-defaults-to-empty-set
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [pid (uuid/next)
|
|
handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::actoken/profile-id pid
|
|
::actoken/perms nil}})]
|
|
(t/is (= #{} (::rpc/token-perms data))))))
|
|
|
|
(t/deftest token-auth-with-empty-perms
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [pid (uuid/next)
|
|
handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::actoken/profile-id pid
|
|
::actoken/perms #{}}})]
|
|
(t/is (= #{} (::rpc/token-perms data))))))
|
|
|
|
(t/deftest nil-actoken-profile-id-not-used
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::actoken/profile-id nil}})]
|
|
(t/is (not (contains? data ::rpc/profile-id)))
|
|
(t/is (not (contains? data ::rpc/auth-type))))))
|
|
|
|
;; --- AUTH: SHARED KEY
|
|
|
|
(t/deftest key-auth-profile-id-is-zero
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::http/auth-key-id "some-key"}})]
|
|
(t/is (= uuid/zero (::rpc/profile-id data))))))
|
|
|
|
(t/deftest key-auth-key-id-in-data
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::http/auth-key-id "some-key"}})]
|
|
(t/is (= "some-key" (::rpc/auth-key-id data))))))
|
|
|
|
(t/deftest nil-auth-key-id-not-used
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::http/auth-key-id nil}})]
|
|
(t/is (not (contains? data ::rpc/profile-id)))
|
|
(t/is (not (contains? data ::rpc/auth-key-id))))))
|
|
|
|
;; --- AUTH: UNAUTHENTICATED
|
|
|
|
(t/deftest unauthenticated-no-profile-id
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler)
|
|
data (call-body handler)]
|
|
(t/is (not (contains? data ::rpc/profile-id))))))
|
|
|
|
(t/deftest unauthenticated-no-auth-type
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler)
|
|
data (call-body handler)]
|
|
(t/is (not (contains? data ::rpc/auth-type))))))
|
|
|
|
;; --- AUTH: PRIORITY
|
|
|
|
(t/deftest session-overrides-token
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [session-pid (uuid/custom 1 1)
|
|
token-pid (uuid/custom 2 2)
|
|
handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::session/profile-id session-pid
|
|
::actoken/profile-id token-pid}})]
|
|
(t/is (= session-pid (::rpc/profile-id data)))
|
|
(t/is (= :session (::rpc/auth-type data))))))
|
|
|
|
(t/deftest session-overrides-key
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [session-pid (uuid/custom 1 1)
|
|
handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::session/profile-id session-pid
|
|
::http/auth-key-id "key-id"}})]
|
|
(t/is (= session-pid (::rpc/profile-id data)))
|
|
(t/is (= :session (::rpc/auth-type data)))
|
|
(t/is (= "key-id" (::rpc/auth-key-id data))))))
|
|
|
|
(t/deftest token-overrides-key
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [token-pid (uuid/custom 2 2)
|
|
handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::actoken/profile-id token-pid
|
|
::http/auth-key-id "key-id"}})]
|
|
(t/is (= token-pid (::rpc/profile-id data)))
|
|
(t/is (= :token (::rpc/auth-type data)))
|
|
(t/is (= "key-id" (::rpc/auth-key-id data))))))
|
|
|
|
(t/deftest session-overrides-token-and-key
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [session-pid (uuid/custom 1 1)
|
|
token-pid (uuid/custom 2 2)
|
|
handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::session/profile-id session-pid
|
|
::actoken/profile-id token-pid
|
|
::http/auth-key-id "key-id"}})]
|
|
(t/is (= session-pid (::rpc/profile-id data)))
|
|
(t/is (= :session (::rpc/auth-type data))))))
|
|
|
|
;; --- REQUEST METADATA: SESSION-ID
|
|
|
|
(t/deftest session-id-parsed-from-header
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [sid (uuid/next)
|
|
handler (make-handler)
|
|
data (call-body handler
|
|
{:headers {"x-session-id" (str sid)}})]
|
|
(t/is (= sid (::rpc/session-id data))))))
|
|
|
|
(t/deftest session-id-nil-when-no-header
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler)
|
|
data (call-body handler)]
|
|
(t/is (nil? (::rpc/session-id data))))))
|
|
|
|
(t/deftest session-id-nil-when-invalid-uuid
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler)
|
|
data (call-body handler
|
|
{:headers {"x-session-id" "not-a-uuid"}})]
|
|
(t/is (nil? (::rpc/session-id data))))))
|
|
|
|
(t/deftest session-id-nil-for-empty-string
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler)
|
|
data (call-body handler
|
|
{:headers {"x-session-id" ""}})]
|
|
(t/is (nil? (::rpc/session-id data))))))
|
|
|
|
(t/deftest session-id-parses-uppercase-uuid
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [sid (uuid/next)
|
|
handler (make-handler)
|
|
upper (clojure.string/upper-case (str sid))
|
|
data (call-body handler
|
|
{:headers {"x-session-id" upper}})]
|
|
(t/is (= sid (::rpc/session-id data))))))
|
|
|
|
;; --- REQUEST METADATA: ETAG / COND KEY
|
|
|
|
(t/deftest cond-key-from-if-none-match
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler)
|
|
data (call-body handler
|
|
{:headers {"if-none-match" "W/\"abc123\""}})]
|
|
(t/is (= "W/\"abc123\"" (::cond/key data))))))
|
|
|
|
(t/deftest cond-key-nil-when-no-header
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler)
|
|
data (call-body handler)]
|
|
(t/is (nil? (::cond/key data))))))
|
|
|
|
;; --- REQUEST METADATA: META
|
|
|
|
(t/deftest data-meta-contains-request
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [request (make-request {})
|
|
handler (make-handler)
|
|
data (call-body handler {})]
|
|
(t/is (= request (::http/request (meta data)))))))
|
|
|
|
;; --- DATA COMPLETENESS
|
|
|
|
(t/deftest data-contains-all-expected-keys
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [pid (uuid/next)
|
|
handler (make-handler)
|
|
data (call-body handler
|
|
{:extras {::session/profile-id pid
|
|
::http/auth-key-id "key"}})]
|
|
(t/is (contains? data ::rpc/handler-name))
|
|
(t/is (contains? data ::rpc/ip-addr))
|
|
(t/is (contains? data ::rpc/request-at))
|
|
(t/is (contains? data ::rpc/request-id))
|
|
(t/is (contains? data ::rpc/session-id))
|
|
(t/is (contains? data ::cond/key))
|
|
(t/is (contains? data ::rpc/profile-id))
|
|
(t/is (contains? data ::rpc/auth-type))
|
|
(t/is (contains? data ::rpc/auth-key-id)))))
|
|
|
|
;; --- GET/HEAD RESTRICTION
|
|
|
|
(t/deftest get-restriction-non-get-handler
|
|
(let [handler (make-handler)
|
|
ex (call-ex handler
|
|
{:method :get
|
|
:path-params {:method-name "foo"}})]
|
|
(t/is (some? ex))
|
|
(t/is (th/ex-of-type? ex :restriction))
|
|
(t/is (th/ex-of-code? ex :method-not-allowed))))
|
|
|
|
(t/deftest head-restriction-non-get-handler
|
|
(let [handler (make-handler)
|
|
ex (call-ex handler
|
|
{:method :head
|
|
:path-params {:method-name "foo"}})]
|
|
(t/is (some? ex))
|
|
(t/is (th/ex-of-type? ex :restriction))
|
|
(t/is (th/ex-of-code? ex :method-not-allowed))))
|
|
|
|
(t/deftest get-allowed-for-get-prefixed-handler
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler {:get-profile [{} echo-handler]})
|
|
data (call-body handler
|
|
{:method :get
|
|
:path-params {:method-name "get-profile"}})]
|
|
(t/is (= "get-profile" (::rpc/handler-name data))))))
|
|
|
|
(t/deftest head-allowed-for-get-prefixed-handler
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler {:get-files [{} echo-handler]})
|
|
data (call-body handler
|
|
{:method :head
|
|
:path-params {:method-name "get-files"}})]
|
|
(t/is (= "get-files" (::rpc/handler-name data))))))
|
|
|
|
(t/deftest post-always-allowed
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler {:any-name [{} echo-handler]})
|
|
data (call-body handler
|
|
{:method :post
|
|
:path-params {:method-name "any-name"}})]
|
|
(t/is (= "any-name" (::rpc/handler-name data))))))
|
|
|
|
(t/deftest put-always-allowed
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler {:any-name [{} echo-handler]})
|
|
data (call-body handler
|
|
{:method :put
|
|
:path-params {:method-name "any-name"}})]
|
|
(t/is (= "any-name" (::rpc/handler-name data))))))
|
|
|
|
(t/deftest delete-always-allowed
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler {:any-name [{} echo-handler]})
|
|
data (call-body handler
|
|
{:method :delete
|
|
:path-params {:method-name "any-name"}})]
|
|
(t/is (= "any-name" (::rpc/handler-name data))))))
|
|
|
|
(t/deftest patch-always-allowed
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [handler (make-handler {:any-name [{} echo-handler]})
|
|
data (call-body handler
|
|
{:method :patch
|
|
:path-params {:method-name "any-name"}})]
|
|
(t/is (= "any-name" (::rpc/handler-name data))))))
|
|
|
|
(t/deftest get-to-non-get-handler-restriction-checks-before-handler-execution
|
|
(let [called? (atom false)
|
|
handler (make-handler {:foo [{} (fn [data]
|
|
(reset! called? true)
|
|
data)]})
|
|
ex (call-ex handler
|
|
{:method :get
|
|
:path-params {:method-name "foo"}})]
|
|
(t/is (some? ex))
|
|
(t/is (false? @called?))))
|
|
|
|
;; --- RESPONSE: NIL -> 204
|
|
|
|
(t/deftest nil-response-returns-204
|
|
(let [handler (make-handler {:nil-method [{} (constantly nil)]})
|
|
response (call handler
|
|
{:path-params {:method-name "nil-method"}})]
|
|
(t/is (= 204 (::yres/status response)))))
|
|
|
|
(t/deftest nil-response-gets-stream-content-type
|
|
(let [handler (make-handler {:nil-method [{} (constantly nil)]})
|
|
response (call handler
|
|
{:path-params {:method-name "nil-method"}})]
|
|
(t/is (= "application/octet-stream"
|
|
(get (::yres/headers response) "content-type")))))
|
|
|
|
;; --- RESPONSE: MAP -> 200
|
|
|
|
(t/deftest map-response-returns-200
|
|
(let [handler (make-handler {:map-method [{} (constantly {:foo "bar"})]})
|
|
response (call handler
|
|
{:path-params {:method-name "map-method"}})]
|
|
(t/is (= 200 (::yres/status response)))
|
|
(t/is (= {:foo "bar"} (::yres/body response)))))
|
|
|
|
;; --- RESPONSE: FALSY NON-NIL VALUES
|
|
|
|
(t/deftest false-response-returns-200
|
|
(let [handler (make-handler {:false-method [{} (constantly false)]})
|
|
response (call handler
|
|
{:path-params {:method-name "false-method"}})]
|
|
(t/is (= 200 (::yres/status response)))
|
|
(t/is (false? (::yres/body response)))))
|
|
|
|
(t/deftest zero-response-returns-200
|
|
(let [handler (make-handler {:zero-method [{} (constantly 0)]})
|
|
response (call handler
|
|
{:path-params {:method-name "zero-method"}})]
|
|
(t/is (= 200 (::yres/status response)))
|
|
(t/is (= 0 (::yres/body response)))))
|
|
|
|
(t/deftest empty-map-response-returns-200
|
|
(let [handler (make-handler {:empty-method [{} (constantly {})]})
|
|
response (call handler
|
|
{:path-params {:method-name "empty-method"}})]
|
|
(t/is (= 200 (::yres/status response)))
|
|
(t/is (= {} (::yres/body response)))))
|
|
|
|
;; --- RESPONSE: CUSTOM STATUS
|
|
|
|
(t/deftest custom-status-from-metadata
|
|
(let [result (with-meta {:ok true} {::http/status 201})
|
|
handler (make-handler {:created [{} (constantly result)]})
|
|
response (call handler
|
|
{:path-params {:method-name "created"}})]
|
|
(t/is (= 201 (::yres/status response)))))
|
|
|
|
;; --- RESPONSE: CUSTOM HEADERS
|
|
|
|
(t/deftest custom-headers-from-metadata
|
|
(let [result (with-meta {:ok true} {::http/headers {"x-custom" "val"}})
|
|
handler (make-handler {:hdr-method [{} (constantly result)]})
|
|
response (call handler
|
|
{:path-params {:method-name "hdr-method"}})]
|
|
(t/is (= "val" (get (::yres/headers response) "x-custom")))))
|
|
|
|
;; --- RESPONSE: CUSTOM STATUS AND HEADERS TOGETHER
|
|
|
|
(t/deftest custom-status-and-headers-from-metadata
|
|
(let [result (with-meta {:ok true}
|
|
{::http/status 202
|
|
::http/headers {"x-request-id" "req-123"}})
|
|
handler (make-handler {:both [{} (constantly result)]})
|
|
response (call handler
|
|
{:path-params {:method-name "both"}})]
|
|
(t/is (= 202 (::yres/status response)))
|
|
(t/is (= "req-123" (get (::yres/headers response) "x-request-id")))))
|
|
|
|
;; --- RESPONSE: FUNCTION
|
|
|
|
(t/deftest function-response-called-with-request
|
|
(let [captured (atom nil)
|
|
resp-fn (fn [req]
|
|
(reset! captured req)
|
|
{::yres/status 200
|
|
::yres/body "from-fn"})
|
|
handler (make-handler {:fn-method [{} (constantly resp-fn)]})
|
|
request (make-request {:path-params {:method-name "fn-method"}})
|
|
response (handler request)]
|
|
(t/is (= request @captured))
|
|
(t/is (= 200 (::yres/status response)))
|
|
(t/is (= "from-fn" (::yres/body response)))))
|
|
|
|
(t/deftest function-response-returning-nil
|
|
(let [handler (make-handler {:fn-nil [{} (constantly (fn [_] nil))]})
|
|
response (call handler
|
|
{:path-params {:method-name "fn-nil"}})]
|
|
(t/is (nil? (::yres/body response)))))
|
|
|
|
(t/deftest function-response-uses-return-value-as-is
|
|
(let [resp-fn (fn [_req]
|
|
{:result "ok"})
|
|
handler (make-handler {:fn-meta [{} (constantly resp-fn)]})
|
|
response (call handler
|
|
{:path-params {:method-name "fn-meta"}})]
|
|
;; When handler returns a fn, handle-response calls (fn request)
|
|
;; and uses the return value directly as the response.
|
|
(t/is (= {:result "ok"} response))))
|
|
|
|
;; --- RESPONSE: METADATA WRAPPER
|
|
|
|
(t/deftest metadata-wrapper-unwrapped-in-response
|
|
(let [wrapped (rph/wrap {:data "value"})
|
|
handler (make-handler {:wrap-method [{} (constantly wrapped)]})
|
|
response (call handler
|
|
{:path-params {:method-name "wrap-method"}})]
|
|
(t/is (= {:data "value"} (::yres/body response)))))
|
|
|
|
(t/deftest metadata-wrapper-preserves-metadata
|
|
(let [wrapped (-> (rph/wrap {:data "value"})
|
|
(vary-meta assoc ::http/status 201))
|
|
handler (make-handler {:wrap-meta [{} (constantly wrapped)]})
|
|
response (call handler
|
|
{:path-params {:method-name "wrap-meta"}})]
|
|
(t/is (= 201 (::yres/status response)))))
|
|
|
|
(t/deftest metadata-wrapper-with-custom-headers
|
|
(let [wrapped (-> (rph/wrap {:data "value"})
|
|
(vary-meta assoc ::http/headers {"x-wrap" "yes"}))
|
|
handler (make-handler {:wrap-hdr [{} (constantly wrapped)]})
|
|
response (call handler
|
|
{:path-params {:method-name "wrap-hdr"}})]
|
|
(t/is (= "yes" (get (::yres/headers response) "x-wrap")))))
|
|
|
|
;; --- RESPONSE: STREAM BODY
|
|
|
|
(t/deftest stream-response-default-content-type
|
|
(let [stream (yres/stream-body (fn [_response _output] nil))
|
|
handler (make-handler {:stream-method [{} (constantly stream)]})
|
|
response (call handler
|
|
{:path-params {:method-name "stream-method"}})]
|
|
(t/is (= "application/octet-stream"
|
|
(get (::yres/headers response) "content-type")))))
|
|
|
|
(t/deftest stream-response-preserves-existing-content-type
|
|
(let [stream (with-meta
|
|
(yres/stream-body (fn [_response _output] nil))
|
|
{::http/headers {"content-type" "text/plain"}})
|
|
handler (make-handler {:stream-ct [{} (constantly stream)]})
|
|
response (call handler
|
|
{:path-params {:method-name "stream-ct"}})]
|
|
(t/is (= "text/plain"
|
|
(get (::yres/headers response) "content-type")))))
|
|
|
|
;; --- RESPONSE TRANSFORMATION
|
|
|
|
(t/deftest response-transform-fns-applied
|
|
(let [transform (fn [_req resp]
|
|
(assoc-in resp [::yres/headers "x-transformed"] "yes"))
|
|
result (with-meta {:ok true}
|
|
{::rpc/response-transform-fns [transform]})
|
|
handler (make-handler {:transform [{} (constantly result)]})
|
|
response (call handler
|
|
{:path-params {:method-name "transform"}})]
|
|
(t/is (= "yes" (get (::yres/headers response) "x-transformed")))))
|
|
|
|
(t/deftest response-transform-fns-applied-in-order
|
|
(let [t1 (fn [_req resp]
|
|
(assoc-in resp [::yres/headers "x-step"] "1"))
|
|
t2 (fn [_req resp]
|
|
(update-in resp [::yres/headers "x-step"] str "-2"))
|
|
result (with-meta {:ok true}
|
|
{::rpc/response-transform-fns [t1 t2]})
|
|
handler (make-handler {:multi-transform [{} (constantly result)]})
|
|
response (call handler
|
|
{:path-params {:method-name "multi-transform"}})]
|
|
(t/is (= "1-2" (get (::yres/headers response) "x-step")))))
|
|
|
|
(t/deftest transform-fn-can-modify-status
|
|
(let [transform (fn [_req _resp]
|
|
{::yres/status 418
|
|
::yres/body "teapot"
|
|
::yres/headers {}})
|
|
result (with-meta {:ok true}
|
|
{::rpc/response-transform-fns [transform]})
|
|
handler (make-handler {:teapot [{} (constantly result)]})
|
|
response (call handler
|
|
{:path-params {:method-name "teapot"}})]
|
|
(t/is (= 418 (::yres/status response)))))
|
|
|
|
(t/deftest empty-transform-fns-no-op
|
|
(let [result (with-meta {:ok true}
|
|
{::rpc/response-transform-fns []})
|
|
handler (make-handler {:empty-t [{} (constantly result)]})
|
|
response (call handler
|
|
{:path-params {:method-name "empty-t"}})]
|
|
(t/is (= 200 (::yres/status response)))))
|
|
|
|
(t/deftest transform-receives-request-and-response
|
|
(let [captured-req (atom nil)
|
|
captured-resp (atom nil)
|
|
transform (fn [req resp]
|
|
(reset! captured-req req)
|
|
(reset! captured-resp resp)
|
|
resp)
|
|
result (with-meta {:ok true}
|
|
{::rpc/response-transform-fns [transform]})
|
|
handler (make-handler {:cap-t [{} (constantly result)]})
|
|
request (make-request {:path-params {:method-name "cap-t"}})
|
|
response (handler request)]
|
|
(t/is (= request @captured-req))
|
|
(t/is (some? @captured-resp))
|
|
(t/is (= 200 (::yres/status @captured-resp)))))
|
|
|
|
;; --- BEFORE-COMPLETE HOOKS
|
|
|
|
(t/deftest before-complete-hooks-called
|
|
(let [called? (atom false)
|
|
hook (fn [] (reset! called? true))
|
|
result (with-meta {:ok true}
|
|
{::rpc/before-complete-fns [hook]})
|
|
handler (make-handler {:hook-method [{} (constantly result)]})]
|
|
(call handler {:path-params {:method-name "hook-method"}})
|
|
(t/is (true? @called?))))
|
|
|
|
(t/deftest multiple-hooks-all-called
|
|
(let [called-a (atom false)
|
|
called-b (atom false)
|
|
hook-a (fn [] (reset! called-a true))
|
|
hook-b (fn [] (reset! called-b true))
|
|
result (with-meta {:ok true}
|
|
{::rpc/before-complete-fns [hook-a hook-b]})
|
|
handler (make-handler {:multi-hook [{} (constantly result)]})]
|
|
(call handler {:path-params {:method-name "multi-hook"}})
|
|
(t/is (true? @called-a))
|
|
(t/is (true? @called-b))))
|
|
|
|
(t/deftest before-complete-hook-errors-ignored
|
|
(let [hook (fn [] (throw (Exception. "hook error")))
|
|
result (with-meta {:ok true}
|
|
{::rpc/before-complete-fns [hook]})
|
|
handler (make-handler {:hook-err [{} (constantly result)]})]
|
|
(let [response (call handler
|
|
{:path-params {:method-name "hook-err"}})]
|
|
(t/is (= 200 (::yres/status response))))))
|
|
|
|
(t/deftest hook-error-does-not-prevent-other-hooks
|
|
(let [called? (atom false)
|
|
hook-a (fn [] (throw (Exception. "boom")))
|
|
hook-b (fn [] (reset! called? true))
|
|
result (with-meta {:ok true}
|
|
{::rpc/before-complete-fns [hook-a hook-b]})
|
|
handler (make-handler {:hook-chain [{} (constantly result)]})]
|
|
(call handler {:path-params {:method-name "hook-chain"}})
|
|
(t/is (true? @called?))))
|
|
|
|
(t/deftest empty-hooks-no-op
|
|
(let [result (with-meta {:ok true}
|
|
{::rpc/before-complete-fns []})
|
|
handler (make-handler {:empty-hooks [{} (constantly result)]})
|
|
response (call handler
|
|
{:path-params {:method-name "empty-hooks"}})]
|
|
(t/is (= 200 (::yres/status response)))))
|
|
|
|
;; --- METHODS MAP FORMAT
|
|
|
|
(t/deftest methods-map-uses-peek-to-extract-handler
|
|
(with-redefs [ct/now (constantly fixed-time)
|
|
uuid/next (constantly fixed-uuid)
|
|
inet/parse-request (constantly "127.0.0.1")]
|
|
(let [metadata {:some-meta "value"}
|
|
methods {:peek-test [metadata echo-handler]}
|
|
handler (rpc/make-rpc-handler methods)
|
|
data (call-body handler
|
|
{:path-params {:method-name "peek-test"}})]
|
|
(t/is (= "peek-test" (::rpc/handler-name data))))))
|
|
|
|
;; --- COND/*ENABLED* BINDING
|
|
|
|
(t/deftest cond-enabled-bound-to-true-inside-handler
|
|
(let [captured (atom nil)
|
|
handler (make-handler
|
|
{:cond-test [{} (fn [data]
|
|
(reset! captured cond/*enabled*)
|
|
data)]})]
|
|
(call handler {:path-params {:method-name "cond-test"}})
|
|
(t/is (true? @captured))))
|
|
|
|
(t/deftest cond-enabled-false-outside-handler
|
|
(let [handler (make-handler)]
|
|
(call handler)
|
|
(t/is (false? cond/*enabled*))))
|
|
|
|
(t/deftest cond-enabled-restored-after-handler-throws
|
|
(let [handler (make-handler
|
|
{:throwing [{} (fn [_]
|
|
(ex/raise :type :test :code :x))]})]
|
|
(try (call handler {:path-params {:method-name "throwing"}})
|
|
(catch Exception _))
|
|
(t/is (false? cond/*enabled*))))
|
|
|
|
;; --- HANDLER EXCEPTION PROPAGATION
|
|
|
|
(t/deftest handler-exception-propagates-with-type-and-code
|
|
(let [handler (make-handler
|
|
{:fail [{} (fn [_]
|
|
(ex/raise :type :test
|
|
:code :boom))]})
|
|
ex (call-ex handler {:path-params {:method-name "fail"}})]
|
|
(t/is (some? ex))
|
|
(t/is (th/ex-of-type? ex :test))
|
|
(t/is (th/ex-of-code? ex :boom))))
|
|
|
|
;; --- DEFAULT HANDLER
|
|
|
|
(t/deftest default-handler-for-missing-method
|
|
(let [handler (rpc/make-rpc-handler {})
|
|
ex (try (handler (make-request
|
|
{:path-params {:method-name "nonexistent"}}))
|
|
(catch Exception e e))]
|
|
(t/is (some? ex))
|
|
(t/is (th/ex-of-type? ex :not-found))))
|
|
|
|
;; --- TOKEN TYPE RESTRICTIONS
|
|
|
|
(t/deftest mcp-token-authentication
|
|
(let [pid (uuid/custom 1 1)
|
|
method (fn [mdata]
|
|
(rpc/make-rpc-handler
|
|
{:test-method [mdata (partial (#'rpc/wrap-authentication
|
|
nil (fn [_ params] params) mdata)
|
|
nil)]}))
|
|
handler (method {::rpc/auth-type :token ::rpc/token-type "mcp"})]
|
|
(doseq [extras [{::session/profile-id pid}
|
|
{::actoken/profile-id pid}
|
|
{::actoken/profile-id pid ::actoken/type "personal"}]]
|
|
(t/is (th/ex-of-code? (call-ex handler {:extras extras}) :token-auth-required)))
|
|
(let [extras {::actoken/profile-id pid ::actoken/type "mcp"}]
|
|
(t/is (= "mcp" (::rpc/token-type (call-body handler {:extras extras}))))
|
|
(t/is (th/ex-of-code? (call-ex (method {}) {:extras extras}) :token-auth-required)))
|
|
(t/is (= pid (::rpc/profile-id
|
|
(call-body (method {}) {:extras {::actoken/profile-id pid
|
|
::actoken/type "personal"}}))))))
|
|
|
|
;; --- SHARED KEY AUTHORIZATION
|
|
|
|
(defn- call-shared-key-wrapper
|
|
[mdata params]
|
|
(let [wrapped (#'rpc/wrap-shared-key-authorization
|
|
nil (fn [_ params] params) mdata)]
|
|
(wrapped nil params)))
|
|
|
|
(t/deftest shared-key-authorization-allows-declared-keys
|
|
(let [mdata {::rpc/allowed-shared-keys #{:exporter :media-processor}}]
|
|
(t/is (= :exporter
|
|
(::rpc/auth-key-id
|
|
(call-shared-key-wrapper mdata {::rpc/auth-key-id :exporter}))))
|
|
(t/is (= :media-processor
|
|
(::rpc/auth-key-id
|
|
(call-shared-key-wrapper mdata {::rpc/auth-key-id :media-processor}))))))
|
|
|
|
(t/deftest shared-key-authorization-rejects-other-key
|
|
(let [ex (try (call-shared-key-wrapper {::rpc/allowed-shared-keys #{:exporter}}
|
|
{::rpc/auth-key-id :nexus})
|
|
(catch Exception e e))]
|
|
(t/is (th/ex-of-type? ex :authorization))
|
|
(t/is (th/ex-of-code? ex :shared-key-not-allowed))))
|
|
|
|
(t/deftest shared-key-authorization-rejects-missing-key
|
|
(let [ex (try (call-shared-key-wrapper {::rpc/allowed-shared-keys #{:exporter}} {})
|
|
(catch Exception e e))]
|
|
(t/is (th/ex-of-type? ex :authorization))
|
|
(t/is (th/ex-of-code? ex :shared-key-not-allowed))))
|
|
|
|
(t/deftest shared-key-authorization-without-metadata-allows-any-key
|
|
(t/is (= :nexus
|
|
(::rpc/auth-key-id
|
|
(call-shared-key-wrapper {} {::rpc/auth-key-id :nexus}))))
|
|
(t/is (nil? (::rpc/auth-key-id (call-shared-key-wrapper {} {})))))
|
|
|
|
(t/deftest shared-key-authorization-empty-set-denies-all
|
|
(let [ex (try (call-shared-key-wrapper {::rpc/allowed-shared-keys #{}}
|
|
{::rpc/auth-key-id :exporter})
|
|
(catch Exception e e))]
|
|
(t/is (th/ex-of-type? ex :authorization))
|
|
(t/is (th/ex-of-code? ex :shared-key-not-allowed))))
|
|
|
|
(t/deftest shared-key-authorization-logs-the-denied-key
|
|
(let [records (atom [])
|
|
watch (fn [_ _ _ record] (swap! records conj record))]
|
|
(add-watch l/log-record ::shared-key-denied watch)
|
|
(try
|
|
(try (call-shared-key-wrapper {::rpc/allowed-shared-keys #{:exporter}}
|
|
{::rpc/auth-key-id :nexus
|
|
::rpc/handler-name "test-method"})
|
|
(catch Exception _))
|
|
(let [deadline (+ (System/currentTimeMillis) 2000)
|
|
logged? (fn []
|
|
(some #(and (= :warn (::l/level %))
|
|
(= "shared key is not allowed for this method"
|
|
(get (::l/props %) :hint))
|
|
(= :nexus (get (::l/props %) :key-id))
|
|
(= "test-method" (get (::l/props %) :handler-name)))
|
|
@records))]
|
|
(loop []
|
|
(when-not (or (logged?)
|
|
(>= (System/currentTimeMillis) deadline))
|
|
(Thread/sleep 10)
|
|
(recur)))
|
|
(t/is (some? (logged?))))
|
|
(finally
|
|
(remove-watch l/log-record ::shared-key-denied)))))
|
|
|
|
(t/deftest shared-key-authorization-is-applied-to-rpc-requests
|
|
(let [mdata {::rpc/allowed-shared-keys #{:exporter}}
|
|
wrapped (partial (#'rpc/wrap-shared-key-authorization
|
|
nil (fn [_ params] params) mdata)
|
|
nil)
|
|
handler (make-handler {:shared-key-test [mdata wrapped]})]
|
|
(t/is (= :exporter
|
|
(::rpc/auth-key-id
|
|
(call-body handler
|
|
{:path-params {:method-name "shared-key-test"}
|
|
:extras {::http/auth-key-id :exporter}}))))
|
|
(t/is (th/ex-of-code?
|
|
(call-ex handler
|
|
{:path-params {:method-name "shared-key-test"}
|
|
:extras {::http/auth-key-id :nexus}})
|
|
:shared-key-not-allowed))))
|
|
|
|
;; --- PARAMS VALIDATION: SERVER AUTH CONTEXT VS CLIENT PARAMS
|
|
|
|
(t/deftest client-qualified-keys-do-not-override-server-auth-context
|
|
(let [captured (atom nil)
|
|
inner (fn [_cfg params] (reset! captured params) {:ok true})
|
|
schema [:map [:name :string]]
|
|
wrap-validate @#'rpc/wrap-params-validation
|
|
wrapped (wrap-validate nil inner {::sm/params schema})
|
|
server-pid (uuid/custom 1 1)
|
|
attacker-pid (uuid/custom 2 2)
|
|
request {:params {:name "valid"
|
|
:app.rpc/profile-id attacker-pid
|
|
:app.rpc/auth-type :token
|
|
:app.rpc/token-perms #{"admin"}
|
|
:app.rpc/token-type "mcp"}}
|
|
params (with-meta {::rpc/profile-id server-pid
|
|
::rpc/auth-type :session}
|
|
{::http/request request})]
|
|
(wrapped nil params)
|
|
(t/is (= server-pid (::rpc/profile-id @captured)))
|
|
(t/is (= :session (::rpc/auth-type @captured)))
|
|
(t/is (not (contains? @captured ::rpc/token-perms)))
|
|
(t/is (not (contains? @captured ::rpc/token-type)))
|
|
(t/is (= "valid" (:name @captured)))))
|
|
|
|
;; --- RESPONSE: NIL BODY WITH CUSTOM HEADERS
|
|
|
|
(t/deftest nil-body-with-custom-headers
|
|
(let [wrapped (-> (rph/wrap nil)
|
|
(vary-meta assoc ::http/headers {"x-custom" "val"}))
|
|
handler (make-handler {:nil-hdr [{} (constantly wrapped)]})
|
|
response (call handler
|
|
{:path-params {:method-name "nil-hdr"}})]
|
|
(t/is (= 204 (::yres/status response)))
|
|
(t/is (= "val" (get (::yres/headers response) "x-custom")))))
|