Skip to main content

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-defense measure implemented by the state of the data itself, limiting who can read, write, and access the contents of your ledger.

Operating Principles

note

The examples below use compact IRIs for the policy vocabulary - https://ns.flur.ee/ is aliased to f.

A Policy is used to decide which flakes can be viewed or modified. Here is the Fluree policy vocabulary:

keyrequired?typeexample values
f:queryyesWhereClause{"type": "@json", "@value": {"where": ["filter", "(= ?$this ?$identity)"]}}
@contextno{[key: string]:TermDef}{"f": "https://ns.flur.ee/"}
@idnoIRI[]"ex:MyPolicy"
@typenoIRI[]"ex:AdminPolicy"
f:actionnoIdMap[]{"@id": "f:view"}, {"@id": "f:modify"}
f:targetSubjectno(IdMap|WhereClause)[]{"@id": "ex:subject1"}, {"@id": "ex:subject2"}, {"@type": "@json", "@value": {"where" [{"@id": "?$target", "@type": "ex:SecretData"}]}}
f:targetPropertyno(IdMap|WhereClause)[]{"@id": "ex:name"}, {"@id": "ex:ssn"}, {"@type": "@json", "@value": {"where" [{"@id": "ex:secretSubject", "?$target": "?o"}]}}
f:requirednobooleantrue
  • f:query This is a Fluree query that is used to determine if a policy allows access to a flake. It is the only required property on a policy. It is stored as an @type @json blob with its own @context that is separate from the query or transaction context.
  • @id (optional) An identifier for the policy.
  • @type (optional) A identifier for a group of policies.
  • f:action (optional) Used to constrain when a policy is evaluated.
    • f:view: only during queries
    • f:modify: only during transactions
      If no f:action is specified then a policy is applied for both operations.
  • f:targetSubject (optional) Either a list of subject ids or a where clause that binds the ?$target variable to the subjects for which the policy should be evaluated.
  • f:targetProperty (optional) Either a list of property ids or a where clause that binds the ?$target variable to the properties for which the policy should be evaluated.
  • f:required (optional) If true, all non-required policies will be ignored. All required policies must evaluate to true to permit access.
  • f:exMessage (optional) A custom error message to return if a policy prevents modification during a transaction.

Policy Lifecycle

Once a policy has been transacted, it can be enforced. A policy cannot apply to the transaction that inserts it.

There are two phases to policy enforcement:

  1. policy targeting
  2. policy evaluation

Policy targeting

Policies are first targeted, so that only policies that apply to the affected flakes are enforced. The affected flakes for a query are any flakes that are pulled out of an index during query execution. The affected flakes for a transaction are any new flakes created.

Targeting is used to limit the number of times a policy f:query is evaluated. If no f:targetSubject or f:targetProperty are present on a policy, then it targets all flakes in the affected data.

The two targeting properties, f:targetSubject and f:targetProperty, are similar in form: either static identifiers or a query where clause. If a where clause, it is inserted into a query and executed before the view or modify operation is executed. The result set forms either the target subject set or the target properties set. Likewise, static identifiers are used to form the target subject set or the target properties set but without executing any queries.

If both f:targetSubject and f:targetProperty are present on a policy, then it targets flakes whose subject is in the target subjects set AND whose property is in the target properties set.

Policy evaluation

Policies are processed for each flake that will be returned in this way:

  1. All f:required policies must evaluate to true to permit access the flake. Non required policies are ignored.
  2. If no policies are "required", then all the policies are checked in sequence. The first one to evaluate to true permits access. If no policy evaluates to true, access to the flake is denied.

During policy evaluation, the f:query of each targeted policy is evaluated with certain variables bound (see Policy Variables Reference below).

The query returns an authorization result:

  • 1+ results: flake is allowed
  • 0 results: flake is not allowed
  • error: operation is aborted

Policy Variables Reference

Policy queries have access to special variables prefixed with ?$. These are automatically bound during policy evaluation:

VariableDescriptionAvailable In
?$thisThe subject (entity ID) of the flake being evaluatedAll policies
?$identityThe identity (DID or IRI) of the user making the requestAll policies
?$targetBound by f:targetSubject or f:targetProperty where clausesTargeting queries
?$propertyThe property of the flake being evaluatedProperty-targeted policies

Using Policy Variables

?$this — The entity being accessed:


{
"f:query": {
"@type": "@json",
"@value": {
"where": [
{ "@id": "?$this", "ex:owner": "?owner" },
["filter", "(= ?owner ?$identity)"]
]
}
}
}

?$identity — The authenticated user:


{
"f:query": {
"@type": "@json",
"@value": {
"where": [
{ "@id": "?$identity", "ex:role": "admin" }
]
}
}
}

?$target — Used in targeting where clauses:


{
"f:targetSubject": {
"@type": "@json",
"@value": {
"where": [{ "@id": "?$target", "@type": "ex:ConfidentialDocument" }]
}
}
}

