Row Level Security
Row level security
Row level security is a way to enable security on a per instance basis.
Use cases are:
- user A has right to see record Y, but not to update it
- records X, Y, Z are visible only to users A and B
Architecture
Row level security is controlled by an internal object call "AccessList". An access list is:
- one or more users
- one or more groups
- one or more security profile
An access list is a fairly flexible way to tell if a user has the right to do something on a given entity instance.
A user "has access" to an access list if:
- he belongs to the list of "users" of the access list
- he belongs to a group (as in sysUserGroup) which is in the list of groups of the access list
- he has access to a security profile of the access list.
For example:
Access list 554543 contains:
- John Doe and Paul Smith
- The HR user group
John Doe, Paul Smith and all the folks from the HR group have "access" to 554543
Access list usage
AccessList may be used in 3 different ways:
1 - in the sysRead property of every entity.
Every entity has a sysRead property. This property may be programmatically filled (through a script for example).
When the persistence engine reads an entity instance (or row), and the sysRead is not null, then the persistence service will execute a check to make sure that the user has access to the access list id that is in sysRead. If not, a "no access" entity will be returned instead of the actual entity, resulting into the impossibility for the user to access any property of the "protected" entity.
Warning:
every attempt to read a property on a no access entity will return null.
2 - in the sysWrite property of every entity.
Every entity has a sysWrite id that may be filled as the id of a sysAccessList. In that case, the entity will be protected against writes (by the persistence service) resulting into security exceptions.
NOTE: For those two use cases (sysRead and sysWrite), the protection is a last barrier protection, resulting into security errors. You should build your user actions to avoid gestating into those (for example by restricting the list of object a user may see by filtering).
3 - by using the System.User.hasListAccess method in visibility scripts of user actions or properties.
if you have created (calculated) an access list object and stored the reference to the access list or the access list id in an entity, you may use System.User.hasListAccess to check if the current user has "access" to your access list.
Creating access lists
Access lists can only be created programatically through scripts using the "AccessList" object:
acl.addUser(user.sysId);
acl.addGroup(grp.sysId);
var aclId = acl.getSysId(); // retrieve the aclId and stores it
data.myAccessControlId = aclId
Why access list are only manipulated through code?
AccessList are designed to be cached in the system (actually the AccessList sysId is cached) and the platform maintains for each logged user a map of the access lists that match and a map of the ones that do not match with this user.
Therefore, AccessList are checked on a first check basis only, and subsequent checks are extremely fast. The first one may be slow, and that why we try to maintain a cache.
In order to limit the memory foot print and make that cache the most efficient as possible, the platform maintains a hash code of the access list and access lists with the same definition are shared.
So for example 2 access list containing only "Paul Smith" will be merged into one, and only one sysId will be put into the user access list cache.
This merging mechanism is controlled by the underlying AccessList service. That why it is required to do that by code.
A concrete example
Let's imagine an entity called "MyRequest".
Let's have another entity called a "Branch" (as in company branch office), and the "Branch" has a list of "Approvers".
"MyRequest" has a "reference" to a "Branch", and we want only the "Approvers" of the Branch of the Request to be able to "approve".
Solution:
1 - Create the Branch "entity" with the list of "Approvers" are a one to many ref to "rqEmployee".
In the Branch entity, add an AccessList id (as a string) called "ApproversAccessId".
2 - in the Save operation of the branch entity, we calculate the access list Id for once:
var approvers = data.pfxApprover;
for(var i=0; i<approvers.length; i++) {
acl.addUser(approvers[i].sysId);
}
// retrieve the access list id
data.pfxApproversAccessId = acl.getSysId();
3 - in the MyRequest entity "approve" user action, we add the visibility script based on the Branch property: