QueryPathPolicy
in package
Restricts the field paths in `filters` / `orderBys` / `columns` to fields the caller would also receive in the output.
FieldAccess and PersonalData filter only the emitted
payload, but a where clause is an equally good channel: filters[...][field]=user.password with a
like start match returns a record or nothing, which allows reconstructing a password hash, salt
or confirming someone else's e-mail character by character. Ordering by a hidden field leaks the
same through the order.
An unprivileged caller (same decision as the emit, FieldAudienceContext::allows()) may use only path segments that are mapped fields or associations of the entity and have no read audience that would withhold the field from the output, and no PersonalData. A privileged caller (admin) and an unknown audience (cron, CLI) pass unchanged.
Ready-made Filter objects are not checked: from the wire only arrays arrive, and objects are built by server PHP code, which chooses its fields itself.
An unknown segment (typo, nonexistent field) is rejected on the external path, otherwise it would end in a Doctrine exception (HTTP 500); on the internal path it passes, so front end behavior is unchanged.
columns goes into DQL verbatim (alias.<column>), so an external caller may send only the bare
name of a mapped field of the root entity.
Table of Contents
Constants
- GROUP_TYPES : array<string|int, mixed> = ['group', 'or', 'and']
- Types whose `filters` carry nested definitions (same as `FilterBuilder`).
- LANGUAGE_SCOPED : string = '/^([A-Za-z_][A-Za-z0-9_]*)\[languageId:\d+\]\....
- `translations[languageId:2].name`: ordering by a translation in one language.
- PLAIN_FIELD : string = '/^[A-Za-z_][A-Za-z0-9_]*$/'
- Bare field name; anything else in `columns` from the wire is suspicious.
Properties
- $audience : FieldAudienceContext
- $em : EntityManagerInterface
- $rulesCache : array<string, array<string, Audience, 1: bool}>>
Methods
- __construct() : mixed
- findDeniedPath() : string|null
- Returns the first denied path, or `null` when everything is allowed.
- isPathAllowed() : bool
- Returns whether an unprivileged caller may use this dotted path on the entity.
- collectRules() : array<string, Audience, 1: bool}>
- filterPaths() : array<int, string>
- Collects all field paths from the filter definition arrays (recursively through groups).
- hasPolicyAttribute() : bool
- isHidden() : bool
- Returns whether the field would be withheld from an unprivileged caller in the output (`FieldAccess` read / `PersonalData`).
- orderByPath() : string|null
- Returns the field path from an ordering key ({@see OrderByHelper::addOrderBys()}); `null` is a virtual key without an entity field (current price in a currency).
Constants
GROUP_TYPES
Types whose `filters` carry nested definitions (same as `FilterBuilder`).
private
array<string|int, mixed>
GROUP_TYPES
= ['group', 'or', 'and']
LANGUAGE_SCOPED
`translations[languageId:2].name`: ordering by a translation in one language.
private
string
LANGUAGE_SCOPED
= '/^([A-Za-z_][A-Za-z0-9_]*)\[languageId:\d+\]\.([A-Za-z_][A-Za-z0-9_]*)$/'
PLAIN_FIELD
Bare field name; anything else in `columns` from the wire is suspicious.
private
string
PLAIN_FIELD
= '/^[A-Za-z_][A-Za-z0-9_]*$/'
Properties
$audience read-only
private
FieldAudienceContext
$audience
$em read-only
private
EntityManagerInterface
$em
$rulesCache
private
array<string, array<string, Audience, 1: bool}>>
$rulesCache
= []
class => field => [read audience, personal data]
Methods
__construct()
public
__construct(FieldAudienceContext $audience, EntityManagerInterface $em) : mixed
Parameters
- $audience : FieldAudienceContext
- $em : EntityManagerInterface
findDeniedPath()
Returns the first denied path, or `null` when everything is allowed.
public
findDeniedPath(string $entityClass, array<string|int, mixed> $filters[, array<string|int, mixed>|null $orderBys = null ][, array<string|int, mixed>|null $columns = null ][, bool $external = true ]) : string|null
Parameters
- $entityClass : string
- $filters : array<string|int, mixed>
-
filter definitions (arrays and ready-made objects)
- $orderBys : array<string|int, mixed>|null = null
-
field => direction - $columns : array<string|int, mixed>|null = null
- $external : bool = true
-
external HTTP: unknown segments and non-bare
columnsare rejected
Return values
string|nullisPathAllowed()
Returns whether an unprivileged caller may use this dotted path on the entity.
public
isPathAllowed(string $entityClass, string $path[, bool $external = true ]) : bool
Parameters
- $entityClass : string
- $path : string
- $external : bool = true
Return values
boolcollectRules()
private
static collectRules(string $class) : array<string, Audience, 1: bool}>
Parameters
- $class : string
Return values
array<string, Audience, 1: bool}> —field => [read audience, personal data]
filterPaths()
Collects all field paths from the filter definition arrays (recursively through groups).
private
filterPaths(array<string|int, mixed> $filters) : array<int, string>
Parameters
- $filters : array<string|int, mixed>
Return values
array<int, string>hasPolicyAttribute()
private
static hasPolicyAttribute(ReflectionProperty $property) : bool
Parameters
- $property : ReflectionProperty
Return values
boolisHidden()
Returns whether the field would be withheld from an unprivileged caller in the output (`FieldAccess` read / `PersonalData`).
private
isHidden(ClassMetadata<string|int, object> $metadata, string $field) : bool
Parameters
- $metadata : ClassMetadata<string|int, object>
- $field : string
Return values
boolorderByPath()
Returns the field path from an ordering key ({@see OrderByHelper::addOrderBys()}); `null` is a virtual key without an entity field (current price in a currency).
private
static orderByPath(string $key) : string|null
Parameters
- $key : string