@spinajs/rbac 2.0.522 → 2.0.523
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/lib/cjs/actions.d.ts.map +1 -1
- package/lib/cjs/actions.js.map +1 -1
- package/lib/cjs/index.d.ts +2 -0
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +2 -0
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/interfaces.d.ts +0 -24
- package/lib/cjs/interfaces.d.ts.map +1 -1
- package/lib/cjs/interfaces.js +1 -5
- package/lib/cjs/interfaces.js.map +1 -1
- package/lib/cjs/middleware.d.ts +16 -6
- package/lib/cjs/middleware.d.ts.map +1 -1
- package/lib/cjs/middleware.js +151 -90
- package/lib/cjs/middleware.js.map +1 -1
- package/lib/cjs/orm-permission.d.ts +77 -0
- package/lib/cjs/orm-permission.d.ts.map +1 -0
- package/lib/cjs/orm-permission.js +128 -0
- package/lib/cjs/orm-permission.js.map +1 -0
- package/lib/cjs/permission-service.d.ts +80 -0
- package/lib/cjs/permission-service.d.ts.map +1 -0
- package/lib/cjs/permission-service.js +144 -0
- package/lib/cjs/permission-service.js.map +1 -0
- package/lib/mjs/actions.d.ts.map +1 -1
- package/lib/mjs/actions.js.map +1 -1
- package/lib/mjs/index.d.ts +2 -0
- package/lib/mjs/index.d.ts.map +1 -1
- package/lib/mjs/index.js +2 -0
- package/lib/mjs/index.js.map +1 -1
- package/lib/mjs/interfaces.d.ts +0 -24
- package/lib/mjs/interfaces.d.ts.map +1 -1
- package/lib/mjs/interfaces.js +0 -4
- package/lib/mjs/interfaces.js.map +1 -1
- package/lib/mjs/middleware.d.ts +16 -6
- package/lib/mjs/middleware.d.ts.map +1 -1
- package/lib/mjs/middleware.js +152 -91
- package/lib/mjs/middleware.js.map +1 -1
- package/lib/mjs/orm-permission.d.ts +77 -0
- package/lib/mjs/orm-permission.d.ts.map +1 -0
- package/lib/mjs/orm-permission.js +119 -0
- package/lib/mjs/orm-permission.js.map +1 -0
- package/lib/mjs/permission-service.d.ts +80 -0
- package/lib/mjs/permission-service.d.ts.map +1 -0
- package/lib/mjs/permission-service.js +137 -0
- package/lib/mjs/permission-service.js.map +1 -0
- package/lib/tsconfig.cjs.tsbuildinfo +1 -1
- package/lib/tsconfig.mjs.tsbuildinfo +1 -1
- package/package.json +11 -11
package/lib/cjs/middleware.js
CHANGED
|
@@ -13,13 +13,12 @@ exports.RbacModelPermissionMiddleware = void 0;
|
|
|
13
13
|
const di_1 = require("@spinajs/di");
|
|
14
14
|
const orm_1 = require("@spinajs/orm");
|
|
15
15
|
const async_hooks_1 = require("async_hooks");
|
|
16
|
-
const interfaces_js_1 = require("./interfaces.js");
|
|
17
|
-
const accesscontrol_1 = require("accesscontrol");
|
|
18
16
|
const exceptions_1 = require("@spinajs/exceptions");
|
|
19
17
|
const log_common_1 = require("@spinajs/log-common");
|
|
18
|
+
const orm_permission_js_1 = require("./orm-permission.js");
|
|
19
|
+
const permission_service_js_1 = require("./permission-service.js");
|
|
20
20
|
/**
|
|
21
|
-
* Which permission scopes a builder type is checked against
|
|
22
|
-
* model hook it looks for.
|
|
21
|
+
* Which permission scopes a builder type is checked against.
|
|
23
22
|
*
|
|
24
23
|
* Keyed on the CONSTRUCTOR, not on `constructor.name`. A name-keyed lookup breaks under
|
|
25
24
|
* minification and silently yields "no mapping" — for a security check that means an
|
|
@@ -28,14 +27,13 @@ const log_common_1 = require("@spinajs/log-common");
|
|
|
28
27
|
* `InsertQueryBuilder` was missing here while `PERMISSION_SCOPE_TO_QUERY` claimed to
|
|
29
28
|
* support `createOwn`/`createAny`, so the insert branch of the middleware could only ever
|
|
30
29
|
* have thrown a TypeError. The inverse map below is now DERIVED from this one, so the two
|
|
31
|
-
* cannot drift apart again
|
|
32
|
-
* can never be paired with the wrong builder type.
|
|
30
|
+
* cannot drift apart again.
|
|
33
31
|
*/
|
|
34
32
|
const QUERY_TO_PERMISSION = new Map([
|
|
35
|
-
[orm_1.DeleteQueryBuilder, { own: 'deleteOwn', all: 'deleteAny'
|
|
36
|
-
[orm_1.UpdateQueryBuilder, { own: 'updateOwn', all: 'updateAny'
|
|
37
|
-
[orm_1.SelectQueryBuilder, { own: 'readOwn', all: 'readAny'
|
|
38
|
-
[orm_1.InsertQueryBuilder, { own: 'createOwn', all: 'createAny'
|
|
33
|
+
[orm_1.DeleteQueryBuilder, { own: 'deleteOwn', all: 'deleteAny' }],
|
|
34
|
+
[orm_1.UpdateQueryBuilder, { own: 'updateOwn', all: 'updateAny' }],
|
|
35
|
+
[orm_1.SelectQueryBuilder, { own: 'readOwn', all: 'readAny' }],
|
|
36
|
+
[orm_1.InsertQueryBuilder, { own: 'createOwn', all: 'createAny' }],
|
|
39
37
|
]);
|
|
40
38
|
/** Derived inverse of {@link QUERY_TO_PERMISSION}. Never hand-maintained. */
|
|
41
39
|
const PERMISSION_SCOPE_TO_QUERY = new Map();
|
|
@@ -56,32 +54,60 @@ function permissionsFor(builder) {
|
|
|
56
54
|
return undefined;
|
|
57
55
|
}
|
|
58
56
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
57
|
+
* Steps from `model` up its prototype (class inheritance) chain to `boundModel`, or
|
|
58
|
+
* `undefined` if `boundModel` is not `model` itself or an ancestor. ES class inheritance
|
|
59
|
+
* puts the parent constructor at `Object.getPrototypeOf(Child)`, so walking constructors —
|
|
60
|
+
* not instances — is the correct way to test "is a subclass of".
|
|
61
|
+
*/
|
|
62
|
+
function distanceTo(model, boundModel) {
|
|
63
|
+
let current = model;
|
|
64
|
+
for (let distance = 0; current; distance++) {
|
|
65
|
+
if (current === boundModel) {
|
|
66
|
+
return distance;
|
|
67
|
+
}
|
|
68
|
+
current = Object.getPrototypeOf(current);
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Picks the policy bound to `model` itself or the closest bound ancestor. Two unrelated
|
|
74
|
+
* models can share one resource+scope key on purpose (CampaignFileAttachment /
|
|
75
|
+
* ArrowV1CampaignView) — the resource string alone cannot disambiguate them, so every
|
|
76
|
+
* candidate is checked against `model`'s actual prototype chain. When both an ancestor-bound
|
|
77
|
+
* and an exact-bound policy are registered, the most-derived (smallest distance) one wins.
|
|
72
78
|
*/
|
|
73
|
-
function
|
|
74
|
-
|
|
75
|
-
if (!statics) {
|
|
79
|
+
function selectPolicyClass(candidates, model) {
|
|
80
|
+
if (!candidates) {
|
|
76
81
|
return undefined;
|
|
77
82
|
}
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
let best;
|
|
84
|
+
let bestDistance = Infinity;
|
|
85
|
+
for (const candidate of candidates) {
|
|
86
|
+
const boundModel = (0, orm_permission_js_1.ormPermissionModel)(candidate);
|
|
87
|
+
if (!boundModel) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const distance = distanceTo(model, boundModel);
|
|
91
|
+
if (distance !== undefined && distance < bestDistance) {
|
|
92
|
+
best = candidate;
|
|
93
|
+
bestDistance = distance;
|
|
94
|
+
}
|
|
80
95
|
}
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
return best;
|
|
97
|
+
}
|
|
98
|
+
/** Fallback for models with `@ResourceOwner()` and no registered policy class. */
|
|
99
|
+
class OwnerFieldPolicy extends orm_permission_js_1.OrmPermissionPolicy {
|
|
100
|
+
constructor(ownerField) {
|
|
101
|
+
super();
|
|
102
|
+
this.ownerField = ownerField;
|
|
103
|
+
}
|
|
104
|
+
scope(query, user) {
|
|
105
|
+
query.andWhere(this.ownerField, user.PrimaryKeyValue);
|
|
106
|
+
}
|
|
107
|
+
async authorizeCreate(query, user) {
|
|
108
|
+
// Overwrite, never merge: a caller-supplied owner id is exactly the IDOR this closes.
|
|
109
|
+
query.forceColumn(this.ownerField, user.PrimaryKeyValue);
|
|
83
110
|
}
|
|
84
|
-
return undefined;
|
|
85
111
|
}
|
|
86
112
|
let RbacModelPermissionMiddleware = class RbacModelPermissionMiddleware extends orm_1.QueryMiddleware {
|
|
87
113
|
/**
|
|
@@ -106,26 +132,31 @@ let RbacModelPermissionMiddleware = class RbacModelPermissionMiddleware extends
|
|
|
106
132
|
if (!canOwn) {
|
|
107
133
|
throw new exceptions_1.Forbidden(`User does not have permission to access ${resource}:${context.action} permission`);
|
|
108
134
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
135
|
+
const policies = this.policiesFor(context.roles, resource, context.ownScope, descriptor, builder.Model);
|
|
136
|
+
// A candidate that does not opt into insert control denies by default (see
|
|
137
|
+
// OrmPermissionPolicy.authorizeCreate). Pre-refactor, a model with no rbacCreate static
|
|
138
|
+
// but an OwnerField had its owner column stamped and the insert allowed — an unrelated
|
|
139
|
+
// policy registered only for scope() must not shadow that. Deny-by-default is kept when
|
|
140
|
+
// there is no OwnerField to fall back to.
|
|
141
|
+
const createPolicies = policies.map((policy) => {
|
|
142
|
+
const opensOwnCreate = policy.authorizeCreate !== orm_permission_js_1.OrmPermissionPolicy.prototype.authorizeCreate;
|
|
143
|
+
return !opensOwnCreate && descriptor.OwnerField ? new OwnerFieldPolicy(descriptor.OwnerField) : policy;
|
|
144
|
+
});
|
|
145
|
+
// Multiple distinct scopes OR-compose on inserts too: the insert is allowed if ANY
|
|
146
|
+
// granted role's policy authorizes it. Policies are tried in registration-set order;
|
|
147
|
+
// when every one denies, the last error propagates.
|
|
148
|
+
let lastError;
|
|
149
|
+
for (const policy of createPolicies) {
|
|
150
|
+
try {
|
|
151
|
+
this.Log.trace(`Applying authorizeCreate of ${policy.constructor.name} for ${resource}`);
|
|
152
|
+
await policy.authorizeCreate(builder, context.user);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
lastError = err;
|
|
157
|
+
}
|
|
126
158
|
}
|
|
127
|
-
|
|
128
|
-
builder.forceColumn(descriptor.OwnerField, context.user.PrimaryKeyValue);
|
|
159
|
+
throw lastError;
|
|
129
160
|
}
|
|
130
161
|
afterQueryCreation(builder) {
|
|
131
162
|
// Insert is handled at execution time; anything else without a WHERE clause to constrain
|
|
@@ -146,11 +177,6 @@ let RbacModelPermissionMiddleware = class RbacModelPermissionMiddleware extends
|
|
|
146
177
|
throw new exceptions_1.Forbidden(`User does not have permission to access ${resource}:${context.action} permission`);
|
|
147
178
|
}
|
|
148
179
|
this.Log.trace(`Resource ${resource}:own permission granted`);
|
|
149
|
-
/**
|
|
150
|
-
* Model can have a custom rbac permission check, either for this operation
|
|
151
|
-
* specifically (`rbacRead` / `rbacUpdate` / `rbacDelete`) or generically (`rbac`).
|
|
152
|
-
*/
|
|
153
|
-
const rbacFunc = rbacHook(builder.Model, context.hook, true);
|
|
154
180
|
/**
|
|
155
181
|
* `relationScope: 'join'` ( @OrmResource options ): when this model is populated as a
|
|
156
182
|
* BelongsTo relation, its :own constraint folded into the parent WHERE would silently
|
|
@@ -161,31 +187,79 @@ let RbacModelPermissionMiddleware = class RbacModelPermissionMiddleware extends
|
|
|
161
187
|
*/
|
|
162
188
|
const joinScoped = descriptor.RbacRelationScope === 'join' && builder instanceof orm_1.SelectQueryBuilder;
|
|
163
189
|
const user = context.user;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
rbacFunc.call(builder, user);
|
|
190
|
+
const policies = this.policiesFor(context.roles, resource, context.ownScope, descriptor, builder.Model);
|
|
191
|
+
const method = builder instanceof orm_1.UpdateQueryBuilder ? 'scopeUpdate' : builder instanceof orm_1.DeleteQueryBuilder ? 'scopeDelete' : 'scopeRead';
|
|
192
|
+
const apply = (target) => {
|
|
193
|
+
if (policies.length === 1) {
|
|
194
|
+
policies[0][method](target, user);
|
|
195
|
+
return;
|
|
173
196
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
197
|
+
// Distinct scopes across the caller's roles OR-compose: reachable if ANY admits.
|
|
198
|
+
target.andWhere(function () {
|
|
199
|
+
for (const policy of policies) {
|
|
200
|
+
this.orWhere(function () {
|
|
201
|
+
policy[method](this, user);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
this.Log.trace(`Applying ${policies.length} permission ${policies.length === 1 ? 'policy' : 'policies'} (${method}) for ${resource}`);
|
|
181
207
|
if (joinScoped) {
|
|
182
208
|
builder.whereOnJoin(function () {
|
|
183
|
-
builder
|
|
209
|
+
apply(builder);
|
|
184
210
|
});
|
|
185
211
|
}
|
|
186
212
|
else {
|
|
187
|
-
builder
|
|
213
|
+
apply(builder);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Policies to apply for this query: one per distinct scope name across the caller's
|
|
218
|
+
* granted roles. A role without the grant must not contribute a policy — that would
|
|
219
|
+
* WIDEN the OR-composition in `afterQueryCreation`. Multiple distinct scopes OR-compose
|
|
220
|
+
* downstream: permissions are additive, a row is reachable if any granted role's policy
|
|
221
|
+
* admits it.
|
|
222
|
+
*/
|
|
223
|
+
policiesFor(roles, resource, ownScope, descriptor, model) {
|
|
224
|
+
const scopeNames = new Set();
|
|
225
|
+
for (const role of roles) {
|
|
226
|
+
// role unknown to accesscontrol answers null — contributes no grant, no scope
|
|
227
|
+
const permission = (0, permission_service_js_1.probeGrant)([role], ownScope, resource);
|
|
228
|
+
if (!permission?.granted) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const attrs = permission.attributes ?? [];
|
|
232
|
+
const tokens = attrs.filter((a) => typeof a === 'string' && a.startsWith(orm_permission_js_1.PERMISSION_SCOPE_ATTR_PREFIX)).map((a) => a.slice(orm_permission_js_1.PERMISSION_SCOPE_ATTR_PREFIX.length));
|
|
233
|
+
if (tokens.length === 0) {
|
|
234
|
+
scopeNames.add(orm_permission_js_1.DEFAULT_PERMISSION_SCOPE);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
tokens.forEach((t) => scopeNames.add(t));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// canOwn was computed over the role union; per-role queries can still all miss when
|
|
241
|
+
// grants come from odd extension shapes. Degrade to the default scope — resolution
|
|
242
|
+
// below still fails loud if nothing is registered.
|
|
243
|
+
if (scopeNames.size === 0) {
|
|
244
|
+
scopeNames.add(orm_permission_js_1.DEFAULT_PERMISSION_SCOPE);
|
|
188
245
|
}
|
|
246
|
+
const registered = (0, orm_permission_js_1.getOrCreatePolicyMap)();
|
|
247
|
+
const policies = [];
|
|
248
|
+
for (const name of scopeNames) {
|
|
249
|
+
const candidates = registered.get((0, orm_permission_js_1.policyMapKey)(resource, name));
|
|
250
|
+
const policyClass = selectPolicyClass(candidates, model);
|
|
251
|
+
if (policyClass) {
|
|
252
|
+
policies.push(di_1.DI.resolve(policyClass));
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (name === orm_permission_js_1.DEFAULT_PERMISSION_SCOPE && descriptor.OwnerField) {
|
|
256
|
+
policies.push(new OwnerFieldPolicy(descriptor.OwnerField));
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
this.Log.error(`No OrmPermissionPolicy registered for ${descriptor.Name}/${name} and no OwnerField fallback`);
|
|
260
|
+
throw new orm_1.OrmException(`no OrmPermissionPolicy registered for model ${descriptor.Name} scope '${name}' and no OwnerField fallback — refusing to run an unscoped :own query`);
|
|
261
|
+
}
|
|
262
|
+
return policies;
|
|
189
263
|
}
|
|
190
264
|
/**
|
|
191
265
|
* Everything both hooks need, or `undefined` when this query is not subject to a check.
|
|
@@ -233,22 +307,13 @@ let RbacModelPermissionMiddleware = class RbacModelPermissionMiddleware extends
|
|
|
233
307
|
}
|
|
234
308
|
const ownScope = storage.PermissionScope ?? scopes.own;
|
|
235
309
|
const anyScope = storage.PermissionScope ?? scopes.all;
|
|
236
|
-
const roles =
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
try {
|
|
240
|
-
canAny = this.Ac.can(roles)[anyScope](resource).granted;
|
|
241
|
-
canOwn = this.Ac.can(roles)[ownScope](resource).granted;
|
|
242
|
-
}
|
|
243
|
-
catch (err) {
|
|
244
|
-
// accesscontrol throws eg. "Role not found" when role has no grants registered
|
|
245
|
-
// treat as no permission so caller gets Forbidden instead of library error
|
|
246
|
-
this.Log.trace(`Permission check for roles ${roles} on resource ${resource} failed: ${err.message}, treating as no permission`);
|
|
247
|
-
}
|
|
310
|
+
const roles = (0, permission_service_js_1.effectiveRoles)(storage.User, storage);
|
|
311
|
+
const canAny = (0, permission_service_js_1.probeGrant)(roles, anyScope, resource)?.granted ?? false;
|
|
312
|
+
const canOwn = (0, permission_service_js_1.probeGrant)(roles, ownScope, resource)?.granted ?? false;
|
|
248
313
|
// 'readOwn' -> 'read'. Keeps the Forbidden message accurate per builder type instead of
|
|
249
314
|
// the old hard-coded ':read', which was wrong for updates and deletes.
|
|
250
315
|
const action = anyScope.replace(/(Any|Own)$/, '');
|
|
251
|
-
return { descriptor, resource, canOwn, canAny, action,
|
|
316
|
+
return { descriptor, resource, canOwn, canAny, action, user: storage.User, roles, ownScope };
|
|
252
317
|
}
|
|
253
318
|
};
|
|
254
319
|
exports.RbacModelPermissionMiddleware = RbacModelPermissionMiddleware;
|
|
@@ -256,10 +321,6 @@ __decorate([
|
|
|
256
321
|
(0, log_common_1.Logger)('RBAC'),
|
|
257
322
|
__metadata("design:type", log_common_1.Log)
|
|
258
323
|
], RbacModelPermissionMiddleware.prototype, "Log", void 0);
|
|
259
|
-
__decorate([
|
|
260
|
-
(0, di_1.Autoinject)(),
|
|
261
|
-
__metadata("design:type", accesscontrol_1.AccessControl)
|
|
262
|
-
], RbacModelPermissionMiddleware.prototype, "Ac", void 0);
|
|
263
324
|
exports.RbacModelPermissionMiddleware = RbacModelPermissionMiddleware = __decorate([
|
|
264
325
|
(0, di_1.Injectable)(orm_1.QueryMiddleware)
|
|
265
326
|
], RbacModelPermissionMiddleware);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/middleware.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Class } from '@spinajs/di';
|
|
2
|
+
import { InsertQueryBuilder, IWhereBuilder, ModelBase } from '@spinajs/orm';
|
|
3
|
+
import type { User } from './models/User.js';
|
|
4
|
+
export declare const DEFAULT_PERMISSION_SCOPE = "default";
|
|
5
|
+
/**
|
|
6
|
+
* Attribute prefix marking a named permission scope on a role grant (eg. `'scope:pool'`),
|
|
7
|
+
* read by `RbacModelPermissionMiddleware.policiesFor()` to pick a non-default
|
|
8
|
+
* `OrmPermissionPolicy`.
|
|
9
|
+
*
|
|
10
|
+
* WARNING: `accesscontrol`'s attribute union across a role and every role it `$extend`s
|
|
11
|
+
* collapses `['*']` merged with `['scope:x']` down to `['*']` (glob union absorption). A role
|
|
12
|
+
* granting `read:own: ['scope:pool']` while a role it `$extend`s (or that extends it) grants
|
|
13
|
+
* the same resource `['*']` silently loses the `scope:pool` token, and resolution falls
|
|
14
|
+
* through to the DEFAULT policy instead of the named one. This is a known limitation, not
|
|
15
|
+
* handled by this module — never mix `['*']` and `scope:` attributes for the SAME resource
|
|
16
|
+
* across an `$extend` chain.
|
|
17
|
+
*/
|
|
18
|
+
export declare const PERMISSION_SCOPE_ATTR_PREFIX = "scope:";
|
|
19
|
+
export declare const ORM_PERMISSION_POLICY_MAP = "__orm_permission_policy__";
|
|
20
|
+
/**
|
|
21
|
+
* Bound model recorded on a policy class by `@OrmPermission`. Needed because the map is
|
|
22
|
+
* keyed on the shared resource STRING (minification-safe), which two unrelated models can
|
|
23
|
+
* legally share for grant purposes — the model recorded here is what lets `policiesFor()`
|
|
24
|
+
* in middleware.ts pick the right policy for `builder.Model` instead of an arbitrary one.
|
|
25
|
+
*/
|
|
26
|
+
export declare const ORM_PERMISSION_MODEL: unique symbol;
|
|
27
|
+
/**
|
|
28
|
+
* Keyed on the DECLARED `@OrmResource` string, never on `constructor.name` — grants are
|
|
29
|
+
* keyed on the same string, so a model's resource name is the single permission identity
|
|
30
|
+
* everywhere, and models sharing a resource (a view over the base table) share policies.
|
|
31
|
+
*/
|
|
32
|
+
export declare function policyMapKey(resource: string, scope: string): string;
|
|
33
|
+
export declare abstract class OrmPermissionPolicy<M extends ModelBase<unknown> = ModelBase<unknown>> {
|
|
34
|
+
/**
|
|
35
|
+
* Default throws so an accidentally inert policy fails loud instead of running an
|
|
36
|
+
* unscoped query.
|
|
37
|
+
*/
|
|
38
|
+
scope(_query: IWhereBuilder<M>, _user: User): void;
|
|
39
|
+
scopeRead(query: IWhereBuilder<M>, user: User): void;
|
|
40
|
+
scopeUpdate(query: IWhereBuilder<M>, user: User): void;
|
|
41
|
+
scopeDelete(query: IWhereBuilder<M>, user: User): void;
|
|
42
|
+
/**
|
|
43
|
+
* Default denies: a policy that does not opt into insert control must not silently
|
|
44
|
+
* allow inserts under a `:own` grant. An authorization denial, not a config error, so
|
|
45
|
+
* `Forbidden` (403) — contrast `scope()`'s default, which stays `OrmException` because an
|
|
46
|
+
* unimplemented scope is a config bug. Callers with an `OwnerField` never reach this: see
|
|
47
|
+
* `RbacModelPermissionMiddleware.beforeQueryExecution`'s OwnerField fallback.
|
|
48
|
+
*/
|
|
49
|
+
authorizeCreate(_query: InsertQueryBuilder, _user: User): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
export type OrmPermissionPolicyClass = Class<OrmPermissionPolicy>;
|
|
52
|
+
/** The model `@OrmPermission` bound `policy` to, or `undefined` for a policy built by hand
|
|
53
|
+
* (eg. the `OwnerFieldPolicy` fallback) that never went through the decorator.
|
|
54
|
+
*
|
|
55
|
+
* Read as an OWN property: `target[ORM_PERMISSION_MODEL] = model` stamps the constructor
|
|
56
|
+
* object itself, and ES class inheritance chains constructors ( `Sub.__proto__ === Base` ), so
|
|
57
|
+
* a plain property read here would let an undecorated `class Sub extends Base {}` silently
|
|
58
|
+
* inherit `Base`'s bound model instead of having none.
|
|
59
|
+
*/
|
|
60
|
+
export declare function ormPermissionModel(policy: OrmPermissionPolicyClass): Class<ModelBase<unknown>> | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* The single accessor for the policy map — used by both the registration side
|
|
63
|
+
* (`OrmPermission`) and the resolution side (`RbacModelPermissionMiddleware.policiesFor`) so
|
|
64
|
+
* the two can never read/write through differing DI cache accessors and drift apart.
|
|
65
|
+
*/
|
|
66
|
+
export declare function getOrCreatePolicyMap(): Map<string, OrmPermissionPolicyClass[]>;
|
|
67
|
+
/**
|
|
68
|
+
* Registers `target` as a policy for `model`'s resource + `scope` in the DI hashmap.
|
|
69
|
+
* Refuses a model without `@OrmResource` (cannot be permission-guarded). The map value is a
|
|
70
|
+
* LIST: two unrelated models are allowed to share one resource+scope key (the
|
|
71
|
+
* CampaignFileAttachment / ArrowV1CampaignView shape) — only the same (model, scope) pair
|
|
72
|
+
* registered twice is a duplicate/config bug.
|
|
73
|
+
*/
|
|
74
|
+
export declare function OrmPermission<M extends ModelBase<unknown>>(model: Class<M>, scope?: string): (target: Class<OrmPermissionPolicy<M>>) => void;
|
|
75
|
+
/** Test helper — wipes all registrations from the DI cache. */
|
|
76
|
+
export declare function clearOrmPermissionRegistry(): void;
|
|
77
|
+
//# sourceMappingURL=orm-permission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orm-permission.d.ts","sourceRoot":"","sources":["../../src/orm-permission.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAM,MAAM,aAAa,CAAC;AACxC,OAAO,EAA0B,kBAAkB,EAAE,aAAa,EAAE,SAAS,EAAgB,MAAM,cAAc,CAAC;AAGlH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,eAAO,MAAM,wBAAwB,YAAY,CAAC;AAElD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,4BAA4B,WAAW,CAAC;AACrD,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;AAErE;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,eAAqC,CAAC;AAEvE;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED,8BAAsB,mBAAmB,CAAC,CAAC,SAAS,SAAS,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC;IACzF;;;OAGG;IACI,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,GAAG,IAAI,CAExD;IAEM,SAAS,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAE1D;IAEM,WAAW,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAE5D;IAEM,WAAW,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAE5D;IAED;;;;;;OAMG;IACU,eAAe,CAAC,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnF;CACF;AAED,MAAM,MAAM,wBAAwB,GAAG,KAAK,CAAC,mBAAmB,CAAC,CAAC;AAElE;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,wBAAwB,GAAG,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CAG1G;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,GAAG,CAAC,MAAM,EAAE,wBAAwB,EAAE,CAAC,CAO9E;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,SAAS,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,GAAE,MAAiC,YACnG,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAAG,IAAI,CAqBrD;AAED,+DAA+D;AAC/D,wBAAgB,0BAA0B,IAAI,IAAI,CAIjD"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OrmPermissionPolicy = exports.ORM_PERMISSION_MODEL = exports.ORM_PERMISSION_POLICY_MAP = exports.PERMISSION_SCOPE_ATTR_PREFIX = exports.DEFAULT_PERMISSION_SCOPE = void 0;
|
|
4
|
+
exports.policyMapKey = policyMapKey;
|
|
5
|
+
exports.ormPermissionModel = ormPermissionModel;
|
|
6
|
+
exports.getOrCreatePolicyMap = getOrCreatePolicyMap;
|
|
7
|
+
exports.OrmPermission = OrmPermission;
|
|
8
|
+
exports.clearOrmPermissionRegistry = clearOrmPermissionRegistry;
|
|
9
|
+
const di_1 = require("@spinajs/di");
|
|
10
|
+
const orm_1 = require("@spinajs/orm");
|
|
11
|
+
const exceptions_1 = require("@spinajs/exceptions");
|
|
12
|
+
exports.DEFAULT_PERMISSION_SCOPE = 'default';
|
|
13
|
+
/**
|
|
14
|
+
* Attribute prefix marking a named permission scope on a role grant (eg. `'scope:pool'`),
|
|
15
|
+
* read by `RbacModelPermissionMiddleware.policiesFor()` to pick a non-default
|
|
16
|
+
* `OrmPermissionPolicy`.
|
|
17
|
+
*
|
|
18
|
+
* WARNING: `accesscontrol`'s attribute union across a role and every role it `$extend`s
|
|
19
|
+
* collapses `['*']` merged with `['scope:x']` down to `['*']` (glob union absorption). A role
|
|
20
|
+
* granting `read:own: ['scope:pool']` while a role it `$extend`s (or that extends it) grants
|
|
21
|
+
* the same resource `['*']` silently loses the `scope:pool` token, and resolution falls
|
|
22
|
+
* through to the DEFAULT policy instead of the named one. This is a known limitation, not
|
|
23
|
+
* handled by this module — never mix `['*']` and `scope:` attributes for the SAME resource
|
|
24
|
+
* across an `$extend` chain.
|
|
25
|
+
*/
|
|
26
|
+
exports.PERMISSION_SCOPE_ATTR_PREFIX = 'scope:';
|
|
27
|
+
exports.ORM_PERMISSION_POLICY_MAP = '__orm_permission_policy__';
|
|
28
|
+
/**
|
|
29
|
+
* Bound model recorded on a policy class by `@OrmPermission`. Needed because the map is
|
|
30
|
+
* keyed on the shared resource STRING (minification-safe), which two unrelated models can
|
|
31
|
+
* legally share for grant purposes — the model recorded here is what lets `policiesFor()`
|
|
32
|
+
* in middleware.ts pick the right policy for `builder.Model` instead of an arbitrary one.
|
|
33
|
+
*/
|
|
34
|
+
exports.ORM_PERMISSION_MODEL = Symbol.for('orm-permission-model');
|
|
35
|
+
/**
|
|
36
|
+
* Keyed on the DECLARED `@OrmResource` string, never on `constructor.name` — grants are
|
|
37
|
+
* keyed on the same string, so a model's resource name is the single permission identity
|
|
38
|
+
* everywhere, and models sharing a resource (a view over the base table) share policies.
|
|
39
|
+
*/
|
|
40
|
+
function policyMapKey(resource, scope) {
|
|
41
|
+
return `${resource}:${scope}`;
|
|
42
|
+
}
|
|
43
|
+
class OrmPermissionPolicy {
|
|
44
|
+
/**
|
|
45
|
+
* Default throws so an accidentally inert policy fails loud instead of running an
|
|
46
|
+
* unscoped query.
|
|
47
|
+
*/
|
|
48
|
+
scope(_query, _user) {
|
|
49
|
+
throw new orm_1.OrmException(`${this.constructor.name} defines neither a generic scope() nor a specific hook for this operation`);
|
|
50
|
+
}
|
|
51
|
+
scopeRead(query, user) {
|
|
52
|
+
this.scope(query, user);
|
|
53
|
+
}
|
|
54
|
+
scopeUpdate(query, user) {
|
|
55
|
+
this.scope(query, user);
|
|
56
|
+
}
|
|
57
|
+
scopeDelete(query, user) {
|
|
58
|
+
this.scope(query, user);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Default denies: a policy that does not opt into insert control must not silently
|
|
62
|
+
* allow inserts under a `:own` grant. An authorization denial, not a config error, so
|
|
63
|
+
* `Forbidden` (403) — contrast `scope()`'s default, which stays `OrmException` because an
|
|
64
|
+
* unimplemented scope is a config bug. Callers with an `OwnerField` never reach this: see
|
|
65
|
+
* `RbacModelPermissionMiddleware.beforeQueryExecution`'s OwnerField fallback.
|
|
66
|
+
*/
|
|
67
|
+
async authorizeCreate(_query, _user) {
|
|
68
|
+
throw new exceptions_1.Forbidden(`${this.constructor.name} does not implement authorizeCreate — INSERT under :own is not permitted by this policy`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.OrmPermissionPolicy = OrmPermissionPolicy;
|
|
72
|
+
/** The model `@OrmPermission` bound `policy` to, or `undefined` for a policy built by hand
|
|
73
|
+
* (eg. the `OwnerFieldPolicy` fallback) that never went through the decorator.
|
|
74
|
+
*
|
|
75
|
+
* Read as an OWN property: `target[ORM_PERMISSION_MODEL] = model` stamps the constructor
|
|
76
|
+
* object itself, and ES class inheritance chains constructors ( `Sub.__proto__ === Base` ), so
|
|
77
|
+
* a plain property read here would let an undecorated `class Sub extends Base {}` silently
|
|
78
|
+
* inherit `Base`'s bound model instead of having none.
|
|
79
|
+
*/
|
|
80
|
+
function ormPermissionModel(policy) {
|
|
81
|
+
const target = policy;
|
|
82
|
+
return Object.prototype.hasOwnProperty.call(policy, exports.ORM_PERMISSION_MODEL) ? target[exports.ORM_PERMISSION_MODEL] : undefined;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The single accessor for the policy map — used by both the registration side
|
|
86
|
+
* (`OrmPermission`) and the resolution side (`RbacModelPermissionMiddleware.policiesFor`) so
|
|
87
|
+
* the two can never read/write through differing DI cache accessors and drift apart.
|
|
88
|
+
*/
|
|
89
|
+
function getOrCreatePolicyMap() {
|
|
90
|
+
if (di_1.DI.RootContainer.Cache.has(exports.ORM_PERMISSION_POLICY_MAP)) {
|
|
91
|
+
return di_1.DI.RootContainer.Cache.get(exports.ORM_PERMISSION_POLICY_MAP)[0];
|
|
92
|
+
}
|
|
93
|
+
const map = new Map();
|
|
94
|
+
di_1.DI.RootContainer.Cache.add(exports.ORM_PERMISSION_POLICY_MAP, map);
|
|
95
|
+
return map;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Registers `target` as a policy for `model`'s resource + `scope` in the DI hashmap.
|
|
99
|
+
* Refuses a model without `@OrmResource` (cannot be permission-guarded). The map value is a
|
|
100
|
+
* LIST: two unrelated models are allowed to share one resource+scope key (the
|
|
101
|
+
* CampaignFileAttachment / ArrowV1CampaignView shape) — only the same (model, scope) pair
|
|
102
|
+
* registered twice is a duplicate/config bug.
|
|
103
|
+
*/
|
|
104
|
+
function OrmPermission(model, scope = exports.DEFAULT_PERMISSION_SCOPE) {
|
|
105
|
+
return (target) => {
|
|
106
|
+
const descriptor = (0, orm_1.extractModelDescriptor)(model);
|
|
107
|
+
const resource = descriptor?.RbacResource;
|
|
108
|
+
if (!resource) {
|
|
109
|
+
throw new orm_1.OrmException(`cannot register ${target.name} for ${model.name}: model has no @OrmResource declaration`);
|
|
110
|
+
}
|
|
111
|
+
target[exports.ORM_PERMISSION_MODEL] = model;
|
|
112
|
+
const key = policyMapKey(resource, scope);
|
|
113
|
+
const map = getOrCreatePolicyMap();
|
|
114
|
+
const list = map.get(key) ?? [];
|
|
115
|
+
if (list.some((p) => ormPermissionModel(p) === model)) {
|
|
116
|
+
throw new orm_1.OrmException(`duplicate OrmPermission registration for ${model.name}/${scope} (${target.name} already registered for this model+scope)`);
|
|
117
|
+
}
|
|
118
|
+
list.push(target);
|
|
119
|
+
map.set(key, list);
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/** Test helper — wipes all registrations from the DI cache. */
|
|
123
|
+
function clearOrmPermissionRegistry() {
|
|
124
|
+
if (di_1.DI.RootContainer.Cache.has(exports.ORM_PERMISSION_POLICY_MAP)) {
|
|
125
|
+
di_1.DI.RootContainer.Cache.remove(exports.ORM_PERMISSION_POLICY_MAP);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=orm-permission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orm-permission.js","sourceRoot":"","sources":["../../src/orm-permission.ts"],"names":[],"mappings":"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"}
|