Filter access to data is a must-have feature to prevent malicious access to your application.
So far, you could expose or not expose as a REST resource a dataclass and some of its attributes. It was already a convenient means to restrict access to your data. Still, we’re thrilled to deliver in the v19R8 a powerful and fully customizable system to protect your data from unauthorized users. A system to protect your data depends on who is accessing it and which data is accessed.
As you asked during customer exchanges, you’ll now be able to handle many users working on different businesses and tune access to your data with several granularity levels, from the most general to the most accurate.
Following on from the scalable web sessions, this feature allows you to build a reliable and protected application.
general context
This new functionality is based on ORDA concepts and web sessions (more precisely on scalable web sessions).
It covers all web processes, such as REST requests, requests received on a remote datastore, and web processes like 4DACTION or 4D tags.
main concepts
In your application, you can now define allowed permissions.
A permission indicates a resource and privileges associated with actions on this resource.
Resources are a dataclass attribute, an ORDA data model function, a dataclass, and the whole datastore.
Actions are: create, read, update, delete, describe, execute (a function), and promote (a function).
a Permission for a dataclass attribute
The personalNotes attribute of the Records dataclass can be read by the medicalAction privilege.
Resource Records.personalNotes | Privileges |
---|---|
Read | medicalAction |
a permission for a dataclass function
The function deleteOldRecords() defined in the Records dataclass can be executed by the administrate privilege.
Resource Records.deleteOldRecords() | Privileges |
---|---|
Execute | administrate |
a Permission for a dataclass
The Records dataclass can be read by medicalAction and administrate privileges, and entities can be created by the medicalAction privilege.
Resource Records | Privileges |
---|---|
Read | medicalAction, administrate |
Create | medicalAction |
a permission for the whole datastore
The administrate privilege can drop entities in any dataclass of the datastore
Resource ds | Privileges |
---|---|
Drop | administrate |
To set up your customized rules for permissions, you just have to create a roles.json file in your application’s Project/Sources folder.
Some roles (i.e., a set of privileges) can also be defined and associated by yourself with each user of your application.
Remember, you can associate some privileges to a scalable web session. Then, when the system receives a request, control is done regarding the user’s privileges stored in the web session and which actions are permitted for those privileges in the roles.json file.
A permission error is raised if the action on the resource is not permitted.
the mechanism in details
When no privileges have been set in the web session, it is a guest one. By default, all data is accessible for this guest privilege. Also, if you don’t set up any permission in the roles.json file, all data is accessible by anyone.
Afterward, permissions you set up in the roles.json file come into play and restrict access to data.
Permissions can be set up for resources at several granularity levels. Those resources are listed below by granularity levels (from the most general one to the most accurate one):
– the datastore
– a dataclass
– a dataclass attribute / a dataclass function
Privileges set up at a given level are overridden or completed by privileges specified at a more accurate level.
The diagram below gives a more graphical representation of privileges associated with actions on some resource.
The guest privilege can run actions on resources associated with no privileges. You restrict access to your data as soon as you associate a privilege with a couple resource/action.
TIP: If you want your data to be unattainable by default, associate to the datastore a nobody privilege for all actions and ensure this privilege will never be put in the session.
going into details with examples
Let’s work with this simple data model.
In this application, we want to implement those rules:
- only the administrate privilege can drop and create entities on the whole datastore
- the dataclass Patients can be read by the medicalAction privilege only
- the Records.personalNotes attribute can be read only by the medicalAction privilege (even if the dataclass is readable by several other privileges)
- the function deleteOldRecords() defined in the Records dataclass is executable only by the administrate privilege
- handle an authenticate() function executable by anybody (guest privilege)
the administrate privilege can drop and create entities on the whole datastore
In the roles.json file below, we have defined an administrate privilege. This privilege is authorized to:
- drop
- create
entities in the whole datastore (all dataclasses).
Only this administrate privilege is authorized to do those actions. Because by default, all actions are permitted on all the data, and we have not associated any privilege for the read action for the datastore, anybody can read all the dataclasses of the datastore.
{"privileges": [{"privilege": "administrate"}], "roles": [{}], "permissions": { "allowed": [{"applyTo": "ds","type": "datastore","drop": ["administrate"],"create": ["administrate"]}] } }
the dataclass patients can be read by the medicalaction privilege only
In the roles.json file below, we have added a medicalAction privilege.
Only this medicalAction privilege is authorized to read the Patients dataclass. Because this permission is set up at the dataclass level, it overrides the permissions set up at the datastore level + the guest permissions. Thus, only the medicalAction privilege can read the Patients dataclass. Other dataclasses are readable by anybody.
{ "privileges": [{"privilege": "administrate"},{"privilege": "medicalAction"}], "roles": [ {} ], "permissions": {"allowed": [ {"applyTo": "ds","type": "datastore","drop": ["administrate"],"create": ["administrate"] }, {"applyTo": "Patients","type": "dataclass","read": ["medicalAction"] } ] } }
The records.personalnotes attribute is readable by the medicalaction privilege only.
In this example, we have added a readRecords privilege. Because the readRecords privilege is included in the medicalAction privilege, the medicalAction privilege can execute all actions permitted for the readRecords privilege.
Both readRecords and medicalAction privileges can read the Records dataclass, but the attribute personalNotes can be read only by the medicalAction privilege.
{ "privileges": [{"privilege": "administrate"}, {"privilege": "readRecords"}, {"privilege": "medicalAction","includes": ["readRecords"] } ], "roles": [ {} ], "permissions": { "allowed": [ {"applyTo": "ds", "type": "datastore", "drop": ["administrate"], "create": ["administrate"] }, {"applyTo": "Patients", "type": "dataclass", "read": ["medicalAction"] }, {"applyTo": "Records", "type": "dataclass", "read": ["readRecords"] }, {"applyTo": "Records.personalNotes", "type": "attribute", "read": ["medicalAction"] } ] } }
The function deleteoldrecords of the records dataclass is executable by the administrate privilege only.
In the example below, because only the administrator of the system must be allowed to delete old records, we have added for the administrate privilege the permission to execute the function deleteOldRecords() defined on the Records dataclass.
Only this privilege can execute it.
We have also added the permission for the administrate privilege to read the Records dataclass because reading the entities is needed to drop them.
{ "privileges": [ {"privilege": "administrate"}, {"privilege": "readRecords"}, {"privilege": "medicalAction", "includes": ["readRecords"] } ], "roles": [ {} ], "permissions": { "allowed": [ {"applyTo": "ds", "type": "datastore", "drop": ["administrate"], "create": [ "administrate"] }, {"applyTo": "Patients", "type": "dataclass", "read": ["medicalAction"] }, {"applyTo": "Records", "type": "dataclass", "read": ["readRecords", "administrate"] }, {"applyTo": "Records.personalNotes", "type": "attribute", "read": ["medicalAction"] }, {"applyTo": "Records.deleteOldRecords", "type": "method", "execute": ["administrate" ] } ] } }
handle an authenticate function executable by guest
To prevent a foreign user from executing any function in our application, we have restricted the execution of functions at the datastore level to the none privilege (it is never put in the web session).
In our system, we have an authenticate() function (defined in the DataStore class) to be executed by users to enter the application. We have added permission to execute this authenticate() function.
Because it must be executable by anyone, the execute action has been set for the guest privilege. We have also added the hr privileges to prevent the Users dataclass from being read by someone foreign to the application.
The authenticate() function needs to read the Users dataclass to check the user’s existence and password. Thus it is promoted with the hr privilege. The promote action adds a privilege in the web session (only during the execution of the function).
{ "privileges": [ {"privilege": "administrate"}, {"privilege": "readRecords"}, {"privilege": "medicalAction", "includes": ["readRecords"] }, {"privilege": "hr"}, {"privilege": "none"} ], "roles": [ {} ], "permissions": { "allowed": [ {"applyTo": "ds", "type": "datastore", "drop": ["administrate"],"create": ["administrate"], "execute": ["none"] }, {"applyTo": "Patients", "type": "dataclass", "read": ["medicalAction"] }, {"applyTo": "Users", "type": "dataclass", "read": ["hr"] }, {"applyTo": "Records", "type": "dataclass", "read": ["readRecords", "administrate"] }, {"applyTo": "Records.personalNotes", "type": "attribute", "read": ["medicalAction"] }, {"applyTo": "Records.deleteOldRecords", "type": "method", "execute": ["administrate"] }, {"applyTo": "ds.authenticate", "type": "method", "promote": ["hr"],"execute": ["guest"] } ] } }
the autentication phase
In the roles.json file, you can define roles: i.e., a set of privileges. In the example below, we have added the role of The Secretary containing the createPatient and readRecords privilege.
The users associated with this role cans run all actions permitted for privileges createPatient and readRecords (create a new patient and read records).
{ "privileges": [ { "privilege": "administrate" }, {"privilege": "readRecords" }, {"privilege": "medicalAction", "includes": ["readRecords"] }, {"privilege": "hr" }, {"privilege": "none"}, {"privilege": "createPatient" } ], "roles": [ {"role": "The Secretary","privileges": ["createPatient","readRecords" ] } ], "permissions": { "allowed": [ {"applyTo": "ds","type": "datastore","drop": ["administrate"], "create": ["administrate"], "execute": ["none"] }, {"applyTo": "Patients","type": "dataclass","read": ["medicalAction"], "create": ["createPatient"] }, {"applyTo": "Users","type": "dataclass","read": ["hr"] }, {"applyTo": "Records","type": "dataclass","read": ["readRecords","administrate"] }, {"applyTo": "Records.personalNotes","type": "attribute", "read": ["medicalAction"] }, {"applyTo": "Records.deleteOldRecords","type": "method", "execute": ["administrate"] }, {"applyTo": "ds.authenticate","type": "method", "promote": ["hr"], "execute": ["guest"] } ] } }
In your application, you must associate each user to a role in a dataclass (the Users dataclass in this example).
Here is the dataclass
and the data
During the authentication phase, you can put the privileges associated with this role in the web session.
Here is an example of an authenticate() function.
exposed Function authenticate($identifier : Text; $password : Text) : Text
var $user : cs.UsersEntity
Session.clearPrivileges()
$user:=ds.Users.query("identifier = :1"; $identifier).first()
If ($user#Null)
If (Verify password hash($password; $user.password))
Session.setPrivileges(New object("roles"; $user.role))
return "Your are authenticated as "+$user.role
Else
return "Your are authenticated as Guest"
End if
Else
return "Your are authenticated as Guest"
End if
Read the documentation carefully to learn more about the permissions, and download the HDI above to run a demo example.
Feel free to open discussions on the forum.