Member Junction
    Preparing search index...

    Class RecordChangeFieldSecurityProjector

    Field-level security for the payload columns of MJ: Record Changes.

    Every other FLS enforcement point computes the denied set against the entity being read. A Record Change row is about a DIFFERENT entity — the one named by its own EntityID column — and MJ: Record Changes itself has field security switched off in every default deployment. The main projection therefore short-circuits on EnableFieldLevelSecurity and returns the row untouched, with a denied field's old and new values sitting in ChangesJSON in plain text. That is the leak this closes: a user with entity-level read on the audit trail could read a salary they were denied on MJ: Employees straight out of it, in the default configuration.

    Column Treatment
    ChangesJSON projected — denied field keys dropped
    FullRecordJSON projected — same, it is a whole-row snapshot
    ChangesDescription withheld entirely whenever the caller is denied anything on the target entity

    ChangesDescription is human prose ("Salary changed from 100000 to 120000"). Redacting prose on the fly leaks on the first value that appears in an unexpected form, and regenerating it from the filtered JSON is a larger surface than this is worth. Withholding the whole column is coarse and it is safe, which is the right trade for a field whose only job is to be readable text — and callers already degrade, the record-changes UI falling back to 'Changes made'.

    EntityID varies row to row, and EntityInfo.GetDeniedReadFields walks every field on the entity — its documented performance contract is that it must not be called in a row loop. Every lookup here is memoized on the raw EntityID string for the projector's lifetime, which is one request. Construct one projector per request, never one per row.

    Index

    Constructors

    Methods

    • Whether this caller is denied ANY field on ANY field-security-enabled entity.

      The whole projector is a no-op for everyone else, and this gate is what makes that true — including for the fail-closed branch in deniedFieldsFor, which would otherwise blank audit rows on deployments that never enabled field security at all. Cost is one pass over the entity list plus one denied-set walk per ENABLED entity, and the flag is off on every entity an administrator has not explicitly opted in.

      Public because the WRITE side needs the same question: a caller who could have been served a narrowed payload must not be allowed to write one back. See ResolverBase.StripRecordChangePayloadFromClientInput.

      Returns boolean

    • Projects a whole result set. Returns the ORIGINAL array when nothing needed changing, so an unrestricted caller's rows are never rebuilt — and, on the cache-hit paths, so the cache's own frozen objects are handed back by reference exactly as they are today.

      Type Parameters

      • T

      Parameters

      • rows: T[]

      Returns T[]