Custom Policy Values

In addition to built-in variables, you can pass custom values via the policy-values option or fluree-policy-values header. Reference them with the ?$ prefix:


{
"f:query": {
"@type": "@json",
"@value": {
"where": [
{ "@id": "?$this", "ex:department": "?dept" },
["filter", "(= ?dept ?$department)"]
]
}
}
}

Pass the value at query time:


{
"opts": {
"identity": "ex:alice",
"policy-values": { "department": "engineering" }
}
}

Policy Examples

Example 1: Read-Only Policy for Own Data

This policy allows users to view only their own data:


{
"@context": {
"f": "https://ns.flur.ee/ledger#",
"ex": "http://example.org/"
},
"@id": "ex:readOwnDataPolicy",
"f:action": { "@id": "f:view" },
"f:query": {
"@type": "@json",
"@value": {
"@context": { "ex": "http://example.org/" },
"where": [
["filter", "(= ?$this ?$identity)"]
]
}
}
}

Example 2: Role-Based Access Control

This policy allows users with an admin role to view all data:


{
"@context": {
"f": "https://ns.flur.ee/ledger#",
"ex": "http://example.org/"
},
"@id": "ex:adminViewPolicy",
"f:action": { "@id": "f:view" },
"f:query": {
"@type": "@json",
"@value": {
"@context": { "ex": "http://example.org/" },
"where": [
{ "@id": "?$identity", "ex:role": "admin" }
]
}
}
}

Example 3: Property-Level Protection

This policy restricts access to sensitive properties:


{
"@context": {
"f": "https://ns.flur.ee/ledger#",
"ex": "http://example.org/"
},
"@id": "ex:sensitivePropertyPolicy",
"f:targetProperty": [
{ "@id": "ex:ssn" },
{ "@id": "ex:salary" }
],
"f:query": {
"@type": "@json",
"@value": {
"@context": { "ex": "http://example.org/" },
"where": [
{ "@id": "?$identity", "ex:role": "hr" }
]
}
}
}

Example 4: Write Protection

This policy restricts who can modify data:


{
"@context": {
"f": "https://ns.flur.ee/ledger#",
"ex": "http://example.org/"
},
"@id": "ex:writeProtectionPolicy",
"f:action": { "@id": "f:modify" },
"f:required": true,
"f:exMessage": "Only data owners can modify their records",
"f:query": {
"@type": "@json",
"@value": {
"@context": { "ex": "http://example.org/" },
"where": [
{ "@id": "?$this", "ex:owner": "?owner" },
["filter", "(= ?owner ?$identity)"]
]
}
}
}

Using Policies with the HTTP API

Query-Level Policy via Request Body

Include the opts object with identity information in your query:


{
"from": "my-ledger",
"select": { "?s": ["*"] },
"where": { "@id": "?s", "@type": "ex:Person" },
"opts": {
"identity": "did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL"
}
}

Header-Based Policy

Policies can be applied via HTTP headers:

Identity Header


curl --location 'http://localhost:58090/fluree/query' \
--header 'Content-Type: application/json' \
--header 'fluree-identity: did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL' \
--data '{
"from": "my-ledger",
"select": { "?s": ["*"] },
"where": { "@id": "?s", "@type": "ex:Person" }
}'

Inline Policy Header

For ad-hoc policy evaluation, use the fluree-policy header with a JSON policy document:


curl --location 'http://localhost:58090/fluree/query' \
--header 'Content-Type: application/json' \
--header 'fluree-identity: ex:alice' \
--header 'fluree-policy: {"@context":{"f":"https://ns.flur.ee/ledger#"},"f:query":{"@type":"@json","@value":{"where":["filter","(= ?$this ?$identity)"]}}}' \
--data '{
"from": "my-ledger",
"select": { "?s": ["*"] },
"where": { "@id": "?s" }
}'

Policy Values Header

Bind dynamic values for policy evaluation:


curl --location 'http://localhost:58090/fluree/query' \
--header 'Content-Type: application/json' \
--header 'fluree-identity: ex:alice' \
--header 'fluree-policy-values: {"department":"engineering"}' \
--data '{
"from": "my-ledger",
"select": { "?s": ["*"] },
"where": { "@id": "?s", "@type": "ex:Employee" }
}'

Policy Values Binding

Policy values allow you to pass dynamic parameters to policy queries. In your policy f:query, reference bound values with the ?$ prefix:


{
"f:query": {
"@type": "@json",
"@value": {
"where": [
{ "@id": "?$this", "ex:department": "?dept" },
["filter", "(= ?dept ?$department)"]
]
}
}
}

When evaluating this policy, pass the department value:


{
"opts": {
"identity": "ex:alice",
"policy-values": {
"department": "engineering"
}
}
}

Default Allow Behavior

By default, if no policies are defined, all data is accessible. When policies are added:

  • If no policy matches a flake, access is denied
  • At least one non-required policy must permit access, OR
  • All required policies must permit access

