Skip to main content

Access Control Policy Syntax

The LifeOmic platform provides access control customization for large number of operations. For an overview of how Access Control works on the platform, see the Access Control Overview documentation.

Some advanced platform use cases may require usage and/or modification of "raw" access control policy documents. All policy documents on the platform are defined using the same syntax, which we call "ABAC". All ABAC policies are defined using JSON syntax.

Terminology

Let's define some of the ABAC terminoloy, using this simple policy document as an example:

{
"policy": {
"readData": [
{
"user.id": {
"comparison": "equals",
"value": "johndoe"
}
}
]
}
}

Operations

In the example policy above, readData is an example of an operation. Policies can describe any number of operations.

The readData operation in particular controls "read" access to FHIR data in an account. Some of the platform's other default operation names are described in the Access Control Overview documentation.

In every policy document, operations are described and controlled by a set of rules. These rules are evaluated at the time of attempted access to determine whether the operation should be allowed.

Rules

In the example policy above, this is an example of a rule:

{
"user.id": {
"comparison": "equals",
"value": "johndoe"
}
}

Rules contain a set of comparisons, which can contain references to direct values (like "johndoe") or to dynamic attributes.

Attributes

In the example policy above, user.id is an example of an attribute. Attributes allow the policy to reference the context of a particular request.

Attributes reference externally provided values. These values can be any serializable data type: arrays, objects, strings, boolean values, etc. The "dot" syntax allows us to reference nested values within a referenced object.

Some attributes (like user.id) are available in all rules. Other attributes are only available in the context of certain rules.

Comparisons

In the example policy above, equals is an example of a comparison. ABAC supports a number of comparison types, described in detail below.

Interpreting Policies

Now that we have a basic understanding of the terminology, let's interpret the meaning of the example policy:

{
"policy": {
"readData": [
{
"user.id": {
"comparison": "equals",
"value": "johndoe"
}
}
]
}
}

This policy allows the user johndoe complete access to the readData operation. This is an extremely permissive policy for johndoe. It allows that user to read any and all FHIR data using the FHIR API, including data that may not be their own.

So, let's make the policy more specific:

{
"policy": {
"readData": [
{
"user.patients": {
"comparison": "includes",
"target": "resource.subject"
}
}
]
}
}

We've made several changes to the policy:

  1. We've changed the key attribute in our readData to user.patients. This is a special attribute which is available in most operations that control access to FHIR data, including readData. The user.patients attribute is a reference to the list of Patient records which are associated with the requesting user.

  2. We've modified our comparison from an equals comparison to an includes comparison, which allows us to work with "array" attributes.

  3. We've replaced "value": "johndoe" with a target reference. The target field allows us to use attributes within our comparison.

  4. We see a new attribute as the target of our comparison: resource.subject. This is another special attribute, also available to most FHIR operations. The resource attribute refers to the FHIR resource that the user is attempting to access, using dot syntax. So resource.subject refers to the subject field of the requested resource.

This policy is now much more reasonable: it allows any user to perform the readData operation, as long as they are reading a resource that contains a reference to one of their associated Patient records in the resource's subject field.

AND/OR

ABAC supports performing combining conditional comparisons using AND/OR-like behaviors

"AND-ing"

To "AND" two comparisons, set them in the same rule block.

{
"policy": {
"readData": [
{
// These two comparisons are AND-ed
"user.id": {
"comparison": "equals",
"value": "johndoe"
},
"user.patients": {
"comparison": "includes",
"target": "resource.subject"
}
}
]
}
}

This policy combines the two rules we've already seen using an AND-like behavior to create a highly specific behavior.

It allows the readData operation only if:

  • the user attempting to read is johndoe, AND
  • johndoe is attempting to read a resource that has a subject property referencing one of johndoe's associated Patient records.

"OR-ing"

To "OR" two comparisons, list them separately in an operation's rule list.

{
"policy": {
"readData": [
// These two comparisons are OR-ed
{
"user.id": {
"comparison": "equals",
"value": "johndoe"
}
},
{
"user.patients": {
"comparison": "includes",
"target": "resource.subject"
}
}
]
}
}

This policy combines the two rules we've already seen using an OR-like behavior.

It allows the readData operation if:

  • the user attempting to read is johndoe, OR
  • the user is attempting to read a resource that has a subject property referencing one of the user's associated Patient records.

Policy Merging

Most accounts have multiple policies defined in their account by default. Policies can be added, updated or removed by account admins.

When evaluating a particular user's access to an operation, these various policy documents are "merged" together. Operations are evaluated cumulatively, in an OR-like behavior. If two policies contain conflicting rules for the same operation, the most permissive policy is used.

Let's look at an example of two policies:

[
{
"policy": {
"readData": [
{
"user.id": {
"comparison": "equals",
"value": "johndoe"
}
},
]
}
},
{
"policy": {
"readData": [
{
"user.id": {
"comparison": "equals",
"value": "janesmith"
}
},
]
}
}
]

"Merging" these two policies results in a single policy that is an OR-ed combination of the two:

{
"policy": {
"readData": [
{
"user.id": {
"comparison": "equals",
"value": "johndoe"
}
},
{
"user.id": {
"comparison": "equals",
"value": "janesmith"
}
}
]
}
}

It is essential to consider policy merging when designing an advanced policy configuration. In particular, keep in mind that a policy cannot disallow an operation that is allowed by another policy. Therefore, if any of the policies in your account allow a operation, that operation will always be allowed.

When modifying your account's policies, we recommend making your rules as specific as possible, to avoid allowing unintentional access.

Supported Comparisons

ABAC supports a number of comparison types, each of which allows comparing the referenced "key" value with the target value specified either via the target or value field.

In this example, user.id is the "key" value.

{
"user.id": {
"comparison": "equals",
"value": "johndoe"
}
}

equals

This comparison evaluates to true if the key references a value that is equal to the target value.

// Evaluates to `true` for user id 'johndoe'
// Evaluates to `false` for user id 'janesmith'
{
"user.id": {
"comparison": "equals",
"value": "johndoe"
}
}

notEquals

This comparison evaluates to true if the key references a value that is not equal to the target value.

// Evaluates to `true` for user id 'janesmith'
// Evaluates to `false` for user id 'johndoe'
{
"user.id": {
"comparison": "notEquals",
"value": "johndoe"
}
}

includes

This comparison evaluates to true if the key references an array, and the target value is an element in the array.

// Evaluates to `true` for user groups ['one']
// Evaluates to `false` for user groups ['two', 'three']
{
"user.groups": {
"comparison": "includes",
"value": "one"
}
}

in

This comparison evaluates to true if the target value is an array, and the key references a value in the array.

// Evaluates to `true` for user id "johndoe"
// Evaluates to `true` for user id "janesmith"
// Evaluates to `false` for user id "otheruser"
{
"user.id": {
"comparison": "in",
"value": ["johndoe", "janesmith"]
}
}

notIn

This comparison evaluates to true if the target value is an array, and the key references a value that is not in the array.

// Evaluates to `false` for user id "johndoe"
// Evaluates to `false` for user id "janesmith"
// Evaluates to `true` for user id "otheruser"
{
"user.id": {
"comparison": "notIn",
"value": ["johndoe", "janesmith"]
}
}

exists

This comparison evaluates to true if the key references any defined value. A target value is not required for this comparison type.

// Evaluates to `true` if user.value is a defined attribute
// Evalutes to `false` otherwise
{
"user.value": {
"comparison": "exists"
}
}

superset

This comparison evaluates to true if the key references an array, the target value references an array, and every value in the target array is included in the key array.

// Evaluates to `true` for user groups ['one']
// Evaluates to `true` for user groups ['one', 'two']
// Evaluates to `false` for user groups ['three']
{
"user.groups": {
"comparison": "superset",
"value": ["one"]
}
}

subset

This comparison evaluates to true if the key references an array, the target value references an array, and every value in the key array is included in the target array.

// Evaluates to `true` for user groups ['one']
// Evaluates to `true` for user groups ['one', 'two']
// Evaluates to `false` for user groups ['three']
{
"user.groups": {
"comparison": "subset",
"value": ["one", "two"]
}
}

startsWith

This comparison evaluates to true if the key references a string, the target value references a string, and the target value matches the start of the key value.

// Evaluates to `true` for user id "johndoe"
// Evaluates to `false` for user id "janedoe"
{
"user.id": {
"comparison": "startsWith",
"value": "john"
}
}

endsWith

This comparison evaluates to true if the key references a string, the target value references a string, and the target value matches the end of the key value.

// Evaluates to `true` for user id "johndoe"
// Evaluates to `false` for user id "johnsmith"
{
"user.id": {
"comparison": "endsWith",
"value": "doe"
}
}

prefixOf

This comparison evaluates to true if the key references a string, the target value references a string, and the key value is partially or fully contained within the target value, starting from the beginning of the string.

// Evaluates to `true` for user rank "1-2"
// Evaluates to `true` for user rank "1-2-3-4"
// Evaluates to `false` for user rank "1-2-3-4-"
// Evaluates to `false` for user rank "1-2-3-4-5"
{
"user.rank": {
"comparison": "prefixOf",
"value": "1-2-3-4"
}
}

suffixOf

This comparison evaluates to true if the key references a string, the target value references a string, and the key value is partially or fully contained within the target value, starting from the end of the string.

// Evaluates to `true` for king title "The Third"
// Evaluates to `true` for king title "Third"
// Evaluates to `true` for king title "hird"
// Evaluates to `false` for king title "The Second"
// Evaluates to `false` for king title "The Third Emperor"
{
"king.title": {
"comparison": "suffixOf",
"value": "William The Third"
}
}

notIncludes

This comparison evaluates to true if the key references an array, and the target value is not an element in the array.

// Evaluates to `true` for user groups ['two', 'three']
// Evaluates to `false` for user groups ['one', 'two', 'three']
{
"user.groups": {
"comparison": "notIncludes",
"value": "one"
}
}