CoolCMS API

PersonalDataPolicy
in package

FinalYes

Applies the personal-data policy: personal data of other users is returned to an unprivileged caller according to the global setting.

Fields marked PersonalData are returned:

  • in full to a privileged caller (admin) or to the record owner (PersonalDataOwner);
  • otherwise by the PersonalDataExposure mode in 1/security.personalDataExposure (sanitized is the default, also when the setting row is missing; full; empty).

Text Hidden fields (author IPs) are emptied; non-text ones (user wallet state such as credits, points, discount) are dropped from the row instead, because an empty string would break the int setter of the consumer. Email is masked; phone, address, company id, VAT id and company follow the mode. The seller card reads the public profile (publicProfile) and the phone reveal showPhone, not includes=user.

Outside a request (cron, CLI, emails, invoice generators) the audience is unknown and nothing is masked, the same boundary as FieldAccess. Notifications that need the real email of the other party read the entity through the manager, not through this boundary.

IMPORTANT: the policy is applied to the finished payload at send time, never in the hydrator. The hydrator output feeds a query cache keyed independently of the caller; an owner-based mask inside the cache would be fixed by the first caller, and the owner's full data would be served to the next visitor. It runs on both API outputs, BasePresenter::sendJson() (external HTTP) and ApiBridge (internal calls), and on writes in self::stripForeignUpdate().

The whole payload is walked: every _references (nested ones too, e.g. data.user in a login response) and the embedded collections inside rows (a OneToMany is embedded whole).

Table of Contents

Constants

SETTING_KEY  : string = 'security.personalDataExposure'
SETTING_MODULE  : string = '1'

Properties

$audience  : FieldAudienceContext
$classMeta  : array<string, PersonalDataKind, 1: bool}>, owner: ?string, via: ?string, ownerTarget: ?string, embedded: array}>
$connection  : Connection
$em  : EntityManagerInterface
$exposure  : PersonalDataExposure|null

Methods

__construct()  : mixed
apply()  : mixed
Applies the policy to the payload sent to the caller.
exposure()  : PersonalDataExposure
guardsUpdateOf()  : bool
Cheap pre-check for writes: is it worth loading the existing record for {@see self::stripForeignUpdate()}?
mask()  : string|null
maskDetected()  : string|null
Masks a field with `detect: true`, where personal data is searched inside the value.
stripForeignUpdate()  : array<string, mixed>
Strips personal data from the payload when writing a foreign record (an unprivileged caller who is not the owner).
entityOwnerId()  : int|null
Returns the owner id of a loaded entity (for writes), also through the parent (`via`).
maskRow()  : void
meta()  : PersonalDataKind, 1: bool}>, owner: ?string, via: ?string, ownerTarget: ?string, embedded: array}
ownedByCapability()  : bool
Returns whether the row belongs to a record the caller proved access to by a capability.
pointerId()  : int|null
Extracts the id from a pointer `{id: {id: N}}`, `{id: N}` or a scalar.
rowOwnerId()  : int|null
walk()  : void

Constants

Properties

$classMeta

private array<string, PersonalDataKind, 1: bool}>, owner: ?string, via: ?string, ownerTarget: ?string, embedded: array}> $classMeta = []

Methods

apply()

Applies the policy to the payload sent to the caller.

public apply(mixed $payload[, array<string, array<int, int>> $capabilityRows = [] ]) : mixed

An unknown audience, a privileged caller and the full mode return the payload unchanged.

$capabilityRows lists records the caller proved access to by a capability (advert password), not by identity: [entity class => [id, ...]]. The caller counts as owner only of the row of that class with that id and of rows whose owner leads through the parent (via) to it (advert parameter to advert). Every other row of the same response (user from includes, reaction, another advert) stays masked.

IMPORTANT: build $capabilityRows only from an entity loaded from the DB (see BasePresenter::exemptPersonalDataForCapabilityHolder()), never from caller data, or the caller could exempt any row from masking.

Parameters
$payload : mixed
$capabilityRows : array<string, array<int, int>> = []

guardsUpdateOf()