To implement a default-allow pattern with specific restrictions, ensure you have a catch-all policy:


{
"@id": "ex:defaultAllowPolicy",
"f:query": {
"@type": "@json",
"@value": {
"where": ["filter", "true"]
}
}
}

Then add required policies to restrict specific data.

Identity-Based Policy Patterns

These examples demonstrate common patterns for user-specific access control.

Linking DIDs to Application Users

A common pattern is to link a DID (used for authentication) to an application-level user entity:


{
"@context": {
"f": "https://ns.flur.ee/ledger#",
"ex": "http://example.org/",
"schema": "http://schema.org/"
},
"insert": [
{
"@id": "ex:alice",
"@type": "ex:User",
"schema:name": "Alice",
"schema:email": "alice@example.com",
"schema:ssn": "111-11-1111"
},
{
"@id": "did:key:z6MkqtpqKGs4Et8mqBLBBAitDC1DPBiTJEbu26AcBX75B5rR",
"f:policyClass": [{"@id": "ex:UserPolicy"}],
"ex:user": {"@id": "ex:alice"}
}
]
}

The DID entity has:

  • f:policyClass — Links to policy classes that apply to this identity
  • ex:user — Custom property linking to the application user

Property-Level Identity Restriction

Restrict sensitive properties so users can only see their own data:


{
"@context": {
"f": "https://ns.flur.ee/ledger#",
"ex": "http://example.org/",
"schema": "http://schema.org/"
},
"insert": [
{
"@id": "ex:ssnRestriction",
"@type": ["f:AccessPolicy", "ex:UserPolicy"],
"f:required": true,
"f:onProperty": [{"@id": "schema:ssn"}],
"f:action": {"@id": "f:view"},
"f:query": {
"@type": "@json",
"@value": {
"@context": {"ex": "http://example.org/"},
"where": {
"@id": "?$identity",
"ex:user": {"@id": "?$this"}
}
}
}
},
{
"@id": "ex:defaultAllow",
"@type": ["f:AccessPolicy", "ex:UserPolicy"],
"f:action": {"@id": "f:view"},
"f:query": {"@type": "@json", "@value": {}}
}
]
}

How this works:

  1. ex:ssnRestriction is marked f:required — it must pass for SSN access
  2. The query checks if ?$identity (the DID) has ex:user pointing to ?$this (the entity with the SSN)
  3. ex:defaultAllow permits viewing other data without restriction
  4. Result: Users can see all data, but only their own SSN

Query Behavior with Identity Policies

When querying with an identity that has the above policies:


{
"from": "my-ledger",
"select": {"?s": ["*"]},
"where": {"@id": "?s", "@type": "ex:User"},
"opts": {
"identity": "did:key:z6MkqtpqKGs4Et8mqBLBBAitDC1DPBiTJEbu26AcBX75B5rR"
}
}

Returns:


[
{
"@id": "ex:alice",
"@type": "ex:User",
"schema:name": "Alice",
"schema:email": "alice@example.com",
"schema:ssn": "111-11-1111"
},
{
"@id": "ex:bob",
"@type": "ex:User",
"schema:name": "Bob",
"schema:email": "bob@example.com"
}
]

Alice sees her own SSN, but Bob's SSN is filtered out.

User Can Only Modify Own Data

Restrict modifications so users can only change their own records:


{
"@id": "ex:modifyOwnDataOnly",
"@type": ["f:AccessPolicy", "ex:UserPolicy"],
"f:required": true,
"f:action": {"@id": "f:modify"},
"f:exMessage": "You can only modify your own data",
"f:query": {
"@type": "@json",
"@value": {
"@context": {"ex": "http://example.org/"},
"where": {
"@id": "?$identity",
"ex:user": {"@id": "?$this"}
}
}
}
}

Team-Based Access

Allow users to access data belonging to their team:


{
"@id": "ex:teamAccess",
"@type": ["f:AccessPolicy", "ex:UserPolicy"],
"f:action": {"@id": "f:view"},
"f:query": {
"@type": "@json",
"@value": {
"@context": {"ex": "http://example.org/"},
"where": [
{"@id": "?$identity", "ex:user": "?user"},
{"@id": "?user", "ex:team": "?team"},
{"@id": "?$this", "ex:team": "?team"}
]
}
}
}

This allows access when:

  1. The identity's linked user belongs to a team
  2. The target entity belongs to the same team

Hierarchical Access (Manager Can See Reports)

Allow managers to see data of their direct reports:


{
"@id": "ex:managerAccess",
"@type": ["f:AccessPolicy", "ex:UserPolicy"],
"f:action": {"@id": "f:view"},
"f:query": {
"@type": "@json",
"@value": {
"@context": {"ex": "http://example.org/"},
"where": [
{"@id": "?$identity", "ex:user": "?manager"},
{"@id": "?$this", "ex:reportsTo": {"@id": "?manager"}}
]
}
}
}