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?
protectedoptional_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
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
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
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
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
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
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.