Skip to main content

Class: ACLUtils

Defined in: service-core/src/security/ACLUtils.ts:27

Common utility functions for working with AccessControlList objects and validating user permissions.

Constructors​

Constructor​

new ACLUtils(): ACLUtils

Returns​

ACLUtils

Properties​

_objectFactory?​

protected optional _objectFactory?: ObjectFactory

Defined in: service-core/src/security/ACLUtils.ts:29


enabled​

enabled: boolean = true

Defined in: service-core/src/security/ACLUtils.ts:32

Methods​

checkRequestPerms()​

checkRequestPerms(uid, user, req): Promise<boolean>

Defined in: service-core/src/security/ACLUtils.ts:150

Validates that the user has permission to perform the request operation against the URL path for the provided request. If ACLUtils has not been initialized or the acl datasource has not been configured then always returns true.

Parameters​

uid​

string

The uid of the access control list to verify against.

user​

JWTUser | undefined

The user to validate.

req​

HttpRequest

The HTTP request to check permissions for.

Returns​

Promise<boolean>


findACL()​

findACL(entityId, parentUids?, options?): Promise<AccessControlList | undefined>

Defined in: service-core/src/security/ACLUtils.ts:246

Retrieves the access control list with the associated identifier and populates the parent(s).

Deliberately not @Transactional and never participates in a caller's transaction: it's on the hot path for every permission check (hasPermission/checkRequestPerms), not just writes, so wrapping it would add a session open/close to every authorization check. It also has no way to safely reuse a caller's transaction context.

Parameters​

entityId​

string

The unique identifier of the ACL to retrieve.

parentUids?​

string[] = []

The list of already found parent UIDs. This is used to break circular dependencies.

options?​

Set skipCache: true to bypass the cache and always read the current database state — mirrors RepoUtils's RepoFindOptions.skipCache (applies to the whole parent chain). A database read is written back into the cache afterward. Set skipParents: true to not populate the parent chain at all, when only the ACL itself is needed.

skipCache?​

boolean

skipParents?​

boolean

Returns​

Promise<AccessControlList | undefined>


getRecord()​

getRecord(acl, user, options?): ACLRecord | null

Defined in: service-core/src/security/ACLUtils.ts:772

