Osy#betaRun it on Osyrin: osyrin.com

Reference / Security

Security.MayRead — may THIS person read this row, under their own rules

bool Security.MayRead(Principal person, <entity> row)

`Security.MayRead(person, row)` answers whether a given person may read a given row under THEIR OWN security — the app's read rules, every kit's floor, restricted rows — exactly as a request of theirs would be answered. Never the caller's: a workflow body reads everything, and a notifier asking through it would tell anybody about anything. What a notifier asks before telling somebody about a record, and a mention before naming somebody on one.

preview1 example compiled by CIsecuritynotificationsserver

Summary#

Before you TELL somebody about a record — a notification, a mention, a mail with its title in it — ask whether THEY may read it. Their own rules answer, not yours:

if (Security.MayRead(colleague, ticket)) { Notify(colleague, TicketNotice.Mentioned, ticket.Id); }

Signature#

bool Security.MayRead(Principal person, <entity> row)   // person: the row, or its id · row: the row itself

Description#

Whose rules. The person's — a fresh read armed with their account and their roles, the one a request of theirs gets: the entity's allow read, every deny read a kit wrote, a restricted queue they are not in, a row restricted for a privacy purpose. The caller's own access is irrelevant: a workflow body (which reads everything) and an admin's request (which reads more) both get the person's answer. SECURITY-INVARIANT may-read-answers-as-the-person-never-as-the-caller.

The row, not its id. Which rules apply depends on what kind of row it is, so pass the row you read — Security.MayRead(p, Ticket.Single(t => t.Id == id)).

Somebody who is nobody. An id that is no account answers false — never what an anonymous visitor could read.

As stored. It reads the row as the database holds it: a row this unit of work made and has not committed yet is not readable by anybody else, so it answers false. Ask after the commit — the notifications kit delivers after it for exactly this reason.

It reads; it never acts as them. Nothing runs as the person, nothing is written as them, and nothing they could read is handed back — only the answer. There is still no way to become another user outside a [Test] (acting for another principal).

Examples#

[Principal] entity Member {
  [MaxLength(100)] string Name = "";
  security { allow read, create when IsAnonymous || IsAuthenticated; }
}

entity Memo {
  [Required, MaxLength(100)] string Title;
  [Required] Member Owner;
  bool Private;
  security {
    allow read, create when IsAnonymous;
    allow read when IsAuthenticated where !Private || Owner == user;
  }
}

/// Who may be told about `memo`: everybody who may read it, under their own rules.
List<string> WhoMayBeTold(Memo memo) {
  return Member.ToList().Where(m => Security.MayRead(m, memo)).Select(m => m.Name).ToList();
}

See also#

Related

The Notifications kit — telling a person that something needs them

`Osysharp.Notifications` tells a person that something needs them — in the app, a bell with a live count and a list…

The ReplyComposer kit — where a person writes a reply, on any record

`Osysharp.ReplyComposer` is the box a person writes a reply in — on a ticket, a case, an order, a lead. Saved replies…

security { }

The rules that decide who may read and write an entity's rows. A where clause filters by the row (the owner sees their…

restricted rows and purpose reads (Restriction, [PurposeRead])

Keep a row only for a stated purpose: Restriction.Restrict stamps it with a privacy category and the end of its period…

acting for another principal

A shared terminal, a kiosk, a scanner or a back-office integration is a **user of your app**, not a mode of it. It…