@spinajs/rbac-http-admin 2.0.485 → 2.0.487
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -5
- package/lib/cjs/config/rbac-http-admin.d.ts +25 -0
- package/lib/cjs/config/rbac-http-admin.d.ts.map +1 -1
- package/lib/cjs/config/rbac-http-admin.js +25 -0
- package/lib/cjs/config/rbac-http-admin.js.map +1 -1
- package/lib/cjs/controllers/Users/Profile.d.ts.map +1 -1
- package/lib/cjs/controllers/Users/Profile.js +3 -1
- package/lib/cjs/controllers/Users/Profile.js.map +1 -1
- package/lib/cjs/controllers/Users/Roles.d.ts +15 -8
- package/lib/cjs/controllers/Users/Roles.d.ts.map +1 -1
- package/lib/cjs/controllers/Users/Roles.js +34 -16
- package/lib/cjs/controllers/Users/Roles.js.map +1 -1
- package/lib/cjs/controllers/Users/Security.d.ts +151 -11
- package/lib/cjs/controllers/Users/Security.d.ts.map +1 -1
- package/lib/cjs/controllers/Users/Security.js +303 -30
- package/lib/cjs/controllers/Users/Security.js.map +1 -1
- package/lib/cjs/controllers/Users/Users.d.ts +97 -14
- package/lib/cjs/controllers/Users/Users.d.ts.map +1 -1
- package/lib/cjs/controllers/Users/Users.js +278 -73
- package/lib/cjs/controllers/Users/Users.js.map +1 -1
- package/lib/cjs/index.d.ts +6 -1
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +5 -0
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/interfaces.d.ts +81 -0
- package/lib/cjs/interfaces.d.ts.map +1 -0
- package/lib/cjs/interfaces.js +20 -0
- package/lib/cjs/interfaces.js.map +1 -0
- package/lib/cjs/services/RoleGuard.d.ts +96 -0
- package/lib/cjs/services/RoleGuard.d.ts.map +1 -0
- package/lib/cjs/services/RoleGuard.js +258 -0
- package/lib/cjs/services/RoleGuard.js.map +1 -0
- package/lib/mjs/config/rbac-http-admin.d.ts +25 -0
- package/lib/mjs/config/rbac-http-admin.d.ts.map +1 -1
- package/lib/mjs/config/rbac-http-admin.js +25 -0
- package/lib/mjs/config/rbac-http-admin.js.map +1 -1
- package/lib/mjs/controllers/Users/Profile.d.ts.map +1 -1
- package/lib/mjs/controllers/Users/Profile.js +4 -2
- package/lib/mjs/controllers/Users/Profile.js.map +1 -1
- package/lib/mjs/controllers/Users/Roles.d.ts +15 -8
- package/lib/mjs/controllers/Users/Roles.d.ts.map +1 -1
- package/lib/mjs/controllers/Users/Roles.js +35 -17
- package/lib/mjs/controllers/Users/Roles.js.map +1 -1
- package/lib/mjs/controllers/Users/Security.d.ts +151 -11
- package/lib/mjs/controllers/Users/Security.d.ts.map +1 -1
- package/lib/mjs/controllers/Users/Security.js +304 -31
- package/lib/mjs/controllers/Users/Security.js.map +1 -1
- package/lib/mjs/controllers/Users/Users.d.ts +97 -14
- package/lib/mjs/controllers/Users/Users.d.ts.map +1 -1
- package/lib/mjs/controllers/Users/Users.js +279 -74
- package/lib/mjs/controllers/Users/Users.js.map +1 -1
- package/lib/mjs/index.d.ts +6 -1
- package/lib/mjs/index.d.ts.map +1 -1
- package/lib/mjs/index.js +6 -1
- package/lib/mjs/index.js.map +1 -1
- package/lib/mjs/interfaces.d.ts +81 -0
- package/lib/mjs/interfaces.d.ts.map +1 -0
- package/lib/mjs/interfaces.js +16 -0
- package/lib/mjs/interfaces.js.map +1 -0
- package/lib/mjs/services/RoleGuard.d.ts +96 -0
- package/lib/mjs/services/RoleGuard.d.ts.map +1 -0
- package/lib/mjs/services/RoleGuard.js +252 -0
- package/lib/mjs/services/RoleGuard.js.map +1 -0
- package/lib/tsconfig.cjs.tsbuildinfo +1 -1
- package/lib/tsconfig.mjs.tsbuildinfo +1 -1
- package/package.json +14 -12
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../../src/interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAEhB,+EAA+E;IAC/E,gBAAgB,EAAE,OAAO,CAAC;IAE1B,gFAAgF;IAChF,iBAAiB,EAAE,OAAO,CAAC;IAE3B;;;;OAIG;IACH,iBAAiB,EAAE,OAAO,CAAC;IAE3B;;;OAGG;IACH,kBAAkB,EAAE,OAAO,CAAC;IAE5B;;;OAGG;IACH,4BAA4B,EAAE,OAAO,CAAC;IAEtC;;;;OAIG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,8BAAsB,SAAS;IAC7B;;;;;;OAMG;aACa,oBAAoB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAEtG;;OAEG;aACa,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAE3F;;OAEG;aACa,uBAAuB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAE/G;;;;OAIG;aACa,eAAe,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,EAAE;CACvD;AAED,uDAAuD;AACvD,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,QAAQ,GAAG,KAAK,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role and account-state guard for the admin API.
|
|
3
|
+
*
|
|
4
|
+
* The checks live here rather than in the controllers because the same rule has
|
|
5
|
+
* to hold on every path that can change what an account may do: granting a role,
|
|
6
|
+
* revoking one, rewriting the whole role list through `PATCH /users/:uuid`, and
|
|
7
|
+
* taking an account out of service. A rule implemented per-controller is a rule
|
|
8
|
+
* that will be missing from the next controller.
|
|
9
|
+
*
|
|
10
|
+
* All methods THROW on refusal ( `Forbidden` / `InvalidArgument`, which the http
|
|
11
|
+
* error map turns into 403 / 400 ) and return normally when the operation is
|
|
12
|
+
* allowed.
|
|
13
|
+
*/
|
|
14
|
+
export class RoleGuard {
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=interfaces.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../../src/interfaces.ts"],"names":[],"mappings":"AAiDA;;;;;;;;;;;;GAYG;AACH,MAAM,OAAgB,SAAS;CA0B9B"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Log } from '@spinajs/log';
|
|
2
|
+
import { AccessControl, User } from '@spinajs/rbac';
|
|
3
|
+
import { AccountDisableAction, IRoleGuardConfig, RoleGuard } from '../interfaces.js';
|
|
4
|
+
/** A resolved grant map: resource -> action -> attribute list. */
|
|
5
|
+
type GrantMap = {
|
|
6
|
+
[resource: string]: {
|
|
7
|
+
[action: string]: string[];
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* The shipped {@link RoleGuard}.
|
|
12
|
+
*
|
|
13
|
+
* Every check reads its switch from `rbac.admin.roleGuard`, so an application
|
|
14
|
+
* turns one off in configuration instead of replacing the whole service — and
|
|
15
|
+
* an application that needs a different rule entirely registers its own class
|
|
16
|
+
* under {@link RoleGuard} and names it in `rbac.admin.roleGuard.service`.
|
|
17
|
+
*/
|
|
18
|
+
export declare class DefaultRoleGuard extends RoleGuard {
|
|
19
|
+
protected Log: Log;
|
|
20
|
+
protected Options: IRoleGuardConfig;
|
|
21
|
+
protected SystemRole: string;
|
|
22
|
+
/**
|
|
23
|
+
* Declared roles, which is NOT the same set as the roles accesscontrol knows
|
|
24
|
+
* about: a role may be declared for assignment and carry no grants yet.
|
|
25
|
+
*/
|
|
26
|
+
protected DeclaredRoles: Array<{
|
|
27
|
+
Name: string;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Resolved late, never cached in a field: the container entry is replaced
|
|
31
|
+
* whenever the rbac bootstrapper runs ( every test case does ), and a guard
|
|
32
|
+
* holding the stale instance would answer from grants nobody configured.
|
|
33
|
+
*/
|
|
34
|
+
protected get AC(): AccessControl;
|
|
35
|
+
assertCanAssignRoles(actor: User, target: User | null, roles: string[]): Promise<void>;
|
|
36
|
+
assertCanRevokeRole(actor: User, target: User, role: string): Promise<void>;
|
|
37
|
+
assertCanDisableAccount(actor: User, target: User, action: AccountDisableAction): Promise<void>;
|
|
38
|
+
assignableRoles(actor: User): string[];
|
|
39
|
+
/**
|
|
40
|
+
* A missing actor is a programming error on a route that must be behind
|
|
41
|
+
* `AuthorizedPolicy` — refused rather than waved through, because every
|
|
42
|
+
* escalation check below is meaningless without one.
|
|
43
|
+
*/
|
|
44
|
+
protected assertActor(actor: User): void;
|
|
45
|
+
protected assertKnownRole(role: string): void;
|
|
46
|
+
protected assertNotSystemRole(role: string, verb: string): void;
|
|
47
|
+
protected isSelf(actor: User, target: User): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Throws when `target` is the last account that can still act with `role`.
|
|
50
|
+
*
|
|
51
|
+
* Counts accounts that could actually log in — inactive and soft-deleted rows
|
|
52
|
+
* are not a way back into the installation.
|
|
53
|
+
*/
|
|
54
|
+
protected assertNotLastHolder(role: string, action: string): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Active accounts holding `role`.
|
|
57
|
+
*
|
|
58
|
+
* Counted in the database. This used to narrow with LIKE and finish the job in
|
|
59
|
+
* memory, because `withRole` compiled to `FIND_IN_SET` — MySQL only — and threw
|
|
60
|
+
* a driver error on SQLite, turning "is this the last administrator" into a 500
|
|
61
|
+
* on every deployment that is not MySQL. Set membership is a per-dialect
|
|
62
|
+
* statement now, so the scope answers everywhere and the workaround is gone.
|
|
63
|
+
*/
|
|
64
|
+
protected countActiveHolders(role: string): Promise<number>;
|
|
65
|
+
protected isPrivileged(role: string): boolean;
|
|
66
|
+
protected rolesWithGrants(): string[];
|
|
67
|
+
protected allGrants(): GrantMap;
|
|
68
|
+
/** Grants of one role, with its `$extend` chain resolved. */
|
|
69
|
+
protected grantsOfRole(role: string): GrantMap;
|
|
70
|
+
/**
|
|
71
|
+
* The caller's ceiling: the union of every role they hold.
|
|
72
|
+
*
|
|
73
|
+
* Deliberately the union and not the session's active role — an administrator
|
|
74
|
+
* who can switch to a stronger role could otherwise be blocked from an
|
|
75
|
+
* operation they can perform by switching first, which teaches people to
|
|
76
|
+
* switch rather than to stay in the weaker role.
|
|
77
|
+
*/
|
|
78
|
+
protected grantsOf(actor: User): GrantMap;
|
|
79
|
+
/**
|
|
80
|
+
* True when `holder` permits everything `wanted` does.
|
|
81
|
+
*
|
|
82
|
+
* Attributes are compared as sets, with `*` covering anything. Negated
|
|
83
|
+
* attributes ( `!Password` ) are treated as ordinary members: a holder that
|
|
84
|
+
* lists the same negation still covers, one that does not is reported as not
|
|
85
|
+
* covering. That errs towards refusing an assignment, which is the safe
|
|
86
|
+
* direction for a guard.
|
|
87
|
+
*
|
|
88
|
+
* `:any` covers the matching `:own`, exactly as accesscontrol resolves them at
|
|
89
|
+
* enforcement time. Without that an administrator holding `read:any` on users
|
|
90
|
+
* would be told they cannot hand out a role holding `read:own` — a role
|
|
91
|
+
* strictly weaker than their own.
|
|
92
|
+
*/
|
|
93
|
+
protected covers(holder: GrantMap, wanted: GrantMap): boolean;
|
|
94
|
+
}
|
|
95
|
+
export {};
|
|
96
|
+
//# sourceMappingURL=RoleGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RoleGuard.d.ts","sourceRoot":"","sources":["../../../src/services/RoleGuard.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAU,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,aAAa,EAAE,IAAI,EAAiC,MAAM,eAAe,CAAC;AAGnF,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAErF,kEAAkE;AAClE,KAAK,QAAQ,GAAG;IAAE,CAAC,QAAQ,EAAE,MAAM,GAAG;QAAE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;KAAE,CAAA;CAAE,CAAC;AAEvE;;;;;;;GAOG;AACH,qBACa,gBAAiB,SAAQ,SAAS;IAE7C,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;IAGnB,SAAS,CAAC,OAAO,EAAE,gBAAgB,CAAC;IAGpC,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IAEH,SAAS,CAAC,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEjD;;;;OAIG;IACH,SAAS,KAAK,EAAE,IAAI,aAAa,CAEhC;IAEY,oBAAoB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BtF,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2B3E,uBAAuB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBrG,eAAe,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,EAAE;IAa7C;;;;OAIG;IACH,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI;IAMxC,SAAS,CAAC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAY7C,SAAS,CAAC,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAM/D,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAG,OAAO;IAUpD;;;;;OAKG;cACa,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUhF;;;;;;;;OAQG;cACa,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIjE,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAO7C,SAAS,CAAC,eAAe,IAAI,MAAM,EAAE;IASrC,SAAS,CAAC,SAAS,IAAI,QAAQ;IAQ/B,6DAA6D;IAC7D,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ;IAI9C;;;;;;;OAOG;IACH,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,GAAG,QAAQ;IAKzC;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO;CA2B9D"}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Injectable, DI } from '@spinajs/di';
|
|
11
|
+
import { Config } from '@spinajs/configuration';
|
|
12
|
+
import { Log, Logger } from '@spinajs/log';
|
|
13
|
+
import { Forbidden, InvalidArgument } from '@spinajs/exceptions';
|
|
14
|
+
import { User, _combineGrants, _unwindGrants } from '@spinajs/rbac';
|
|
15
|
+
import _ from 'lodash';
|
|
16
|
+
import { RoleGuard } from '../interfaces.js';
|
|
17
|
+
/**
|
|
18
|
+
* The shipped {@link RoleGuard}.
|
|
19
|
+
*
|
|
20
|
+
* Every check reads its switch from `rbac.admin.roleGuard`, so an application
|
|
21
|
+
* turns one off in configuration instead of replacing the whole service — and
|
|
22
|
+
* an application that needs a different rule entirely registers its own class
|
|
23
|
+
* under {@link RoleGuard} and names it in `rbac.admin.roleGuard.service`.
|
|
24
|
+
*/
|
|
25
|
+
let DefaultRoleGuard = class DefaultRoleGuard extends RoleGuard {
|
|
26
|
+
/**
|
|
27
|
+
* Resolved late, never cached in a field: the container entry is replaced
|
|
28
|
+
* whenever the rbac bootstrapper runs ( every test case does ), and a guard
|
|
29
|
+
* holding the stale instance would answer from grants nobody configured.
|
|
30
|
+
*/
|
|
31
|
+
get AC() {
|
|
32
|
+
return DI.get('AccessControl');
|
|
33
|
+
}
|
|
34
|
+
async assertCanAssignRoles(actor, target, roles) {
|
|
35
|
+
const wanted = _.uniq((roles ?? []).map((r) => String(r ?? '').trim()).filter((r) => r.length > 0));
|
|
36
|
+
if (wanted.length === 0) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.assertActor(actor);
|
|
40
|
+
for (const role of wanted) {
|
|
41
|
+
this.assertKnownRole(role);
|
|
42
|
+
this.assertNotSystemRole(role, 'assigned');
|
|
43
|
+
if (this.Options?.preventEscalation !== false && !this.covers(this.grantsOf(actor), this.grantsOfRole(role))) {
|
|
44
|
+
throw new Forbidden(`Role '${role}' grants more than the caller holds and cannot be assigned`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// The guard passed — the assignment itself is the caller's next step. Logged
|
|
48
|
+
// with the actor because the rbac events carry the TARGET only, so this line
|
|
49
|
+
// is the one record of who handed out which role.
|
|
50
|
+
this.Log.info(`Role assignment allowed`, {
|
|
51
|
+
Actor: actor?.Uuid,
|
|
52
|
+
Target: target?.Uuid ?? null,
|
|
53
|
+
Roles: wanted,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async assertCanRevokeRole(actor, target, role) {
|
|
57
|
+
const wanted = String(role ?? '').trim();
|
|
58
|
+
if (wanted.length === 0) {
|
|
59
|
+
throw new InvalidArgument('role cannot be empty');
|
|
60
|
+
}
|
|
61
|
+
this.assertActor(actor);
|
|
62
|
+
this.assertKnownRole(wanted);
|
|
63
|
+
this.assertNotSystemRole(wanted, 'revoked');
|
|
64
|
+
if (!this.isPrivileged(wanted)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Taking a privileged role off YOUR OWN account through the admin API is
|
|
68
|
+
// almost always a mistake and is never recoverable through the same API —
|
|
69
|
+
// the request that would put the role back needs the role.
|
|
70
|
+
if (this.Options?.preventSelfLockout !== false && this.isSelf(actor, target)) {
|
|
71
|
+
throw new Forbidden(`Cannot revoke your own '${wanted}' role`);
|
|
72
|
+
}
|
|
73
|
+
if (this.Options?.preventLastPrivilegedRemoval !== false && target?.Role?.includes(wanted)) {
|
|
74
|
+
await this.assertNotLastHolder(wanted, `revoke '${wanted}'`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async assertCanDisableAccount(actor, target, action) {
|
|
78
|
+
this.assertActor(actor);
|
|
79
|
+
if (this.Options?.preventSelfLockout !== false && this.isSelf(actor, target)) {
|
|
80
|
+
throw new Forbidden(`Cannot ${action} your own account`);
|
|
81
|
+
}
|
|
82
|
+
if (this.Options?.preventLastPrivilegedRemoval === false) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
for (const role of target?.Role ?? []) {
|
|
86
|
+
if (this.isPrivileged(role)) {
|
|
87
|
+
await this.assertNotLastHolder(role, action);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
assignableRoles(actor) {
|
|
92
|
+
const all = _.uniq([...(this.DeclaredRoles ?? []).map((r) => r?.Name).filter(Boolean), ...this.rolesWithGrants()]);
|
|
93
|
+
const withoutSystem = this.Options?.protectSystemRole === false ? all : all.filter((r) => r !== this.SystemRole);
|
|
94
|
+
if (this.Options?.preventEscalation === false) {
|
|
95
|
+
return withoutSystem;
|
|
96
|
+
}
|
|
97
|
+
const actorGrants = this.grantsOf(actor);
|
|
98
|
+
return withoutSystem.filter((r) => this.covers(actorGrants, this.grantsOfRole(r)));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* A missing actor is a programming error on a route that must be behind
|
|
102
|
+
* `AuthorizedPolicy` — refused rather than waved through, because every
|
|
103
|
+
* escalation check below is meaningless without one.
|
|
104
|
+
*/
|
|
105
|
+
assertActor(actor) {
|
|
106
|
+
if (!actor) {
|
|
107
|
+
throw new Forbidden('Role changes require an authenticated caller');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
assertKnownRole(role) {
|
|
111
|
+
if (this.Options?.requireKnownRole === false) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const known = _.uniq([...(this.DeclaredRoles ?? []).map((r) => r?.Name).filter(Boolean), ...this.rolesWithGrants(), this.SystemRole]);
|
|
115
|
+
if (!known.includes(role)) {
|
|
116
|
+
throw new InvalidArgument(`Role '${role}' is not declared in rbac configuration`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
assertNotSystemRole(role, verb) {
|
|
120
|
+
if (this.Options?.protectSystemRole !== false && role === this.SystemRole) {
|
|
121
|
+
throw new Forbidden(`The system role cannot be ${verb} through the API`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
isSelf(actor, target) {
|
|
125
|
+
if (!actor || !target) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
// Uuid first: an actor resolved from a session and a target loaded by route
|
|
129
|
+
// parameter are two different model instances of the same row.
|
|
130
|
+
return (actor.Uuid && actor.Uuid === target.Uuid) || (!!actor.Id && actor.Id === target.Id);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Throws when `target` is the last account that can still act with `role`.
|
|
134
|
+
*
|
|
135
|
+
* Counts accounts that could actually log in — inactive and soft-deleted rows
|
|
136
|
+
* are not a way back into the installation.
|
|
137
|
+
*/
|
|
138
|
+
async assertNotLastHolder(role, action) {
|
|
139
|
+
const holders = await this.countActiveHolders(role);
|
|
140
|
+
// The target itself is one of them ( it is active and holds the role ), so
|
|
141
|
+
// one holder means the operation empties the role.
|
|
142
|
+
if (holders <= 1) {
|
|
143
|
+
throw new Forbidden(`Cannot ${action}: it would leave no active account holding '${role}'`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Active accounts holding `role`.
|
|
148
|
+
*
|
|
149
|
+
* Counted in the database. This used to narrow with LIKE and finish the job in
|
|
150
|
+
* memory, because `withRole` compiled to `FIND_IN_SET` — MySQL only — and threw
|
|
151
|
+
* a driver error on SQLite, turning "is this the last administrator" into a 500
|
|
152
|
+
* on every deployment that is not MySQL. Set membership is a per-dialect
|
|
153
|
+
* statement now, so the scope answers everywhere and the workaround is gone.
|
|
154
|
+
*/
|
|
155
|
+
async countActiveHolders(role) {
|
|
156
|
+
return User.query().isActiveUser().withRole([role]).selectCount();
|
|
157
|
+
}
|
|
158
|
+
isPrivileged(role) {
|
|
159
|
+
const resource = this.Options?.privilegedResource ?? 'users';
|
|
160
|
+
const action = this.Options?.privilegedAction ?? 'update:any';
|
|
161
|
+
return Boolean(this.grantsOfRole(role)?.[resource]?.[action]);
|
|
162
|
+
}
|
|
163
|
+
rolesWithGrants() {
|
|
164
|
+
try {
|
|
165
|
+
return this.AC?.getRoles() ?? [];
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// accesscontrol throws when no grants were ever set
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
allGrants() {
|
|
173
|
+
try {
|
|
174
|
+
return (this.AC?.getGrants() ?? {});
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return {};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/** Grants of one role, with its `$extend` chain resolved. */
|
|
181
|
+
grantsOfRole(role) {
|
|
182
|
+
return _unwindGrants(role, this.allGrants());
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The caller's ceiling: the union of every role they hold.
|
|
186
|
+
*
|
|
187
|
+
* Deliberately the union and not the session's active role — an administrator
|
|
188
|
+
* who can switch to a stronger role could otherwise be blocked from an
|
|
189
|
+
* operation they can perform by switching first, which teaches people to
|
|
190
|
+
* switch rather than to stay in the weaker role.
|
|
191
|
+
*/
|
|
192
|
+
grantsOf(actor) {
|
|
193
|
+
const grants = this.allGrants();
|
|
194
|
+
return _combineGrants(...(actor?.Role ?? []).map((r) => _unwindGrants(r, grants)));
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* True when `holder` permits everything `wanted` does.
|
|
198
|
+
*
|
|
199
|
+
* Attributes are compared as sets, with `*` covering anything. Negated
|
|
200
|
+
* attributes ( `!Password` ) are treated as ordinary members: a holder that
|
|
201
|
+
* lists the same negation still covers, one that does not is reported as not
|
|
202
|
+
* covering. That errs towards refusing an assignment, which is the safe
|
|
203
|
+
* direction for a guard.
|
|
204
|
+
*
|
|
205
|
+
* `:any` covers the matching `:own`, exactly as accesscontrol resolves them at
|
|
206
|
+
* enforcement time. Without that an administrator holding `read:any` on users
|
|
207
|
+
* would be told they cannot hand out a role holding `read:own` — a role
|
|
208
|
+
* strictly weaker than their own.
|
|
209
|
+
*/
|
|
210
|
+
covers(holder, wanted) {
|
|
211
|
+
for (const [resource, actions] of Object.entries(wanted ?? {})) {
|
|
212
|
+
const held = holder?.[resource];
|
|
213
|
+
if (!held) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
for (const [action, attributes] of Object.entries(actions ?? {})) {
|
|
217
|
+
const heldAttributes = held[action] ?? (action.endsWith(':own') ? held[action.replace(/:own$/, ':any')] : undefined);
|
|
218
|
+
if (!heldAttributes) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
if (heldAttributes.includes('*')) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if ((attributes ?? []).some((a) => !heldAttributes.includes(a))) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
__decorate([
|
|
233
|
+
Logger('rbac-admin'),
|
|
234
|
+
__metadata("design:type", Log)
|
|
235
|
+
], DefaultRoleGuard.prototype, "Log", void 0);
|
|
236
|
+
__decorate([
|
|
237
|
+
Config('rbac.admin.roleGuard'),
|
|
238
|
+
__metadata("design:type", Object)
|
|
239
|
+
], DefaultRoleGuard.prototype, "Options", void 0);
|
|
240
|
+
__decorate([
|
|
241
|
+
Config('rbac.systemRole', { defaultValue: 'system' }),
|
|
242
|
+
__metadata("design:type", String)
|
|
243
|
+
], DefaultRoleGuard.prototype, "SystemRole", void 0);
|
|
244
|
+
__decorate([
|
|
245
|
+
Config('rbac.roles', { defaultValue: [] }),
|
|
246
|
+
__metadata("design:type", Array)
|
|
247
|
+
], DefaultRoleGuard.prototype, "DeclaredRoles", void 0);
|
|
248
|
+
DefaultRoleGuard = __decorate([
|
|
249
|
+
Injectable(RoleGuard)
|
|
250
|
+
], DefaultRoleGuard);
|
|
251
|
+
export { DefaultRoleGuard };
|
|
252
|
+
//# sourceMappingURL=RoleGuard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RoleGuard.js","sourceRoot":"","sources":["../../../src/services/RoleGuard.ts"],"names":[],"mappings":"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"}
|