Parsware
All articles

Parsware Platform

Whose records a visitor sees

On a portal, every visitor reaches the same table through the same Custom APIs, and each must see only their own company's rows. One rule on the table does it, for reads and writes alike, without a filter in any script. We put two companies' cases side by side, asked for the other company's case by its id, looked at the rule, and switched it off to see what it was holding back.

The Northwind support portal listing two companies' cases to one visitor with the rule switched off, next to the title "Whose records a visitor sees"

A customer portal on your own data ended with one line on the par_case table:

Record.par_customer == Portal.CustomerId

and the claim that it's the whole of the security. This article tests that claim on the Portal service desk sample, samples/solutions/portal-service-desk.

Two companies, one table

Sam Patel works at Contoso and raises a case about an invoice charged twice. Sam's My cases shows it:

Your cases: 1 case for your company. Nothing on this page filtered them. Invoice INV-2026-0915 charged twice, New

Avery Brooks works at Proseware. Avery's list, on the same portal and the same page, shows Proseware's case and not Sam's:

Your cases: 1 case for your company. Order 4471 arrived with two cartons missing, New

The list is filled by par_list_my_cases, a Custom API whose script asks for every case with no filter of any kind:

var page = runQuery({
  entity: 'par_case',
  select: ['par_name', 'par_status', 'par_urgency'],
  order: [{ field: 'par_name', descending: false }]
});

The rule

It's in the solution, under Data policies:

Edit Cases belong to their customer: Table Cases, Condition Record.par_customer == Portal.CustomerId, In force ticked

Its type is Whose records (portal). It isn't granted to a role; it belongs to the table, and applies to every portal visitor who reaches that table through any Custom API: these five, and any written next year.

The condition compares a field of the row with what the platform knows about the visitor: Portal.CustomerId, Portal.ContactId, Portal.UserId, Portal.Email and Portal.Role. Here, the customer on the case has to be the visitor's company. A visitor in no company matches nothing, not everything.

The condition is written in the platform's code editor, so a name it doesn't know is underlined while you type:

The condition with Portal.Custommrd underlined, and: Class member Custommrd not found in class #host:Portal

Asking for the other company's case

The list only shows what it's given. What if Sam asks for Proseware's case by its id, through the portal's own contracts? Sam's browser can send that request, so we sent it:

  • Read it with par_get_my_case: nothing comes back. Not an error that says the case exists, just no answer.
  • Add a note with par_update_case: refused, That case is not one of yours.
  • Close it with par_close_case: refused, That case is not one of yours.

The scripts don't check the company. Each one reads the case with getRecord, and under the rule that read finds nothing for somebody at another company. So the script says the case isn't theirs, and it's right.

Switch it off

Untick In force and Sam's list changes at once:

Your cases: 2 cases for your company. Invoice INV-2026-0915 charged twice, and Order 4471 arrived with two cartons missing

That's Proseware's case on Contoso's screen. It isn't only reading, either. With the rule off, the same three requests read Proseware's case, added Sam's note to it and closed it. We found that out the honest way, by running them, and then put the case back.

Tick In force again and Sam is back to one case. Nothing else changed: no script, no role, no page. That's what the whole of the security means.

And from the inside

Northwind's own support team sees every company's cases, in a model-driven app in the Shell:

The Service desk app: All cases, Invoice INV-2026-0915 charged twice and Order 4471 arrived with two cartons missing

The rule is for portal visitors only. Your own people get what their security roles give them.

What it doesn't do

  • It doesn't narrow by person within a company. Everybody at Contoso sees Contoso's cases. A rule over Portal.ContactId would narrow to the person instead.
  • It doesn't hide columns. It decides which rows. Which fields leave the platform is up to each Custom API's select.
  • It doesn't apply in the Shell. There, roles and their policies decide.

What we fixed

  • The condition was typed into a plain text box. The Data policies drawer was the one place in the Admin Control Center where BS Lang was written without the platform's editor, so nothing was underlined until you pressed Save. Each kind now gets the editor with what it can read: a portal rule Record and Portal, a filter Record and CurrentUser, and a validation rule the functions it can call, such as deny.

Try it

Import samples/solutions/portal-sign-in-kit, then samples/solutions/portal-service-desk, and publish Service desk. Sign up twice from two private windows, start a different company in each, and raise a case from both. Then untick In force on Cases belong to their customer for a minute, and tick it again.

Next: Let visitors save changes and attach files.