Understand record access policy rules

Record access policies apply at the data record level rather than the object type level. As a result, a user may be able to see some records of a given object type, but not all of them. In database terms, it is controlling access to each row of a table, rather than to the whole table (the object) or to a column column (a field on the object).

Each policy comprises rules that define the data access filters. They are request query filters that limit the data that is returned for any request made by the Skedulo web app, mobile app, or API. A rule always has to be part of a parent policy and rules are enabled and disabled via the parent policy.

For more information about record access policies and how to manage them in the web app, see the user guide.

Record access policy rule fields

Each rule consists of the following fields:

Field Description
Rule description A text description that makes it easy to identify the rule’s purpose.
Object type The data object that the rule must apply to, for example, if the rule pertains to accessing job records, then the object type would be Jobs. This field also supports a hasLookup:<lookup name> value (details provided below in the Pattern rules section).
Filter records The filter, written in EQL, that must be applied to the object data to limit what is accessible. See the EQL documentation for more information on how to write and use these filters for the Skedulo data model.
Access type  Controls how the filter applies to the object’s data. If set to deny, then only data that passes the filter expression will be returned. Multiple deny filters for the same object act as if linked with AND operators in the query. When an object has a rule with access type deny in effect, a rule with access type allow can be added to override it if the allow filter expression passes; in effect providing an OR operator.
Roles excluded Users with a role listed in this field will be exempt from the rule. Note that users with the administrator role, or a role containing the “View all data” permission, are exempt from all record access policies.
Permissions excluded  Users with a role that contains any of the permissions in this field will be exempt from the rule. For example, if you have a rule intended for resources, which denies access to jobs unless the it is allocated to the current user,you could exclude schedulers by adding a permission exclusion for skedulo.tenant.schedule.allocation.dispatch. Schedulers would be able to see jobs regardless of their allocation.

Rule filters

Although GraphQL-binding is generally supported in Skedulo, bound lookups are not supported in the RAP rule filters. An IN clause must be used instead. For example, if you have a rule on JobAllocations, you should use:

ResourceId IN (SELECT UID FROM Resources WHERE Name != 'secret')

rather than Resource.Name != 'secret'.

Note that if you were to do a JobAllocations query via GraphQL, this syntax would be perfectly legitimate:

{
  jobAllocations(filter: "Resource.Name != 'secret'") {
    edges {
      node {
        Name
          Resource {
            Name
        }
      }
    }
  }
}

Rule variables

Plain text placeholders resourceId and user are supported in the record access policy rules. Template variables must be enclosed in single quotes, for example, {{resourceId}}, {{user}}.

Pattern rules

Record access policies are generally composed of rules that apply to different data object types. If, however, there is a pattern to the data that requires multiple rules to achieve, then it may be easier to use a rule that describes the pattern instead, for example, lookup relationships to a particular object type. This type of rule also covers future scenarios where objects could be linked via a lookup relationship via a new custom object.

Skedulo supports the hasLookup pattern in rules, such that a rule can be applied to all objects that have a certain lookup relationship.

To add a pattern rule to a policy, do one of the following:

  • In a POST request to /authorization/policies/rules, add hasLookup:<lookup name in the Object type field.
{
  "policyId": "843760be-2355-4a3d-8646-728875d81a75",
  "description": "Deny all object types directly linked to a Region, unless its region is associated with the user (pattern rule)",
  "objectType": "hasLookup:Region",
  "filter": "RegionId IN (SELECT RegionId FROM UserRegions WHERE UserId == '{{userId}}') OR RegionId IN (SELECT PrimaryRegionId FROM Resources WHERE UID == '{{resourceId}}') OR RegionId IN (SELECT RegionId FROM ResourceRegions WHERE ResourceId == '{{resourceId}}')",
  "accessType": "deny"
}
  • In the Skedulo web app, on the Settings > Record access policies > Policy > Create rule page, add hasLookup: before the name of the lookup in the Object type field.

Lookup names can be found in the GraphL schema for your team.

Screenshot of the hasLookup:Region pattern rule in the list of rules in the Data Isolation by Region policy

