UNPKG

@superawesome/permissions

Version:

Fine grained permissions / access control with ownerships & attribute picking, done right.

1,284 lines (1,083 loc) 52.8 kB
<!doctype html> <html class="no-js" lang=""> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <title>SuperAwesome Permissions (@superawesome/permissions)</title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="../images/favicon.ico"> <link rel="stylesheet" href="../styles/style.css"> <link rel="stylesheet" href="../styles/postmark.css"> </head> <body> <div class="navbar navbar-default navbar-fixed-top visible-xs"> <a href="../" class="navbar-brand">SuperAwesome Permissions (@superawesome/permissions)</a> <button type="button" class="btn btn-default btn-menu ion-ios-menu" id="btn-menu"></button> </div> <div class="xs-menu menu" id="mobile-menu"> <div id="book-search-input" role="search"><input type="text" placeholder="Type to search"></div> <compodoc-menu></compodoc-menu> </div> <div class="container-fluid main"> <div class="row main"> <div class="hidden-xs menu"> <compodoc-menu mode="normal"></compodoc-menu> </div> <!-- START CONTENT --> <div class="content class"> <div class="content-data"> <ol class="breadcrumb"> <li>Classes</li> <li>Permissions</li> </ol> <ul class="nav nav-tabs" role="tablist"> <li class="active"> <a href="#info" role="tab" id="info-tab" data-toggle="tab" data-link="info">Info</a> </li> <li > <a href="#source" role="tab" id="source-tab" data-toggle="tab" data-link="source">Source</a> </li> </ul> <div class="tab-content"> <div class="tab-pane fade active in" id="c-info"> <p class="comment"> <h3>File</h3> </p> <p class="comment"> <code>src/Permissions.ts</code> </p> <p class="comment"> <h3>Description</h3> </p> <p class="comment"> <p>The main class - see <a href="/additional-documentation/basic-usage.html">Basic Usage</a></p> </p> <section> <h3 id="index">Index</h3> <table class="table table-sm table-bordered index-table"> <tbody> <tr> <td class="col-md-4"> <h6><b>Methods</b></h6> </td> </tr> <tr> <td class="col-md-4"> <ul class="index-list"> <li> <span class="modifier">Public</span> <a href="#addDefinitions">addDefinitions</a> </li> <li> <span class="modifier">Public</span> <a href="#build">build</a> </li> <li> <span class="modifier">Public</span> <a href="#compare">compare</a> </li> <li> <span class="modifier">Public</span> <a href="#getActions">getActions</a> </li> <li> <span class="modifier">Public</span> <a href="#getDefinitions">getDefinitions</a> </li> <li> <span class="modifier">Public</span> <a href="#getGrants">getGrants</a> </li> <li> <span class="modifier">Public</span> <a href="#getResources">getResources</a> </li> <li> <span class="modifier">Public</span> <a href="#getRoles">getRoles</a> </li> <li> <span class="modifier">Public</span> <span class="modifier">Async</span> <a href="#grantPermit">grantPermit</a> </li> </ul> </td> </tr> <tr> <td class="col-md-4"> <h6><b>Accessors</b></h6> </td> </tr> <tr> <td class="col-md-4"> <ul class="index-list"> <li> <a href="#isBuilt">isBuilt</a> </li> </ul> </td> </tr> </tbody> </table> </section> <section> <h3 id="constructor">Constructor</h3> <table class="table table-sm table-bordered"> <tbody> <tr> <td class="col-md-4"> <code>constructor(undefined: <a href="../interfaces/IPermissionsOptions.html">IPermissionsOptions<TUserId | TResourceId></a>)</code> </td> </tr> <tr> <td class="col-md-4"> <div class="io-line">Defined in <a href="" data-line="67" class="link-to-prism">src/Permissions.ts:67</a></div> </td> </tr> <tr> <td class="col-md-4"> <div> <b>Parameters :</b> <table class="params"> <thead> <tr> <td>Name</td> <td>Type</td> <td>Optional</td> </tr> </thead> <tbody> <tr> <td> <code><a href="../interfaces/IPermissionsOptions.html" target="_self" >IPermissionsOptions&lt;TUserId | TResourceId&gt;</a></code> </td> <td> No </td> </tr> </tbody> </table> </div> </td> </tr> </tbody> </table> </section> <section> <h3 id="methods"> Methods </h3> <table class="table table-sm table-bordered"> <tbody> <tr> <td class="col-md-4"> <a name="addDefinitions"></a> <span class="name"> <b> <span class="modifier">Public</span> addDefinitions </b> <a href="#addDefinitions"><span class="icon ion-ios-link"></span></a> </span> </td> </tr> <tr> <td class="col-md-4"> <span class="modifier-icon icon ion-ios-reset"></span> <code>addDefinitions(permissionDefinitions: <a href="../undefineds/PermissionDefinition.html">PermissionDefinition<TUserId | TResourceId> | PermissionDefinition<TUserId, TResourceId>[]</a>, permissionDefinitionDefaults: <a href="../classes/PermissionDefinitionDefaults.html">PermissionDefinitionDefaults</a>)</code> </td> </tr> <tr> <td class="col-md-4"> <div class="io-line">Defined in <a href="" data-line="78" class="link-to-prism">src/Permissions.ts:78</a></div> </td> </tr> <tr> <td class="col-md-4"> <div class="io-description"> <b>Parameters :</b> <table class="params"> <thead> <tr> <td>Name</td> <td>Type</td> <td>Optional</td> <td>Default value</td> </tr> </thead> <tbody> <tr> <td>permissionDefinitions</td> <td> <code><a href="../miscellaneous/typealiases.html#PermissionDefinition" target="_self" >PermissionDefinition&lt;TUserId | TResourceId&gt; | PermissionDefinition&lt;TUserId, TResourceId&gt;[]</a></code> </td> <td> No </td> <td> </td> </tr> <tr> <td>permissionDefinitionDefaults</td> <td> <code><a href="../classes/PermissionDefinitionDefaults.html" target="_self" >PermissionDefinitionDefaults</a></code> </td> <td> No </td> <td> <code>{}</code> </td> </tr> </tbody> </table> </div> <div> </div> <div class="io-description"> <b>Returns : </b> <code><a href="https://www.typescriptlang.org/docs/handbook/basic-types.html" target="_blank" >void</a></code> </div> <div class="io-description"> </div> </td> </tr> </tbody> </table> <table class="table table-sm table-bordered"> <tbody> <tr> <td class="col-md-4"> <a name="build"></a> <span class="name"> <b> <span class="modifier">Public</span> build </b> <a href="#build"><span class="icon ion-ios-link"></span></a> </span> </td> </tr> <tr> <td class="col-md-4"> <span class="modifier-icon icon ion-ios-reset"></span> <code>build()</code> </td> </tr> <tr> <td class="col-md-4"> <div class="io-line">Defined in <a href="" data-line="194" class="link-to-prism">src/Permissions.ts:194</a></div> </td> </tr> <tr> <td class="col-md-4"> <div class="io-description"> <b>Returns : </b> <code>this</code> </div> </td> </tr> </tbody> </table> <table class="table table-sm table-bordered"> <tbody> <tr> <td class="col-md-4"> <a name="compare"></a> <span class="name"> <b> <span class="modifier">Public</span> compare </b> <a href="#compare"><span class="icon ion-ios-link"></span></a> </span> </td> </tr> <tr> <td class="col-md-4"> <span class="modifier-icon icon ion-ios-reset"></span> <code>compare(permissions1: <a href="../classes/Permissions.html">Permissions<any | any></a>, permissions2: <a href="../classes/Permissions.html">Permissions<any | any></a>)</code> </td> </tr> <tr> <td class="col-md-4"> <div class="io-line">Defined in <a href="" data-line="383" class="link-to-prism">src/Permissions.ts:383</a></div> </td> </tr> <tr> <td class="col-md-4"> <div class="io-description"> <b>Parameters :</b> <table class="params"> <thead> <tr> <td>Name</td> <td>Type</td> <td>Optional</td> <td>Default value</td> </tr> </thead> <tbody> <tr> <td>permissions1</td> <td> <code><a href="../classes/Permissions.html" target="_self" >Permissions&lt;any | any&gt;</a></code> </td> <td> No </td> <td> </td> </tr> <tr> <td>permissions2</td> <td> <code><a href="../classes/Permissions.html" target="_self" >Permissions&lt;any | any&gt;</a></code> </td> <td> No </td> <td> <code>this</code> </td> </tr> </tbody> </table> </div> <div> </div> <div class="io-description"> <b>Returns : </b> <code><a href="https://www.typescriptlang.org/docs/handbook/basic-types.html" target="_blank" >any</a></code> </div> <div class="io-description"> </div> </td> </tr> </tbody> </table> <table class="table table-sm table-bordered"> <tbody> <tr> <td class="col-md-4"> <a name="getActions"></a> <span class="name"> <b> <span class="modifier">Public</span> getActions </b> <a href="#getActions"><span class="icon ion-ios-link"></span></a> </span> </td> </tr> <tr> <td class="col-md-4"> <span class="modifier-icon icon ion-ios-reset"></span> <code>getActions()</code> </td> </tr> <tr> <td class="col-md-4"> <div class="io-line">Defined in <a href="" data-line="366" class="link-to-prism">src/Permissions.ts:366</a></div> </td> </tr> <tr> <td class="col-md-4"> <div class="io-description"> <b>Returns : </b> <code>string[]</code> </div> </td> </tr> </tbody> </table> <table class="table table-sm table-bordered"> <tbody> <tr> <td class="col-md-4"> <a name="getDefinitions"></a> <span class="name"> <b> <span class="modifier">Public</span> getDefinitions </b> <a href="#getDefinitions"><span class="icon ion-ios-link"></span></a> </span> </td> </tr> <tr> <td class="col-md-4"> <span class="modifier-icon icon ion-ios-reset"></span> <code>getDefinitions(filter?: literal type, consolidateFlag: boolean | "force")</code> </td> </tr> <tr> <td class="col-md-4"> <div class="io-line">Defined in <a href="" data-line="403" class="link-to-prism">src/Permissions.ts:403</a></div> </td> </tr> <tr> <td class="col-md-4"> <div class="io-description"><p>Returns a list of the <code>PermissionDefinition</code> objects stored in this instance, with optional filtering &amp; consolidations removing duplicates and redundant grants (<strong>WARNING</strong>: this is experimental)</p> </div> <div class="io-description"> <b>Parameters :</b> <table class="params"> <thead> <tr> <td>Name</td> <td>Type</td> <td>Optional</td> <td>Default value</td> <td>Description</td> </tr> </thead> <tbody> <tr> <td>filter</td> <td> <code>literal type</code> </td> <td> Yes </td> <td> </td> <td> <p>allows you to filter PDs: Use an object eg <code>{ resource: &#39;document&#39; }</code> as the <code>_.matches</code> iteratee shorthand. If this <code>_.matches</code> object is used, the props used for filtering are considered &quot;default&quot; and are omitted from each PD. OR use a function returning boolean for each PD, eg (pd) =&gt; pd.resource === &#39;document&#39; See <a href="https://lodash.com/docs/4.17.11#filter">https://lodash.com/docs/4.17.11#filter</a></p> </td> </tr> <tr> <td>consolidateFlag</td> <td> <code>boolean | &quot;force&quot;</code> </td> <td> No </td> <td> <code>false</code> </td> <td> <p>is <strong>experimental</strong>, it tries to consolidate PermissionDefinitions, remove duplicates and merge compatible ones</p> </td> </tr> </tbody> </table> </div> <div> </div> <div class="io-description"> <b>Returns : </b> <code>Partial[]</code> </div> <div class="io-description"> </div> </td> </tr> </tbody> </table> <table class="table table-sm table-bordered"> <tbody> <tr> <td class="col-md-4"> <a name="getGrants"></a> <span class="name"> <b> <span class="modifier">Public</span> getGrants </b> <a href="#getGrants"><span class="icon ion-ios-link"></span></a> </span> </td> </tr> <tr> <td class="col-md-4"> <span class="modifier-icon icon ion-ios-reset"></span> <code>getGrants()</code> </td> </tr> <tr> <td class="col-md-4"> <div class="io-line">Defined in <a href="" data-line="376" class="link-to-prism">src/Permissions.ts:376</a></div> </td> </tr> <tr> <td class="col-md-4"> <div class="io-description"><p>Returns a deep clone of <a href="https://onury.io/accesscontrol/?api=ac#AccessControl#getGrants"><code>AccessControl#getGrants()</code></a> (which according to its docs <code>Gets the internal grants object that stores all current grants.</code>), but omitting empty arrays eg <code>&#39;rollover:any&#39;: []</code>.</p> </div> <div class="io-description"> <b>Returns : </b> <code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/object" target="_blank" >object</a></code> </div> </td> </tr> </tbody> </table> <table class="table table-sm table-bordered"> <tbody> <tr> <td class="col-md-4"> <a name="getResources"></a> <span class="name"> <b> <span class="modifier">Public</span> getResources </b> <a href="#getResources"><span class="icon ion-ios-link"></span></a> </span> </td> </tr> <tr> <td class="col-md-4"> <span class="modifier-icon icon ion-ios-reset"></span> <code>getResources()</code> </td> </tr> <tr> <td class="col-md-4"> <div class="io-line">Defined in <a href="" data-line="361" class="link-to-prism">src/Permissions.ts:361</a></div> </td> </tr> <tr> <td class="col-md-4"> <div class="io-description"> <b>Returns : </b> <code>string[]</code> </div> </td> </tr> </tbody> </table> <table class="table table-sm table-bordered"> <tbody> <tr> <td class="col-md-4"> <a name="getRoles"></a> <span class="name"> <b> <span class="modifier">Public</span> getRoles </b> <a href="#getRoles"><span class="icon ion-ios-link"></span></a> </span> </td> </tr> <tr> <td class="col-md-4"> <span class="modifier-icon icon ion-ios-reset"></span> <code>getRoles()</code> </td> </tr> <tr> <td class="col-md-4"> <div class="io-line">Defined in <a href="" data-line="356" class="link-to-prism">src/Permissions.ts:356</a></div> </td> </tr> <tr> <td class="col-md-4"> <div class="io-description"> <b>Returns : </b> <code>string[]</code> </div> </td> </tr> </tbody> </table> <table class="table table-sm table-bordered"> <tbody> <tr> <td class="col-md-4"> <a name="grantPermit"></a> <span class="name"> <b> <span class="modifier">Public</span> <span class="modifier">Async</span> grantPermit </b> <a href="#grantPermit"><span class="icon ion-ios-link"></span></a> </span> </td> </tr> <tr> <td class="col-md-4"> <span class="modifier-icon icon ion-ios-reset"></span> <code>grantPermit(undefined: GrantPermitQuery<TUserId | TResourceId>)</code> </td> </tr> <tr> <td class="col-md-4"> <div class="io-line">Defined in <a href="" data-line="213" class="link-to-prism">src/Permissions.ts:213</a></div> </td> </tr> <tr> <td class="col-md-4"> <div class="io-description"><p>The <code>grantPermit()</code> is the way to <em>query</em> the Permissions instance for granting permissions to a User.</p> <p>The method responds with an instance of <a href="/classes/Permit.html">Permit</a> that holds all known information about the queried <strong>user</strong>, <strong>resource</strong> and <strong>action</strong>.</p> <p>In short, the question is &quot;can some of <code>user.roles</code> perform <code>action</code> either a) on <strong>any</strong> <code>resource</code> or b) on an <strong>own</strong> <code>resource</code> (AND the specific <code>resourceId</code> if passed)?</p> <p>We are checking all roles for both <strong>any</strong> &amp; <strong>own</strong>, while collecting all <code>isOwner</code> &amp; <code>listOwned</code> and feed all known information into a <strong>Permit</strong> object.</p> </div> <div class="io-description"> <b>Parameters :</b> <table class="params"> <thead> <tr> <td>Name</td> <td>Type</td> <td>Optional</td> </tr> </thead> <tbody> <tr> <td> <code>GrantPermitQuery&lt;TUserId | TResourceId&gt;</code> </td> <td> No </td> </tr> </tbody> </table> </div> <div> </div> <div class="io-description"> <b>Returns : </b> <code><a href="../classes/Permit.html" target="_self" >Promise&lt;Permit&lt;TUserId, TResourceId&gt;&gt;</a></code> </div> <div class="io-description"> <p>Promise<Permit> a Promise of a <a href="/classes/Permit.html">Permit</a> instance.</p> </div> </td> </tr> </tbody> </table> </section> <section> <h3 id="accessors"> Accessors </h3> <table class="table table-sm table-bordered"> <tbody> <tr> <td class="col-md-4"> <a name="isBuilt"></a> <span class="name"><b>isBuilt</b><a href="#isBuilt"><span class="icon ion-ios-link"></span></a></span> </td> </tr> <tr> <td class="col-md-4"> <span class="accessor"><b>get</b><code>isBuilt()</code></span> </td> </tr> <tr> <td class="col-md-4"> <div class="io-line">Defined in <a href="" data-line="190" class="link-to-prism">src/Permissions.ts:190</a></div> </td> </tr> <tr> <td class="col-md-4"> <div class="io-description"><p>Check is this Permissions instance has been built (so no more .addDefinitions() allowed)</p> </div> <div class="io-description"> <b>Returns : </b> <code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/boolean" target="_blank" >boolean</a></code> </div> </td> </tr> </tbody> </table> </section> </div> <div class="tab-pane fade tab-source-code" id="c-source"> <pre class="line-numbers compodoc-sourcecode"><code class="language-typescript">import * as _ from &#x27;lodash&#x27;; import * as _f from &#x27;lodash/fp&#x27;; import { diff } from &#x27;json-diff&#x27;; import { AccessControl, IQueryInfo, Permission } from &#x27;accesscontrol&#x27;; // own import { AccessControlRe } from &#x27;accesscontrol-re&#x27;; import { EPossession, GrantPermitQuery, isValidIUser, Tid, TisOwner, TlimitOwned, TlimitOwnReduce, TlistOwned, } from &#x27;./types&#x27;; import { buildAccessControl, deleteEmptyArrayKeys, hasSomeOwnGrant, stringify, projectPDWithDefaultsToInternal, } from &#x27;./utils&#x27;; import { consolidatePermissionDefinitions } from &#x27;./consolidations&#x27;; import { Permit } from &#x27;./Permit.class&#x27;; import { PermissionDefinition, PermissionDefinitionDefaults, PermissionDefinitionInternal, } from &#x27;./PermissionDefinitions&#x27;; import { getLogger } from &#x27;./logger&#x27;; /** The options passed at the &#x60;Permissions&#x60; constructor */ export interface IPermissionsOptions&lt; TUserId extends Tid &#x3D; number, TResourceId extends Tid &#x3D; number &gt; { permissionDefinitions?: | PermissionDefinition&lt;TUserId, TResourceId&gt; | PermissionDefinition&lt;TUserId, TResourceId&gt;[]; permissionDefinitionDefaults?: PermissionDefinitionDefaults; limitOwnReduce?: TlimitOwnReduce&lt;TUserId, any&gt;; } /** The main class - see [Basic Usage](/additional-documentation/basic-usage.html) */ export class Permissions&lt;TUserId extends Tid &#x3D; number, TResourceId extends Tid &#x3D; number&gt; { private _permissionDefinitionsInternal: PermissionDefinitionInternal[] &#x3D; []; private _accessControl: AccessControl; private _acre: AccessControlRe; private _rolesNotFound &#x3D; {}; private roles: string[]; private _limitOwnReduce: TlimitOwnReduce&lt;TUserId, any&gt;; private _isBuilt &#x3D; false; constructor({ permissionDefinitions, permissionDefinitionDefaults, limitOwnReduce, }: IPermissionsOptions&lt;TUserId, TResourceId&gt; &#x3D; {}) { this._limitOwnReduce &#x3D; limitOwnReduce; this.addDefinitions(permissionDefinitions || [], permissionDefinitionDefaults); } public addDefinitions( permissionDefinitions: | PermissionDefinition&lt;TUserId, TResourceId&gt; | PermissionDefinition&lt;TUserId, TResourceId&gt;[], permissionDefinitionDefaults: PermissionDefinitionDefaults &#x3D; {} ) { this.ensureHasNotBuild(); if (!permissionDefinitions) throw new Error( &#x60;SA-Permissions: in addDefinitions(), invalid permissionDefinitions: ${stringify( permissionDefinitions )}&#x60; ); if (!_.isArray(permissionDefinitions)) permissionDefinitions &#x3D; [permissionDefinitions]; const ipdsToAdd &#x3D; permissionDefinitions.map( projectPDWithDefaultsToInternal(permissionDefinitionDefaults) ); // sanity checks before adding ipds _.each(ipdsToAdd, (ipdToAdd, ipdToAddIdx): any &#x3D;&gt; { // if we are trying to redefine a role+resource+action:possession // with DIFFERENT attributes (i.e non-strict) throw as its very dangerous! const nonStrictDuplicatePds &#x3D; this.filterPDsWithDuplicateGrantActions(ipdToAdd); if (!_.isEmpty(nonStrictDuplicatePds)) { const firstConflictingAction &#x3D; _.findKey( ipdToAdd.grant, (attributes, action) &#x3D;&gt; !!nonStrictDuplicatePds[0].grant[action] ); throw new Error( &#x60;SA-Permissions: InvalidPermissionDefinitionError: addDefinitions() redefining action error. Action: &quot;${firstConflictingAction}&quot; Action Attributes: ${stringify(ipdToAdd.grant[firstConflictingAction])} While adding PD: ${stringify(ipdToAdd)} Conflicted with PD: ${stringify(nonStrictDuplicatePds[0])}&#x60; ); } // if we are trying to redefine a role+resource+action:possession // even with SAME different attributes (i.e very strict) warn as obsolete! const strictDuplicatePds &#x3D; this.filterPDsWithDuplicateGrantActions(ipdToAdd, true); if (!_.isEmpty(strictDuplicatePds)) { const firstConflictingAction &#x3D; _.findKey( ipdToAdd.grant, (attributes, action) &#x3D;&gt; !!strictDuplicatePds[0].grant[action] ); getLogger().warn( &#x60;addDefinitions() redefining action in a PD with same attributes is obsolete:&#x60;, { action: firstConflictingAction, attributes: ipdToAdd.grant[firstConflictingAction], permissionDefinition: ipdToAdd, } ); } if (hasSomeOwnGrant(ipdToAdd)) { const isOwnerFound &#x3D; !!ipdToAdd.isOwner; let listOwnedFound &#x3D; !!ipdToAdd.listOwned; let limitOwnedFound &#x3D; !!ipdToAdd.limitOwned; // on this PD if (listOwnedFound &amp;&amp; limitOwnedFound) throw new Error( &#x60;SA-Permissions: in addDefinitions() found BOTH &quot;listOwned&quot; &amp; &quot;limitOwned&quot; callbacks in the added PermissionDefinition. Use one or the other, but not both. PermissionDefinition &#x3D; ${JSON.stringify( permissionDefinitions[ipdToAddIdx], null, 2 )}&#x60; ); // It has some OWN Grant, but no owner hooks found, throw if (!isOwnerFound || (!listOwnedFound &amp;&amp; !limitOwnedFound)) { throw new Error( &#x60;SA-Permissions: in addDefinitions() PermissionDefinition has &#x27;own&#x27; action but no ${ !isOwnerFound ? &#x27;&quot;isOwner&quot;&#x27; : &#x27;&quot;listOwned&quot; nor &quot;limitOwned&quot;&#x27; } callbacks are there. PermissionDefinition &#x3D; ${stringify(ipdToAdd)} &#x60; ); } // check all for same resource as ipdToAdd let conflictedPD; for (const opd of this._permissionDefinitionsInternal) { if (ipdToAdd.resource &#x3D;&#x3D;&#x3D; opd.resource) { listOwnedFound &#x3D; listOwnedFound || !!opd.listOwned; limitOwnedFound &#x3D; limitOwnedFound || !!opd.limitOwned; } if (listOwnedFound &amp;&amp; limitOwnedFound) { conflictedPD &#x3D; opd; break; } } if (listOwnedFound &amp;&amp; limitOwnedFound) throw new Error( &#x60;SA-Permissions: in addDefinitions() found BOTH &quot;listOwned&quot; &amp; &quot;limitOwned&quot; callbacks in some PermissionDefinition for resource &quot;${ ipdToAdd.resource }&quot;. Use one or the other, but not both. Adding PD: ${stringify(permissionDefinitions[ipdToAddIdx])} Conflicted with PD: ${stringify(conflictedPD)}&#x60; ); } this._permissionDefinitionsInternal.push(ipdToAdd); // all ok, add it! }); } /** * Check is this Permissions instance has been built (so no more .addDefinitions() allowed) */ public get isBuilt(): boolean { return this._isBuilt; } public build() { this._isBuilt &#x3D; true; if (this._acre) return this; [this._accessControl, this._acre] &#x3D; buildAccessControl(this._permissionDefinitionsInternal); this.roles &#x3D; this.getRoles(); return this; } /** The &#x60;grantPermit()&#x60; is the way to *query* the Permissions instance for granting permissions to a User. The method responds with an instance of [Permit](/classes/Permit.html) that holds all known information about the queried **user**, **resource** and **action**. In short, the question is &quot;can some of &#x60;user.roles&#x60; perform &#x60;action&#x60; either a) on **any** &#x60;resource&#x60; or b) on an **own** &#x60;resource&#x60; (AND the specific &#x60;resourceId&#x60; if passed)? We are checking all roles for both **any** &amp; **own**, while collecting all &#x60;isOwner&#x60; &amp; &#x60;listOwned&#x60; and feed all known information into a **Permit** object. @return Promise&lt;Permit&gt; a Promise of a [Permit](/classes/Permit.html) instance. */ public async grantPermit({ // &lt;TUserId extends Tid &#x3D; number, TResourceId extends Tid &#x3D; number&gt; user, action, resource, resourceId, }: GrantPermitQuery&lt;TUserId, TResourceId&gt;): Promise&lt;Permit&lt;TUserId, TResourceId&gt;&gt; { this.ensureHasBuild(); if (!isValidIUser(user)) throw new Error( &#x27;SA-Permissions: at grantPermit(), user is not a valid &#x60;interface IUser {id: TId; roles: string[];}&#x60;&#x27; ); if (!this.getResources().includes(resource)) throw new Error(&#x60;SA-Permissions: at grantPermit(), Invalid resource: &quot;${resource}&quot;&#x60;); if (action.split(&#x27;:&#x27;).length &gt; 1) throw new Error( &#x60;SA-Permissions: at grantPermit(), Invalid action structure: &quot;${action}&quot;. The colon &quot;:&quot; in the action is not allowed on grantPermit() and you must NOT specify &quot;:own&quot; or &quot;:any&quot; after the action at it. SA-Permissions always returns a Permit that checks for both any &amp; own.&#x60; ); let acPermission: Permission; // &#x3D; { granted: false } as any; let anyAcPermission: Permission; let ownAcPermission: Permission; // The &#x60;Permit&#x60; values const isOwners: TisOwner&lt;TUserId, TResourceId&gt;[] &#x3D; []; const listOwneds: TlistOwned&lt;TUserId, TResourceId&gt;[] &#x3D; []; const limitOwneds: TlimitOwned&lt;any, TUserId&gt;[] &#x3D; []; // 2 passes: check all EPossession against all roles. // if any permissions.granted is true, granted is true // but continue to gather all permissions.attributes, isOwner &amp; listOwned for (const queryPossession of [EPossession.any, EPossession.own]) { getLogger().debug(&#x27;grantPermit: possession&#x27;, { possession: queryPossession }); const unknownRoles &#x3D; _.difference(user.roles, this.roles); const roles &#x3D; _.without(user.roles, ...unknownRoles); _.each(unknownRoles, (rl) &#x3D;&gt; { if (!this._rolesNotFound[rl]) { this._rolesNotFound[rl] &#x3D; true; getLogger().warn( &#x60;SA-Permissions(): at grantPermit(), role not found: ${rl} (will not warn again about this role)&#x60; ); } }); const queryInfo: IQueryInfo &#x3D; { role: roles, action: &#x60;${action}:${queryPossession}&#x60;, resource, }; try { acPermission &#x3D; this._acre.permission(queryInfo); } catch (error) { // @todo: handle throw error; } getLogger().debug(&#x27;grantPermit: this._accessControl.permission(queryInfo)&#x27;, { queryInfo, &#x27;permission.granted&#x27;: acPermission.granted, &#x27;permission.attributes&#x27;: acPermission.attributes, }); switch (queryPossession) { case EPossession.any: { anyAcPermission &#x3D; acPermission; break; } case EPossession.own: { ownAcPermission &#x3D; acPermission; if (ownAcPermission.granted) { const matchingCpds &#x3D; _.filter(this._permissionDefinitionsInternal, (pd) &#x3D;&gt; { return ( _.some(pd.roles, (pdRole) &#x3D;&gt; user.roles.includes(pdRole)) &amp;&amp; (resource &#x3D;&#x3D;&#x3D; pd.resource || pd.resource &#x3D;&#x3D;&#x3D; &#x27;*&#x27;) &amp;&amp; (!!(pd?.grant || {})[&#x60;${action}:${EPossession.own}&#x60;] || !!(pd?.grant || {})[&#x60;*:${EPossession.own}&#x60;] || !!(pd?.grant || {})[&#x60;${action}:${EPossession.any}&#x60;] || !!(pd?.grant || {})[&#x60;*:${EPossession.any}&#x60;]) ); }); // prettier-ignore if (!anyAcPermission.granted &amp;&amp; _.isEmpty(matchingCpds)) throw new Error( &#x60;SA-Permissions: own access granted but no matching PermissionDefinitions found: &#x60; + &#x60;${stringify({ user, action, resource })}&#x60;, ); _.each(matchingCpds, (cpd) &#x3D;&gt; { const { isOwner, listOwned, limitOwned } &#x3D; cpd; if (isOwner) isOwners.push(isOwner as any); if (listOwned) listOwneds.push(listOwned as any); if (limitOwned) limitOwneds.push(limitOwned as any); }); } break; } default: throw new Error( &#x60;SA-Permissions::grantPermit: invalid EPossession in queryPossession &quot;${queryPossession}&quot;&#x60; ); } } const permit &#x3D; new (Permit as any)( // constructor is best kept private, only we should use it! user, action, resource, resourceId, anyAcPermission, ownAcPermission, _.uniq(isOwners), _.uniq(listOwneds), _.uniq(limitOwneds), this._limitOwnReduce ); // prettier-ignore if (!anyAcPermission.granted &amp;&amp; ownAcPermission.granted) { // The following checks SHOULD NOT be needed, they should be caught at the addDefinitions() call. Please report to authors if you encounter them. const createError &#x3D; (butDetail: string) &#x3D;&gt; new Error(&#x60;SA-Permissions: grantPermit() &quot;OWN&quot; access granted but ${butDetail }. The error should have been caught at addDefinitions() call, please report to authors. GrantPermitQuery &#x3D; ${ stringify({ user, action, resource, resourceId })}&#x60;); if (_.isEmpty(isOwners)) throw createError(&#x27;no &quot;isOwner&quot; ownership hook found&#x27;); if (_.isEmpty(listOwneds) &amp;&amp; _.isEmpty(limitOwneds)) throw createError(&#x27;no &quot;listOwned&quot; nor &quot;limitOwned&quot; ownership hooks found&#x27;); if (!_.isEmpty(listOwneds) &amp;&amp; !_.isEmpty(limitOwneds)) throw createError(&#x27;found BOTH &quot;listOwned&quot; &amp; &quot;limitOwned&quot; ownership hooks. Use one or the other, but not both&#x27;); if (resourceId) (permit as any).resourceIdOwnPermissionGranted &#x3D; await permit.isOwn(resourceId); } return permit as Permit&lt;TUserId, TResourceId&gt;; } // some helpers public getRoles(): string[] { this.ensureHasBuild(); return this._acre.getRoles(); } public getResources(): string[] { this.ensureHasBuild(); return this._acre.getResources(); } public getActions(): string[] { this.ensureHasBuild(); return this._acre.getActions(); } /** * Returns a deep clone of [&#x60;AccessControl#getGrants()&#x60;](https://onury.io/accesscontrol/?api&#x3D;ac#AccessControl#getGrants) (which according to its docs &#x60;Gets the internal grants object that stores all current grants.&#x60;), but omitting empty arrays eg &#x60;&#x27;rollover:any&#x27;: []&#x60;. * * @see https://onury.io/accesscontrol/?api&#x3D;ac#AccessControl#getGrants */ public getGrants(): object { // @todo: typings this.ensureHasBuild(); // delete empty arrays, eg &#x60;&#x27;rollover:any&#x27;: []&#x60; cause they are useless &amp; break our &#x60;compare()&#x60; return deleteEmptyArrayKeys(_.cloneDeep(this._accessControl.getGrants())); } public compare(permissions1: Permissions&lt;any, any&gt;, permissions2: Permissions&lt;any, any&gt; &#x3D; this) { return diff(permissions1.getGrants(), permissions2.getGrants()); } // Grab accessControl.getGrants(), BUT delete all empty / denied grants that /** Returns a list of the &#x60;PermissionDefinition&#x60; objects stored in this instance, with optional filtering &amp; consolidations removing duplicates and redundant grants (**WARNING**: this is experimental) @param filter allows you to filter PDs: * Use an object eg &#x60;{ resource: &#x27;document&#x27; }&#x60; as the &#x60;_.matches&#x60; iteratee shorthand. If this &#x60;_.matches&#x60; object is used, the props used for filtering are considered &quot;default&quot; and are omitted from each PD. * OR use a function returning boolean for each PD, eg (pd) &#x3D;&gt; pd.resource &#x3D;&#x3D;&#x3D; &#x27;document&#x27; See https://lodash.com/docs/4.17.11#filter @param consolidateFlag is **experimental**, it tries to consolidate PermissionDefinitions, remove duplicates and merge compatible ones */ public getDefinitions( filter?: { [key: string]: any; }, consolidateFlag: boolean | &#x27;force&#x27; &#x3D; false ): Partial&lt;PermissionDefinitionInternal&gt;[] { const filteredPDs &#x3D; _f.flow( _f.filter(filter), _f.reject((opd) &#x3D;&gt; _.isEmpty(opd.grant)) )(this._permissionDefinitionsInternal); const resultPDs &#x3D; consolidateFlag ? consolidatePermissionDefinitions( filter, consolidateFlag )(_.cloneDeep(this._permissionDefinitionsInternal)) : filteredPDs; const resultedSaPermissions &#x3D; new Permissions({ permissionDefinitions: resultPDs, permissionDefinitionDefaults: filter, }).build(); const filteredInstancePermissions &#x3D; new Permissions({ permissionDefinitions: filteredPDs as any, permissionDefinitionDefaults: filter, }).build(); const difference &#x3D; this.compare(resultedSaPermissions, filteredInstancePermissions); if (difference !&#x3D;&#x3D; undefined) { throw new Error( &#x60;SA-Permissions: getDefinitions diff: ${stringify(difference)} Existing grants: ${stringify(this.getGrants())} Generated grants: ${stringify(resultedSaPermissions.getGrants())} &#x60; ); } return resultPDs; } private ensureHasBuild() { if (!this._acre) throw new Error( &#x60;SA-Permissions InvalidInvocation: calling permissions methods before having build()&#x60; ); } private ensureHasNotBuild() { if (this._acre) throw new Error( &#x60;SA-Permissions InvalidInvocation: calling addDefinitions() after having build()&#x60; )