Created
June 25, 2018 22:22
-
-
Save comnik/4844bc587abf11357b7ee6b39949134c to your computer and use it in GitHub Desktop.
Applying access policies to Datomic queries.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(require '[datomic.api :as d]) | |
(def uri "datomic:mem://policing") | |
(d/create-database uri) | |
(def conn (d/connect uri)) | |
;; We are managing classified documents. | |
(def schema | |
[{:db/ident :level/rank | |
:db/valueType :db.type/long | |
:db/cardinality :db.cardinality/one} | |
{:db/ident :doc/name | |
:db/valueType :db.type/string | |
:db/unique :db.unique/identity | |
:db/cardinality :db.cardinality/one} | |
{:db/ident :doc/content | |
:db/valueType :db.type/string | |
:db/cardinality :db.cardinality/one} | |
{:db/ident :doc/level | |
:db/valueType :db.type/keyword | |
:db/cardinality :db.cardinality/one} | |
{:db/ident :user/name | |
:db/valueType :db.type/string | |
:db/unique :db.unique/identity | |
:db/cardinality :db.cardinality/one} | |
{:db/ident :user/clearance | |
:db/valueType :db.type/keyword | |
:db/cardinality :db.cardinality/one}]) | |
(comment | |
(d/transact conn schema) | |
(d/transact conn | |
[{:db/ident :level/official :level/rank 0} | |
{:db/ident :level/restricted :level/rank 1} | |
{:db/ident :level/top-secret :level/rank 2}]) | |
(d/transact conn | |
[{:user/name "General" :user/clearance :level/top-secret} | |
{:user/name "Midlevel" :user/clearance :level/restricted} | |
{:user/name "Peon" :user/clearance :level/official} | |
{:doc/name "TOP-SECRET PLAN" | |
:doc/level :level/top-secret | |
:doc/content "Attack here."} | |
{:doc/name "COMPROMISING PHOTO" | |
:doc/level :level/restricted | |
:doc/content "..."} | |
{:doc/name "BUS SCHEDULE" | |
:doc/level :level/official | |
:doc/content "Every couple 'o minutes."}]) | |
;; verify | |
(d/q '[:find [?name ...] :where [?lvl :level/rank _] [?lvl :db/ident ?name]] (d/db conn)) | |
(d/q '[:find [?name ...] :where [_ :doc/name ?name]] (d/db conn)) | |
(d/q '[:find [?name ...] :where [_ :user/name ?name]] (d/db conn))) | |
;; Users have read/write access to any document equal to or below | |
;; their clearance level. | |
(def policy | |
'[[(access? ?user ?doc) | |
[?user :user/clearance ?clearance] [?clearance :level/rank ?r1] | |
[?doc :doc/level ?lvl] [?lvl :level/rank ?r2] | |
[(<= ?r2 ?r1)]]]) | |
;; Given arbitrary, flat transaction data, we can determine | |
;; automatically, whether a transaction is authorized. | |
(def tx [{:doc/name "TOP-SECRET" | |
:doc/content "Updated content"}]) | |
(defn write? [db user tx] | |
(let [;; apply w/o affecting database | |
{:keys [db-before tx-data]} (d/with db tx) | |
;; resolve idents | |
user (d/entid db-before user) | |
;; query across db and novelty | |
unauthorized-docs (d/q '[:find [?doc ...] | |
:in $ % ?user [[?doc ?a _ _]] | |
:where | |
[?a :db/ident :doc/content] | |
(not (access? ?user ?doc))] | |
db-before policy user tx-data)] | |
(empty? unauthorized-docs))) | |
(comment | |
(write? (d/db conn) | |
[:user/name "Peon"] | |
'[{:doc/name "TOP-SECRET PLAN" :doc/content "forbidden"}]) | |
(write? (d/db conn) | |
[:user/name "General"] | |
'[{:doc/name "TOP-SECRET PLAN" :doc/content "forbidden"}])) | |
;; What about reading? | |
(comment | |
(let [db (d/db conn) | |
user (d/entid db [:user/name "Peon"])] | |
(d/q '[:find [?content ...] | |
:in $ % ?user | |
:where | |
[?doc :doc/content ?content] | |
(access? ?user ?doc)] | |
db policy user)) | |
(let [db (d/db conn) | |
user (d/entid db [:user/name "Peon"]) | |
accessible-docs (set (d/q '[:find [?doc ...] | |
:in $ % ?user | |
:where (access? ?user ?doc)] db policy user)) | |
filtered-db (d/filter db (fn [db datom] | |
(contains? accessible-docs (.-e datom))))] | |
(d/q '[:find [?content ...] | |
:where [?doc :doc/content ?content]] filtered-db))) | |
;; In general, this becomes non-trivial, as filtering becomes more | |
;; expensive: per-entity policies, policies depending on prior | |
;; interactions, etc.. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment