Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable policy opts in query and transact #46

Merged
merged 5 commits into from
Apr 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
/target/*
.clj-kondo/babashka
.clj-kondo/metosin
.dir-locals.el
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the tiniest of nitpicks but these editor-specific ignores might be better in your global gitignore (e.g. ~/.gitignore which I think you have to then tell git to use in its config). However, I know Emacs is a pretty popular choice around here and for Clojure in general 😉 so I'm fine with it either way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have this in our gitignores on db and json-ld, so I actually originally assumed its absence here was an oversight.

Emacs is pretty popular among lispers, so it's convenient to have it, but I won't die on this hill either 😄

2 changes: 1 addition & 1 deletion deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
metosin/spec-tools {:mvn/version "0.10.5"}
ring-cors/ring-cors {:mvn/version "0.1.13"}
com.fluree/db {:git/url "https://github.com/fluree/db.git"
:git/sha "269c71e1174be615265abb8e25c52da549f13175"}}
:git/sha "f394d7867dee564b66943c17f3227d79787eb49c"}}

:aliases
{:dev
Expand Down
4 changes: 3 additions & 1 deletion src/fluree/http_api/components/http.clj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
(s/def ::ledger ::non-empty-string)
(s/def ::txn (s/or :single-map map? :collection-of-maps (s/coll-of map?)))
(s/def ::defaultContext any?)
(s/def ::opts map?)

(def server
#::ds{:start (fn [{{:keys [handler options]} ::ds/config}]
Expand Down Expand Up @@ -202,7 +203,8 @@
:handler ledger/create}}]
["/transact"
{:post {:summary "Endpoint for submitting transactions"
:parameters {:body (s/keys :req-un [::ledger ::txn])}
:parameters {:body (s/keys :req-un [::ledger ::txn]
:opt-un [::opts])}
:responses {200 {:body (s/keys :opt-un [::address ::id]
:req-un [::alias ::t])}
400 {:body string?}
Expand Down
21 changes: 15 additions & 6 deletions src/fluree/http_api/handlers/ledger.clj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
(assoc m* (keyword k) v))
{} m))

(defn transform-policy-opts
[opts]
(or (not-empty (select-keys opts [:role :did]))
(let [{:strs [role did] :as policy-opts} opts]
(keywordize-keys policy-opts))))

(defn deref!
"Derefs promise p and throws if the result is an exception, returns it otherwise."
[p]
Expand Down Expand Up @@ -41,20 +47,23 @@
:body {:error (ex-message t)}}}))))))

(defn txn-body->opts
[{:keys [defaultContext txn] :as _body}]
[{:keys [defaultContext txn opts] :as _body}]
(let [first-txn (if (map? txn)
txn
(first txn))]
(cond-> {}
(first txn))
policy-opts (transform-policy-opts opts)]
(cond-> policy-opts
(-> first-txn keys first keyword?) (assoc :context-type :keyword)
(-> first-txn keys first string?) (assoc :context-type :string)
defaultContext (assoc :defaultContext defaultContext))))

(defn query-body->opts
[{:keys [query] :as _body}]
(cond-> {}
(-> query keys first keyword?) (assoc :context-type :keyword)
(-> query keys first string?) (assoc :context-type :string)))
(let [policy-opts (transform-policy-opts (or (get query "opts")
(get query :opts)))]
(cond-> policy-opts
(-> query keys first keyword?) (assoc :context-type :keyword)
(-> query keys first string?) (assoc :context-type :string))))

(defn ledger-summary
[db]
Expand Down
210 changes: 210 additions & 0 deletions test/fluree/http_api/system_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,213 @@
:rdf/type [:schema/Test]}]
:f/retract []}}}]
(-> query-res :body edn/read-string))))))


(deftest ^:integration ^:json policy-opts-test
(testing "policy-enforcing opts are correctly handled"
(let [ledger-name (create-rand-ledger "policy-opts-test")
json-headers {"Content-Type" "application/json"
"Accept" "application/json"}
alice-did "did:fluree:Tf6i5oh2ssYNRpxxUM2zea1Yo7x4uRqyTeU"
txn-req {:body
(json/write-value-as-string
{:ledger ledger-name
:txn [{"id" "ex:alice"
"type" "ex:User"
"ex:secret" "alice's secret"}
{"id" "ex:bob"
"type" "ex:User"
"ex:secret" "bob's secret"}
{"id" "ex:UserPolicy"
"type" ["f:Policy"]
"f:targetClass" {"id" "ex:User"}
"f:allow"
[{"id" "ex:globalViewAllow"
"f:targetRole" {"id" "ex:userRole"}
"f:action" [{"id" "f:view"}]}]
"f:property"
[{"f:path" {"id" "ex:secret"}
"f:allow"
[{"id" "ex:secretsRule"
"f:targetRole" {"id" "ex:userRole"}
"f:action" [{"id" "f:view"} {"id" "f:modify"}]
"f:equals" {"@list" [{"id" "f:$identity"}{"id" "ex:User"}]}}]}]}
{"id" alice-did
"ex:User" {"id" "ex:alice"}
"f:role" {"id" "ex:userRole"}}]})
:headers json-headers}
txn-res (post :transact txn-req)
_ (assert (= 200 (:status txn-res)))
secret-query {"select" {"?s" ["*"]}
"where" [["?s" "rdf:type" "ex:User"]]}

query-req {:body
(json/write-value-as-string
{:ledger ledger-name
:query (assoc secret-query
:opts {"role" "ex:userRole"
"did" alice-did})})
:headers json-headers}
query-res (post :query query-req)]
(is (= 200 (:status query-res))
(str "policy-enforced query response was: " (pr-str query-res)))
(is (= [{"id" "ex:bob", "rdf:type" ["ex:User"]}
{"id" "ex:alice",
"rdf:type" ["ex:User"],
"ex:secret" "alice's secret"}]
(-> query-res :body json/read-value))
"query policy opts should prevent seeing bob's secret")
(let [txn-req {:body
(json/write-value-as-string
{:ledger ledger-name
:txn [{"id" "ex:alice"
"ex:secret" "alice's NEW secret"}]
:opts {"role" "ex:userRole"
"did" alice-did}})
:headers json-headers}
txn-res (post :transact txn-req)
_ (assert (= 200 (:status txn-res)))
query-req {:body
(json/write-value-as-string
{:ledger ledger-name
:query secret-query})
:headers json-headers}
query-res (post :query query-req)
_ (assert (= 200 (:status query-res)))]
(is (= [{"id" "ex:bob",
"rdf:type" ["ex:User"],
"ex:secret" "bob's secret"}
{"id" "ex:alice",
"rdf:type" ["ex:User"],
"ex:secret" "alice's NEW secret"}]
(-> query-res :body json/read-value))
"alice's secret should be modified")
(let [txn-req {:body
(json/write-value-as-string
{:ledger ledger-name
:txn [{"id" "ex:bob"
"ex:secret" "bob's new secret"}]
:opts {"role" "ex:userRole"
"did" alice-did}})
:headers json-headers}
txn-res (post :transact txn-req)]
(is (not= 200 (:status txn-res))
(str "transaction policy opts should have prevented modification, instead response was:" (pr-str txn-res)))
(let [query-req {:body
(json/write-value-as-string
{:ledger ledger-name
:query {:history "ex:bob"
:t {:from 1}
:opts {"role" "ex:userRole"
"did" alice-did}}})
:headers json-headers}
query-res (post :history query-req)]
(is (= 200 (:status query-res))
(str "History query response was: " (pr-str query-res)))
(is (= [{"id" "ex:bob", "rdf:type" ["ex:User"]}]
(-> query-res :body json/read-value first (get "f:assert")))
"policy opts should have prevented seeing bob's secret")))))))


(deftest ^:integration ^:edn policy-opts-test
(testing "policy-enforcing opts are correctly handled"
(let [ledger-name (create-rand-ledger "policy-opts-test")
edn-headers {"Content-Type" "application/edn"
"Accept" "application/edn"}
alice-did "did:fluree:Tf6i5oh2ssYNRpxxUM2zea1Yo7x4uRqyTeU"
txn-req {:body
(pr-str
{:ledger ledger-name
:txn [{:id :ex/alice,
:type :ex/User,
:ex/secret "alice's secret"}
{:id :ex/bob,
:type :ex/User,
:ex/secret "bob's secret"}
{:id :ex/UserPolicy,
:type [:f/Policy],
:f/targetClass :ex/User
:f/allow [{:id :ex/globalViewAllow
:f/targetRole :ex/userRole
:f/action [:f/view]}]
:f/property [{:f/path :ex/secret
:f/allow [{:id :ex/secretsRule
:f/targetRole :ex/userRole
:f/action [:f/view :f/modify]
:f/equals {:list [:f/$identity :ex/User]}}]}]}
{:id alice-did
:ex/User :ex/alice
:f/role :ex/userRole}]})
:headers edn-headers}
txn-res (post :transact txn-req)
_ (assert (= 200 (:status txn-res)))
secret-query '{:select {?s [:*]}
:where [[?s :rdf/type :ex/User]]}

query-req {:body
(pr-str
{:ledger ledger-name
:query (assoc secret-query
:opts {:role :ex/userRole
:did alice-did})})
:headers edn-headers}
query-res (post :query query-req)]
(is (= 200 (:status query-res))
(str "policy-enforced query response was: " (pr-str query-res)))
(is (= [{:id :ex/bob
:rdf/type [:ex/User]}
{:id :ex/alice
:rdf/type [:ex/User]
:ex/secret "alice's secret"}]
(-> query-res :body edn/read-string))
"query policy opts should prevent seeing bob's secret")
(let [txn-req {:body
(pr-str
{:ledger ledger-name
:txn [{:id :ex/alice
:ex/secret "alice's NEW secret"}]
:opts {:role :ex/userRole
:did alice-did}})
:headers edn-headers}
txn-res (post :transact txn-req)
_ (assert (= 200 (:status txn-res)))
query-req {:body
(pr-str
{:ledger ledger-name
:query secret-query})
:headers edn-headers}
query-res (post :query query-req)
_ (assert (= 200 (:status query-res)))]
(is (= [{:id :ex/bob
:rdf/type [:ex/User]
:ex/secret "bob's secret"}
{:id :ex/alice
:rdf/type [:ex/User]
:ex/secret "alice's NEW secret"}]
(-> query-res :body edn/read-string))
"alice's secret should be modified")
(let [txn-req {:body
(pr-str
{:ledger ledger-name
:txn [{:id :ex/bob
:ex/secret "bob's NEW secret"}]
:opts {:role :ex/userRole
:did alice-did}})
:headers edn-headers}
txn-res (post :transact txn-req)]
(is (not= 200 (:status txn-res))
(str "transaction policy opts should have prevented modification, instead response was:" (pr-str txn-res)))
(let [query-req {:body
(pr-str
{:ledger ledger-name
:query {:history :ex/bob
:t {:from 1}
:opts {:role :ex/userRole
:did alice-did}}})
:headers edn-headers}
query-res (post :history query-req)]
(is (= 200 (:status query-res))
(str "History query response was: " (pr-str query-res)))
(is (= [{:id :ex/bob :rdf/type [:ex/User]}]
(-> query-res :body edn/read-string first (get :f/assert)))
"policy opts should have prevented seeing bob's secret")))))))