Access Control Policy Syntax
The LifeOmic Platform provides access control customization for a 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 terminology, 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:
-
We've changed the key attribute in our
readData
touser.patients
. This is a special attribute which is available in most operations that control access to FHIR data, includingreadData
. Theuser.patients
attribute is a reference to the list of Patient records which are associated with the requesting user. -
We've modified our
comparison
from anequals
comparison to anincludes
comparison, which allows us to work with "array" attributes. -
We've replaced
"value": "johndoe"
with atarget
reference. Thetarget
field allows us to use attributes within our comparison. -
We see a new attribute as the target of our comparison:
resource.subject
. This is another special attribute, also available to most FHIR operations. Theresource
attribute refers to the FHIR resource that the user is attempting to access, using dot syntax. Soresource.subject
refers to thesubject
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 asubject
property referencing one ofjohndoe
'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"
}
}