CoolCMS API

JwtCapability
in package

FinalYes

One capability carried in a JWT access token: proof of access to a single record.

It is neither a role nor an identity. It arises only for an anonymous advert editor who entered the advert password or opened the "Manage advert" link (AdvertAuthorizationFormTrait), and for a guest author of a discussion post who opened the "Manage post" link.

Fields:

  • scope: the domain (bazaar.advert.edit, comcat.advert.edit, discussion.message.edit); without it a Bazaar capability would authorize a company or a post with the same id.
  • id: the one record it applies to, never a blanket grant.
  • expiration: unix time, a copy of the session token expiration.
  • fingerprint: sha256 of the password hash stored in the DB at issue time.

IMPORTANT: a capability never carries a foreign identity. The token has userId = 0 (guest, see PermissionResolver). Substituting the advert owner's insertUserId would give an anonymous editor all rights of that account.

IMPORTANT: the fingerprint lets a password change by the owner revoke all open editing sessions at once. The API side compares the token fingerprint with the fingerprint of the password of the entity loaded from the DB (DB against token, never token against token). With only [id, expiration] the capability would survive a password change until expiry.

NOTE: the token carries a fingerprint, not the bcrypt hash itself; any password change changes it, and it is useless as a credential elsewhere. Compare it with hash_equals(), not ===.

Table of Contents

Constants

SCOPE_BAZAAR_ADVERT_EDIT  : string = 'bazaar.advert.edit'
Editing a Bazaar advert reached by an anonymous user through the advert password.
SCOPE_COMCAT_ADVERT_EDIT  : string = 'comcat.advert.edit'
The same for the company catalog (Comcat); a separate scope keeps the `id` values apart.
SCOPE_DISCUSSION_MESSAGE_EDIT  : string = 'discussion.message.edit'
Editing a discussion post reached by its guest author through the "Manage post" link from the insert e-mail; the fingerprint is taken from `Message::$password` (a random secret of a guest post).

Properties

$expiration  : int
$fingerprint  : string
$id  : int
$scope  : string

Methods

__construct()  : mixed
fingerprint()  : string
Returns the fingerprint of the record password.
fromValue()  : self|null
Tolerantly deserializes a capability, returning null for anything that lacks any of the four parts in a usable shape.
getExpiration()  : int
getFingerprint()  : string
getId()  : int
getScope()  : string
grants()  : bool
Returns whether any of the capabilities applies to this record.
matches()  : bool
Returns whether this capability applies to exactly this record right now.
toArray()  : array{scope: string, id: int, exp: int, fp: string}

Constants

SCOPE_BAZAAR_ADVERT_EDIT

Editing a Bazaar advert reached by an anonymous user through the advert password.

public string SCOPE_BAZAAR_ADVERT_EDIT = 'bazaar.advert.edit'

SCOPE_COMCAT_ADVERT_EDIT

The same for the company catalog (Comcat); a separate scope keeps the `id` values apart.

public string SCOPE_COMCAT_ADVERT_EDIT = 'comcat.advert.edit'

SCOPE_DISCUSSION_MESSAGE_EDIT

Editing a discussion post reached by its guest author through the "Manage post" link from the insert e-mail; the fingerprint is taken from `Message::$password` (a random secret of a guest post).

public string SCOPE_DISCUSSION_MESSAGE_EDIT = 'discussion.message.edit'

Properties

Methods

__construct()

public __construct(string $scope, int $id, int $expiration, string $fingerprint) : mixed
Parameters
$scope : string
$id : int
$expiration : int
$fingerprint : string

fingerprint()

Returns the fingerprint of the record password.

public static fingerprint(string|null $passwordHash) : string

A null or empty hash gives an empty fingerprint that self::matches() never accepts, so a record without a password cannot be edited through a capability (fail-closed).

Parameters
$passwordHash : string|null
Return values
string

fromValue()

Tolerantly deserializes a capability, returning null for anything that lacks any of the four parts in a usable shape.

public static fromValue(mixed $value) : self|null

One broken capability must not discard the whole token (and log out a live session).

NOTE: the input may be a stdClass, because JwtManager::decodeToken() casts only the top level with (array) $decoded->data and nested objects stay objects.

Parameters
$value : mixed
Return values
self|null

getExpiration()

public getExpiration() : int
Return values
int

getFingerprint()

public getFingerprint() : string
Return values
string

grants()

Returns whether any of the capabilities applies to this record.

public static grants(array<int, self> $capabilities, string $scope, int $id, string $fingerprint[, int|null $now = null ]) : bool

The only place that walks the list, so the rule cannot diverge between Bazaar and Comcat.

Parameters
$capabilities : array<int, self>
$scope : string
$id : int
$fingerprint : string
$now : int|null = null
Return values
bool

matches()

Returns whether this capability applies to exactly this record right now.

public matches(string $scope, int $id, string $fingerprint[, int|null $now = null ]) : bool

Fail-closed on all four axes: scope, id, expiration, password fingerprint from the DB.

Parameters
$scope : string
$id : int
$fingerprint : string
$now : int|null = null
Return values
bool

toArray()

public toArray() : array{scope: string, id: int, exp: int, fp: string}
Return values
array{scope: string, id: int, exp: int, fp: string}
On this page

Search results