Policy Syntax
Introduction
In the context of Fluree, "Policy" is a term used to describe read/write or access controls enforced on the data in your ledger. However, unlike the limitations enforced in a simple SQL database, policy logic is transacted as data into the ledger itself.
Policy is therefore a self-defence measure implemented by the state of the data itself, limiting who can read, write, and access the contents of your ledger.
Transaction Syntax for Policy
The following examples are pulled from the Fluree cookbook, which you can use to recreate these examples and play around with the syntax yourself.
Transaction Object
Policy transactions are JSON with the following keys:
key | required? | type | required values |
---|---|---|---|
@context | yes | string , array or object | Typically "https://ns.flur.ee" , but any context is valid (provided fluree-reserved IRIs like f:allow are expanded appropriately) |
ledger | yes | string | Name of ledger |
insert | yes | array or object | This includes the id for the policy, instantiates it's type, describes the permissions associated with the targetClass or targetNode |
@id | yes | string , array or object | the name of your policy |
@type | yes | array or object | Must always be ["f:Policy"] , this is an internal fluree class used to handle access policy evalution for transactions and queries |
f:targetNode or f:targetClass | yes | array or object | Can be ["f:allNodes] or the IRI of a class, node, or user. Specifies what data is referenced in a policy. |
f:allow | yes | array or object | Array or object contianing the f:action , and f:targetRole or f:targetNode |
f:targetRole | yes | array or object | {"@id": <targetRole>} |
f:action | yes | array or object | "f:view" or "f:modify" |
f:property | no | array or object | Typically contains object for "f:path" and "f:allow" |
f:path | no | array or object | Any valid IRI |
f:equals | no | object | Typically continas an @list key with objects mapping out the logic of data state relations that enforce policy (see more in the f:equals section below) |
Use Case: Transacting Root Access Policy
Let's break down the use of these keys in constructing an example policy transaction:
-
Transact policy with
"@id": "ex:rootPolicy"
-
Instantiate data will be
"@type": ["f:Policy"]
-
Identify which data this policy will pertain to, in this case it's
"f:targetNode"
will be"f:allNodes"
-
After establishing that we are transacting a new policy that will apply to all nodes, we have to explain what the policy actually does. This logic will always fall inside of the
"f:allow"
object- here we are creating a new line of logic for our policy assocated with the
@id
value"ex:rootAccessAllow"
. - This new policy adds a
"f:targetRole"
with the@id
value"ex:rootRole"
- And finally, we make it so that any user with the
"ex:rootRole"
can perform view and modify actions on all data
- here we are creating a new line of logic for our policy assocated with the
{ "@context": "https://ns.flur.ee", "ledger": "cookbook/base", "insert": { "@id": "ex:rootPolicy", "@type": ["f:Policy"], "f:targetNode": { "@id": "f:allNodes" }, "f:allow": [ { "@id": "ex:rootAccessAllow", "f:targetRole": { "@id": "ex:rootRole" }, "f:action": [{ "@id": "f:view" }, { "@id": "f:modify" }] } ] }}
Once the policy is written, you can transact the users, like ex:andrew
, to have assigned roles. Users will be identified with a fluree did, which users can generate from our npm library (NOTE: you will append did:fluree
to the value generated).
{ "@context": "https://ns.flur.ee", "ledger": "cookbook/base", "insert": { "@id": "did:fluree:TfCzWTrXqF16hvKGjcYiLxRoYJ1B8a6UMH6", "ex:user": { "@id": "ex:andrew" }, "f:role": { "@id": "ex:rootRole" } }}
From this point forward, you will only be able to query or transact data, using your user did
, to your ledger with the role ex:rootRole
.
{ "from": "cookbook/base", "where": { "@id": "?s", "schema:name": "?name" }, "select": { "?s": ["*"] }, "opts": { "role": "ex:rootRole", "did": "did:fluree:TfCzWTrXqF16hvKGjcYiLxRoYJ1B8a6UMH6" }}
Transactions that violate policies will fail with the appropriate error. However, for security reasons, the responses for queries that violate a policy will only return data allowed as determined by the policy. For this reason, you should always endevor to test that your policy does and does not work as expected.
Use Case: Limiting Access
Policies can include complicated logic defining read and write permissions. This example from the cookbook below states:
-
users with a
yetiRole
-
are allowed to view data
-
associated with the class
ex:Yeti
-
except that which pertains to the
schema:age
of a user other than the one submitting the query.
{ "@context": "https://ns.flur.ee", "ledger": "cookbook/base", "insert": { "@id": "ex:yetiPolicy", "@type": ["f:Policy"], "f:targetClass": { "@id": "ex:Yeti" }, "f:allow": [ { "@id": "ex:yetiViewAllow", "f:targetRole": { "@id": "ex:yetiRole" }, "f:action": [{ "@id": "f:view" }] } ], "f:property": [ { "@id": "ex:yetisViewOnlyOwnAge", "f:path": { "@id": "schema:age" }, "f:allow": [ { "@id": "ex:ageViewRule", "f:targetRole": { "@id": "ex:yetiRole" }, "f:action": [{ "@id": "f:view" }], "f:equals": { "@list": [{ "@id": "f:$identity" }, { "@id": "ex:user" }] } } ] } ] }}
This additional complexity, where a user is only allowed to view data associated with their own profile, is outlined in the f:property
object in the query above.
Here we use a binding for f:equals
where f:$identity
is a placeholder for information about the user. This is possible because data and access evaluation happens inside the data, so we can use datastate
or relationships that may or may not exist in the data to determine access.
Similarly to our first example, after writing an identity policy we can now transact user roles into our ledger, enabling us to query our newly permissioned data:
{ "@context": "https://ns.flur.ee", "ledger": "cookbook/base", "insert": { "@id": "did:fluree:Tf5M4L7SNkziB4Q5gC8Hjuqu9WQKCwKpU1Y", "ex:user": { "@id": "ex:freddy" }, "f:role": { "@id": "ex:yetiRole" } }}
For reference, here's an example query that would return limited results based on the provided role, ex:yetiRole
.
{ "from": "cookbook/base", "where": { "@id": "?s", "schema:name": "?name" }, "select": { "?s": ["*"] }, "opts": { "did": "did:fluree:Tf5M4L7SNkziB4Q5gC8Hjuqu9WQKCwKpU1Y", "role": "ex:yetiRole" }}