The filter query will now apply to all object types that have the lookup specified.

Record access type

Each rule in a policy has a filter (a query the specifies criteria for data to show) and an access type (which determines how the queries work with each other). When multiple rules are in a single policy, the filters are applied using logic based on the access type. This helps you to separate out complex logic that applies to a single object type into separate rules, which then work as a single piece of logic at runtime.

All filters work the same, in that only data that passes the expression will be accessible. Rules are generally of the access type deny, because they deny access to data that doesn’t meet the criteria in the filter.

If, however, an object type has a deny rule in effect and there are some special cases when the data should be accessible, then it may make sense to add a filter that describes the circumstances under which the deny rule does not hold. This can be achieved by adding an allow rule, which effectively overrides the deny rule if the given filter expression passes.

Another way to think of it is that deny filters are applied with the AND operator, while allow filters are added to any existing deny filters with the OR operator.

Note: An allow rule on an object type that does not have a deny rule typically has no effect on records of that object type, but can still have an impact on another object type because of how record access policies handle relationships between them. For example, an allow rule on Jobs would have no effect in isolation, but if there was also a deny rule on Regions, then any job records with a region that is denied would still be visible because of the allow rule.

Multiple rules for a single object type can be created, but it is generally simpler to contain all logic pertaining to a single object type in a single rule per access type (if more than one access type is needed).

Performance considerations

When creating record access policy rules, be mindful that some rules could have an impact on performance. For example, rules with filters that require searching through the contents of multiple text fields may require considerable computation. As always, rules need to be tested extensively before being put into production to assess their impact on the user experience and data access.

Practical example

Business requirement

Users must only see jobs that are in their region, unless they are allocated to a job in another region.

Record access policy solution

This could be achieved by adding two rules for the Jobs object type; one with access type deny and one with access type allow:

  1. A deny rule that only returns jobs in the user’s region:
{
    "description": "Deny access to Jobs unless they are in a region associated with the user",
    "objectType": "Jobs",
    "filter": "RegionId IN (SELECT RegionId FROM UserRegions WHERE UserId == '{{userId}}')",
    "accessType": "deny",
    "rolesExcluded": [],
    "permissionsExcluded": []
}
  1. An allow rule that returns any jobs that have been allocated to the user, regardless of region (thereby overriding the first rule):
{
    "description": "Allow access to Jobs that are allocated to the current Resource",
    "filter": "UID IN (SELECT JobId FROM JobAllocations WHERE ResourceId == '{{resourceId}}' AND Status != 'Deleted' AND Status != 'Declined')",
    "objectType": "Jobs",
    "accessType": "allow",
    "rolesExcluded": [],
    "permissionsExcluded": []
}

The Data isolation by region policy rules

The scenario above can be seen in more detail in the Data isolation by region policy template. The Data isolation by region policy is designed so that users only see jobs that are in the same region as they are, as well as any other jobs (or shifts) that are allocated to them, regardless of region.

Rule description Object Access type
Deny Regions unless they are associated with the user Regions deny
Deny all object types linked to a region, unless its region is associated with the user (pattern rule) hasLookup:Region deny
Deny Activities unless their allocated resource’s region is associated with the user Activities deny
Deny Users unless their region is associated with the user Users deny
Deny Holidays unless they are global or their region is associated with the user Holidays deny
Allow Contacts that do not have a region Contacts allow
Allow Jobs that are allocated to the current resource Jobs allow
Allow Shifts that are allocated to the current resource Shifts allow

To achieve this, the policy uses a deny rule to only show records that have the same region as the user (as in the example in the previous section). The policy has additional deny rules for Holidays, Activities, and Users, because these objects do not have direct lookups to the Region object, but are linked via another object, for example Holiday has HolidayRegion and Activity has Location.

These rules work together to deny access; i.e., the filters in rule 1 AND rule 2 AND rule etc. are in effect.

The policy also has allow rules that override the hasLookup:Region rule for Jobs and Shifts so that users can always see work allocated to them, regardless of the region. There is a further allow rule for Contacts so that these are not hidden when the region field has no value.