Retrieves the most specific record in the provided ACL associated with the provided user. An exact uid/anonymous match beats a role match, which beats a wildcard (.*/*) match, regardless of where each record sits in acl.records. Without this, a wildcard grant authored before a specific record (a natural authoring order) would silently shadow that more specific record. If no record can be found in the ACL provided, searches each parent for a matching record.

The depth of the search can be limited by setting the options.maxDepth option to a non-negative value. A value of 0 only searches the provided ACL, a value of 1 searches the provided ACL and its direct parent, a value of 2 searches the ACL, it's parent and grand-parent, and so on. A value of -1 (the default) means no limit; the entire parent chain is searched.

Since records support regex patterns, it is also possible to cap the match quality (specificity) via the options.specificity option. This is a ceiling, not an exact requirement: setting options.specificity to exact only matches the user's UID exactly. A value of role matches the UID or a user's role (preferring the UID match). A value of wildcard matches the UID, role, or any regex pattern (e.g. .* or user*), preferring the most specific of those that match.

The default options are:

{
maxDepth: -1 // no limit,
specificity: "wildcard"
}

Parameters​

acl​

AccessControlList

The access control list that will be searched.

user​

JWTUser | undefined

The user to find a record for.

options?​

The set of options to consider

curDepth?​

number

maxDepth?​

number

specificity?​

"exact" | "role" | "wildcard"

Returns​

ACLRecord | null

The ACL record associated with the given user if found, otherwise undefined.


hasPermission()​

hasPermission(user, acl, action): Promise<boolean>

Defined in: service-core/src/security/ACLUtils.ts:202

Validates that the user has permission to perform the provided action using the given access control list.

Parameters​

user​

JWTUser | undefined

The user to validate permissions of.

acl​

string | AccessControlList

The ACL or uid of an ACL to validate permissions against.

action​

string

The action that the user desires permission for.

Returns​

Promise<boolean>

true if the user has at least one of the permissions granted for the given entity, otherwise false.


invalidateACLs()​

invalidateACLs(uids): Promise<void>

Defined in: service-core/src/security/ACLUtils.ts:134

Drops the cached copies of the given ACLs, so the next permission check reads them from the database. Any code that writes ACL documents without going through saveACL()/removeACL() must call this (e.g. RepoUtils serving the ACL REST routes).

Parameters​

uids​

string[]

The uids of the ACLs to invalidate.

Returns​

Promise<void>


isProtectedACL()​

isProtectedACL(acl): boolean

Defined in: service-core/src/security/ACLUtils.ts:123

Determines whether acl is a code-defined ACL: its uid is reserved (see isReservedUid()), or it has the shape of the user-editable half of a default ACL (its parent is default_<uid>). The latter also catches one registered by a route or model that this process doesn't load.

Parameters​

acl​

AccessControlList | null | undefined

The ACL to check.

Returns​

boolean


isReservedUid()​

isReservedUid(uid): boolean

Defined in: service-core/src/security/ACLUtils.ts:112

Determines whether uid names a code-defined ACL: any default_<uid> ACL, or a class/route/endpoint ACL (or its default_ counterpart) registered through saveDefaultACL(). Such an ACL governs a whole model or route, so a record must never be created at, adopt, or remove it (see RepoUtils.create()/delete()/truncate()).

Parameters​

uid​

string | undefined

The ACL uid to check.

Returns​

boolean


populateParent()​

populateParent(acl, parentUids?): Promise<void>

Defined in: service-core/src/security/ACLUtils.ts:829

Attempts to retrieve the parent access control list for the given ACL object.

Parameters​

acl​

AccessControlList

The access control list whose parents will be populated.

parentUids?​

string[] = []

The list of already found parent UIDs. This is used to break circular dependencies.

Returns​

Promise<void>


removeACL()​

removeACL(uid, options?): Promise<AccessControlList | undefined>

Defined in: service-core/src/security/ACLUtils.ts:320

Deletes the ACL with the given identifier from the database, returning the document that was actually deleted (or undefined if none existed) — captured atomically as part of the delete itself (Mongo: findOneAndDelete; SQL: a findOne immediately followed by the delete, both inside this method's own acl transaction), not a separate, earlier read. A caller using the return value as a restore snapshot (see RepoUtils.delete()/truncate()'s registerRollbackHook() usage) always gets the exact row that was removed, never a possibly-stale one raced by a concurrent legitimate write between an earlier read and this delete — and restoring it later still goes through saveACL()'s own optimistic-lock version check, so a restore attempt can't clobber a newer row that appeared after this delete either.

Runs in its own transaction scoped to the acl connection (see Transactional) rather than trying to join whatever transaction the caller is itself in — acl is frequently configured as its own, separate datastore (a documented, supported deployment shape), so a caller's session/entityManager almost never actually belongs to the same physical connection this repo does. Reusing it directly used to throw (Mongo) or silently target the wrong connection (SQL).

Pass unlessProtected: true to leave a code-defined ACL (see isProtectedACL()) in place, resolving to undefined as if there had been nothing to remove. RepoUtils uses this when cleaning up after a record, so a record that shares a class/route ACL's uid can never delete that ACL.

Parameters​

uid​

string

The unique identifier of the ACL to remove.

options?​

Set unlessProtected: true to never remove a code-defined ACL.

unlessProtected?​

boolean

Returns​

Promise<AccessControlList | undefined>


removeACLs()​

removeACLs(uids, options?): Promise<AccessControlList[]>

Defined in: service-core/src/security/ACLUtils.ts:712

Atomic removal of multiple ACLs in a single acl-scoped transaction (see @Transactional), returning whichever of them actually existed to be deleted (see removeACL()'s doc comment for why the returned documents — not a separate, earlier read — are the right thing to snapshot for a possible restore).

Deliberately sequential, not batched-concurrent: every removeACL() call below resolves to this same acl datasource, so @Transactional's merge logic joins all of them onto the one MongoDB ClientSession/SQL EntityManager this method opened. Running these with Promise.all would issue overlapping commands against one session, which drivers reject or misorder.

Parameters​

uids​

string[]

The unique identifiers of the ACLs to remove.

options?​

Set unlessProtected: true to never remove a code-defined ACL (see removeACL()).

unlessProtected?​

boolean

Returns​

Promise<AccessControlList[]>


saveACL()​

saveACL(acl, options?): Promise<AccessControlList | null>

Defined in: service-core/src/security/ACLUtils.ts:468

Stores the given access control list into the ACL database. By default, bumps the version for optimistic locking (see below) — pass preserveVersion: true to instead write acl back with its own version exactly as given, for restoring a known-good snapshot (see RepoUtils.delete()/truncate()'s registerRollbackHook() usage) rather than applying a new change on top of whatever's currently stored. A restore only ever targets a uid with nothing currently at it (that's the point — the entity-side transaction it was tied to is being rolled back, so the row it deleted should no longer exist); if something does exist there already, the restore is refused rather than silently clobbering it.

Runs in its own transaction scoped to the acl connection (see Transactional) rather than trying to join whatever transaction the caller is itself in — see removeACL's doc comment for why. Callers that need the entity-side write and this ACL save to stay consistent should register a compensating action via registerRollbackHook().

Pass createOnly: true to claim a brand new ACL: it is only ever inserted (at version 0), never merged into or overwritten on top of an ACL that already exists at the same uid. If one already exists - including one a concurrent caller inserted first, caught by the ACL collection's unique key - an IDENTIFIER_EXISTS ApiError (400) is thrown instead. Used by RepoUtils.create() so that a create can never adopt (and grant its creator rights on) an ACL that belongs to some other record.

Parameters​

acl​

AccessControlList

The ACL to store.

options?​

Set preserveVersion: true to restore acl exactly as given (see above) instead of the normal optimistic-locking update semantics. Set createOnly: true to only ever insert a new ACL (see above).

createOnly?​

boolean

preserveVersion?​

boolean

Returns​

Promise<AccessControlList | null>

Returns the ACL that was stored in the database.


saveACLs()​

saveACLs(acls): Promise<void>

Defined in: service-core/src/security/ACLUtils.ts:735

Atomic save of multiple ACLs in a single acl-scoped transaction (see Transactional). Used to restore a batch of ACL snapshots — e.g. by a registerRollbackHook() compensating action — if the entity-side transaction they were removed alongside subsequently fails. Always restores each ACL's own version as given (see saveACL()'s preserveVersion option) rather than bumping it, since this is a restore, not a new change.

Deliberately sequential. See removeACLs()'s doc comment for why.

Parameters​

acls​

AccessControlList[]

The ACLs to store.

Returns​

Promise<void>


saveDefaultACL()​

saveDefaultACL(acl): Promise<AccessControlList | null>

Defined in: service-core/src/security/ACLUtils.ts:631

Stores the given default access control list into the ACL database. A default ACL is a special type of ACL that is primarily defined and maintained within the code but allows for user-specific overrides. To accomplish this, the provided ACL is split in two. A new record is automatically created with the uid of the form default_<uid> that stores the exact record as provided by code. Then a second ACL record is created with the uid being that of what is passed as the argument. This second ACL is used to store user-defined overrides. As the default_<uid> record is always overwritten with the lastest version of the code, any user-defined changes made to it are lost on service restart.

Both uids are reserved (see isReservedUid()), so no record can be created at, adopt or remove either ACL.

Contract: when RBAC is enabled and acl is given, this either resolves to the user-editable ACL (whose uid is acl.uid) or throws; it never resolves to null then. A missing user-editable ACL (e.g. one deleted since the last start) is recreated, with no records, under default_<uid>. It resolves to null only when RBAC is disabled or no acl is given, and then stores nothing.

Parameters​

acl​

AccessControlList

The code-defined ACL to store.

Returns​

Promise<AccessControlList | null>

The user-editable ACL, or null when RBAC is disabled or no ACL was given.