Skip to content

Commit

Permalink
Merge pull request #46 from fluree/feature/policy-opts
Browse files Browse the repository at this point in the history
Enable policy opts in query and transact
  • Loading branch information
mpoffald authored Apr 6, 2023
2 parents e514d07 + 4b141f2 commit 97d4d7a
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 8 deletions.
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
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")))))))

0 comments on commit 97d4d7a

Please sign in to comment.