Osy#betaRun it on Osyrin: osyrin.com

Reference / Security

Security.IsMasked — was this value withheld from me?

bool Security.IsMasked(<row>.<Member>)

A field a `deny read` withholds reads as its type's default — 0, false, DateTime.MinValue, the enum's zero; null for a nullable or reference type. `Security.IsMasked(p.Salary)` tells that stand-in from a real value: true when the member was withheld from the reader on that row. The same answer in a function, a page's slot and its action.

preview1 example compiled by CIsecurityserverclient

Summary#

A member a deny read withholds from the reader reads as its type's default — never the real value. For a non-nullable value type that default is a real value of the type (0, 0m, false, DateTime.MinValue, the enum's zero), so the value alone cannot say whether it was withheld. Ask:

var shown = Security.IsMasked(p.Salary) ? "hidden" : p.Salary.ToString("C");

Signature#

bool Security.IsMasked(<row>.<Member>)   // the argument is a member read off an entity row

Description#

What a withheld member reads as. default(T) of its declared type:

declaredreads as when withheld
int long double decimal bigdecimal0
boolfalse
TimeSpanTimeSpan.Zero
DateTime DateOnly TimeOnlyMinValue
GuidGuid.Empty
an enumits zero — (E)0, the first member of a positional enum
string, T?, a referencenull

So p.Salary + 1 is ordinary arithmetic for every reader, and nothing needs a null check the type says is impossible. The real value never reaches the reader: the default is a constant of the TYPE, decided before the value is read.

Everywhere the reader reads it. In a function body, in a filter, an OrderBy, an aggregate and a projection of the reader's own query (Pay.Count(p => p.Salary == 0) counts the rows whose salary is withheld from them, exactly as reading each row would), in a page's slot and in its action. A security { } condition is the one place a member reads its real value, because a rule judges the data as it is ([[security-entity-security#field-mask-in-queries]]).

Security.IsMasked(p.Salary) answers true when the rules withhold that member from the reader on that row — a blanket deny read Salary when !IsStaff, or a rule decided per row (deny read Commission where Owner != user) — and the value read is the stand-in. It is false:

  • for a reader the rules do not cover;
  • for a member this unit of work assigned, and every member of a row it created — that value is the reader's own;
  • for a member the reader wrote and committed, which reads back what they wrote.

The argument must be a member read off a row — Security.IsMasked(p.Salary), Security.IsMasked(o.Customer.Credit). A string naming the member is not accepted: the compiler checks the member exists.

Writing it back is safe. A reader who may write a member but not read it reads the stand-in, and writing that stand-in (or a null) straight back changes nothing — the stored value survives. The consequence: such a reader cannot set the member to exactly its default, since that write cannot be told from a write-back.

In the browser. A served row names the members withheld from its reader, and the page answers IsMasked from that, in-process, with no round trip — the same answer the server gives.

Not inside a query clause. Pay.Where(p => !Security.IsMasked(p.Salary)) is refused: read the rows, then ask of each.

Examples#

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

[Role] enum AppRole { Staff }
[RoleGrant] entity RoleGrant { Member Grantee; [Required] AppRole Level; }
policy IsStaff => RoleGrant.Any(g => g.Grantee == user && g.Level == AppRole.Staff);

entity Pay {
  [MaxLength(100)] string Label = "";
  decimal Salary;
  security {
    allow read when IsAuthenticated;
    deny read Salary when !IsStaff;   // everybody else reads 0
  }
}

/// What a person may be shown for one pay row.
string SalaryText(Pay p) {
  return Security.IsMasked(p.Salary) ? "hidden" : p.Salary.ToString("0.00");
}

See also#

Related

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…

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

`Security.MayRead(person, row)` answers whether a given person may read a given row under THEIR OWN security — the…

when is who, where is which rows

A rule's `when` says WHO it is for and its `where` says WHICH ROWS. A rule whose `when` does not hold is never asked…

runas

Runs a block as a given principal, so security rules apply exactly as they would for that user. It is how you test that…