Cheap pre-check for writes: is it worth loading the existing record for {@see self::stripForeignUpdate()}?

public guardsUpdateOf(string $entityClass) : bool

True only for an unprivileged caller and an entity with marked fields; other saves cost no extra query.

Parameters
$entityClass : string
Return values
bool

maskDetected()

Masks a field with `detect: true`, where personal data is searched inside the value.

public static maskDetected(PersonalDataKind $kind, string|null $value, PersonalDataExposure $mode) : string|null

Email replaces only email addresses (mask or empty by mode) and keeps the other text; a value without an address stays unchanged (price, city).

Parameters
$kind : PersonalDataKind
$value : string|null
$mode : PersonalDataExposure
Return values
string|null

stripForeignUpdate()

Strips personal data from the payload when writing a foreign record (an unprivileged caller who is not the owner).

public stripForeignUpdate(array<string, mixed> $data, string $entityClass, object|null $existing) : array<string, mixed>

IMPORTANT: without the strip, a front end that read the record masked would overwrite the real email with the mask when saving the whole entity. Only the owner or the admin may change foreign contact data.

Parameters
$data : array<string, mixed>
$entityClass : string
$existing : object|null
Return values
array<string, mixed>

entityOwnerId()

Returns the owner id of a loaded entity (for writes), also through the parent (`via`).

private entityOwnerId(object $entity, string $field, string|null $via) : int|null
Parameters
$entity : object
$field : string
$via : string|null
Return values
int|null

maskRow()

private maskRow(string $class, array<string|int, mixed> &$row, PersonalDataExposure $mode, int|null $userId, array<string|int, mixed> $lookup, array<string, array<int, int>> $capabilityRows) : void
Parameters
$class : string
$row : array<string|int, mixed>
$mode : PersonalDataExposure
$userId : int|null
$lookup : array<string|int, mixed>

_references of the same response

$capabilityRows : array<string, array<int, int>>

meta()

private meta(string $class) : PersonalDataKind, 1: bool}>, owner: ?string, via: ?string, ownerTarget: ?string, embedded: array}
Parameters
$class : string
Return values
PersonalDataKind, 1: bool}>, owner: ?string, via: ?string, ownerTarget: ?string, embedded: array}

ownedByCapability()

Returns whether the row belongs to a record the caller proved access to by a capability.

private ownedByCapability(string $class, PersonalDataKind, 1: bool}>, owner: ?string, via: ?string, ownerTarget: ?string, embedded: array} $meta, array<string|int, mixed> $row, array<string, array<int, int>> $capabilityRows) : bool

A row without via is compared by its own id and class; a row with via by the parent id in the pointer and the parent class from Doctrine metadata (not from the payload). A row without an owner (PersonalDataOwner missing) never gets the capability exemption; reactions and reports on an advert are not data of its author.

Parameters
$class : string
$meta : PersonalDataKind, 1: bool}>, owner: ?string, via: ?string, ownerTarget: ?string, embedded: array}
$row : array<string|int, mixed>
$capabilityRows : array<string, array<int, int>>
Return values
bool

pointerId()

Extracts the id from a pointer `{id: {id: N}}`, `{id: N}` or a scalar.

private static pointerId(mixed $value) : int|null
Parameters
$value : mixed
Return values
int|null

rowOwnerId()

private rowOwnerId(PersonalDataKind, 1: bool}>, owner: ?string, via: ?string, ownerTarget: ?string, embedded: array} $meta, array<string|int, mixed> $row, array<string|int, mixed> $lookup) : int|null
Parameters
$meta : PersonalDataKind, 1: bool}>, owner: ?string, via: ?string, ownerTarget: ?string, embedded: array}
$row : array<string|int, mixed>
$lookup : array<string|int, mixed>
Return values
int|null

walk()

private walk(array<string|int, mixed> &$node, PersonalDataExposure $mode, int|null $userId, array<string, array<int, int>> $capabilityRows) : void
Parameters
$node : array<string|int, mixed>
$mode : PersonalDataExposure
$userId : int|null
$capabilityRows : array<string, array<int, int>>
On this page

Search results