Parsware
Tous les articles

Parsware Platform

Data policies

A security role says what somebody may do with a table. A data policy says which records that reaches, and when a save is refused. We imported a sample with one of each kind, gave its role to two regional managers, and watched the same grant show each of them a different list and refuse one of them an approval.

Cet article n’est disponible qu’en anglais.

The Data policies list of the row-level security sample: Claims in my units, which filters the Claims table, and Nobody approves their own claim, checked on Update, next to the title "Data policies"

A security role answers one question: may this person read claims? That's rarely the whole answer. A regional manager may read claims, but only the ones raised in their region. They may approve a claim, but not one they raised themselves.

The platform's built-in answers cover a lot of it: records I own, my unit, my unit and below, every record. When they don't fit, you write a data policy: a rule of your own that a role's permission can be held under.

We used the Row-level security that travels with the solution sample. Import it from samples/solutions/mda-row-level-security to follow along. It brings a Claims table and app, a Claim Reviewer role, and one policy of each kind. It runs against the organisation chart the Organisation app seeds into every environment, so it needs nothing else installed.

Two kinds, and they fail differently

In the solution, Data policies:

The Data policies list: Claims in my units, Which records Claims, and Nobody approves their own claim, Which records Every record, Checked on Update, both in force

  • Claims in my units is a Which records policy. It decides which records exist for the person asking. A record it leaves out isn't there: it isn't in the list, and opening it by its address answers not found.
  • Nobody approves their own claim is a Rule on each record. It judges a record the person can plainly see, as it's saved, and when it refuses it says why.

The rule of thumb: if the answer depends on who is asking, it's Which records. If it depends on what the record says, it's a rule.

Which records: one condition

Edit Claims in my units: Type Which records, Table Claims, Condition Record.par_unit == CurrentUser.UnitsAndBelow

Record.par_unit == CurrentUser.UnitsAndBelow

Record.par_unit is the claim's Raised by unit field. CurrentUser.UnitsAndBelow is the signed-in person's unit and every unit under it. Comparing a field with a set means is one of.

This isn't a script that runs on each row. The platform turns it into part of the database query, so records it excludes never leave the server. That's why the language is deliberately small: comparisons joined with && and ||, and no function calls, arithmetic or following a lookup. Anything it can't turn into a query is refused when you save, not when somebody opens a list.

Fixed while writing this: a set comparison like this one used to fail on every list read with a database error. The Claims list showed the error instead of claims. It's fixed, and there's a test for it now.

A rule on each record: BS Lang, and deny

Edit Nobody approves their own claim: Type Rule on each record, Which records Every record, Checked on Update ticked, and the rule

if (isOwner() && getValue('par_status') == 'Approved' && getPreValue('par_status') != 'Approved') {
  deny('You cannot approve a claim you raised yourself. Ask somebody else in your unit to review it.');
}

getValue is the record as it will be saved, getPreValue as it's stored now, and isOwner() asks whether the person saving it owns it. Calling deny refuses the save, and its message is what the person reads. A rule that denies nothing allows it.

Two more settings:

  • Checked on: which operations run the rule. This one is Update only. Leave Read off unless you mean it: a rule that refuses reads makes records vanish for reasons nobody can see.
  • Which records: a rule can't filter a list, because it needs a record that's already been loaded. So a rule policy also names a built-in that does the filtering. Every record means "everything, subject to my rule".

Fixed while writing this: the list and this panel used to show no operations at all for any rule policy, so opening one and saving it unchanged quietly turned an Update-only rule into one checked on every write. Both now read the operations correctly.

Holding a permission under a policy

A policy does nothing until a role uses it. Open the solution's Roles, then Claim Reviewer:

The Claim Reviewer role: Claims row with Create Every record, Read Claims in my units, Update Nobody approves their own claim, Delete Not granted

Each cell is a permission, and the choice in it is the policy it's held under. For Claims: create any claim, read under Claims in my units, update under Nobody approves their own claim, and no delete. Your own policies appear in the same list as the built-in ones. A Which records policy is only offered on the row of the table it was written for.

Fixed while writing this: apps used to appear in this grid as rows with four empty cells, which read like headings: the Claims table looked like part of "Organisation". Apps are shared from the app's own Access panel, so they're no longer in the grid.

One role, two lists

We gave Claim Reviewer to two people in the development environment's sample organisation: Robin Park, whom we placed in Northern Region, and Sam Taylor, in Shiraz Branch, which is in the south.

The Organisation app's unit tree: Head Office, Northern Region with Enghelab and Vali-asr branches and Robin Park, Southern Region with Isfahan and Shiraz branches, and Sam Taylor under Shiraz

We then raised four claims as the administrator, one in each branch. Robin's Claims list:

Robin's Claims list: CLM-1001 from Vali-asr Branch and CLM-1004 from Enghelab Branch

Sam's:

Sam's Claims list: only CLM-1003 from Shiraz Branch

Same role, same grant, different lists, because the policy is worked out for each person. Robin sees both northern branches because his unit is the region above them. Sam sees his own branch. Neither sees Isfahan's claim. When Sam opened Robin's CLM-1001 by its address, the answer was not found, not forbidden: saying forbidden would tell him the claim exists.

The rule, refused and allowed

Sam raised a claim of his own, CLM-1005, then set its Status to Approved and saved:

CLM-1005 refused with "You cannot approve a claim you raised yourself. Ask somebody else in your unit to review it."

Refused, in the words the policy's author wrote. Robin approving CLM-1001, which the administrator raised, is a different story:

CLM-1001 saved by Robin with Status Approved and the note "Checked against the receipts."

Policies travel with the solution

A policy is a solution component like a table or a form. It's exported with the solution, checked again when it's imported, and removed when the solution is uninstalled, along with the grants held under it. That's the point: "nobody approves their own claim" is part of how the application works, and an application shipped to Production without it would allow what Development refused.

What doesn't travel is who holds the role. That's your organisation's decision and stays in each environment.

Try it

In your own solution, write a Which records policy on Claims with the condition Record.par_amount >= 100 and save. It's refused before it exists: "This condition cannot be turned into a query: (line 1) '>=' and '<=' cannot be used in a filter policy. Use '>' or '<'." Change it to Record.par_amount > 99 and it saves.

Next: a tour of the process designer, the first of a series on drawing business processes.