@backstage-community/plugin-rbac-backend 5.2.9 → 5.3.0

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.
@@ -5,18 +5,66 @@ var EventEmitter = require('events');
5
5
  var adminCreation = require('../admin-permissions/admin-creation.cjs.js');
6
6
  var helper = require('../helper.cjs.js');
7
7
  var permissionModel = require('./permission-model.cjs.js');
8
+ var auditLogger = require('../audit-log/audit-logger.cjs.js');
8
9
 
9
10
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
10
11
 
11
12
  var EventEmitter__default = /*#__PURE__*/_interopDefaultCompat(EventEmitter);
12
13
 
13
14
  class EnforcerDelegate {
14
- constructor(enforcer, roleMetadataStorage, knex) {
15
+ // Queue to track edit operations
16
+ constructor(enforcer, auditLogger, roleMetadataStorage, knex) {
15
17
  this.enforcer = enforcer;
18
+ this.auditLogger = auditLogger;
16
19
  this.roleMetadataStorage = roleMetadataStorage;
17
20
  this.knex = knex;
18
21
  }
19
22
  roleEventEmitter = new EventEmitter__default.default();
23
+ loadPolicyPromise = null;
24
+ semaphore = 0;
25
+ editOperationsQueue = [];
26
+ async loadPolicy() {
27
+ if (this.loadPolicyPromise) {
28
+ return this.loadPolicyPromise;
29
+ }
30
+ this.semaphore++;
31
+ this.loadPolicyPromise = (async () => {
32
+ try {
33
+ await this.waitForEditOperationsToFinish();
34
+ await this.enforcer.loadPolicy();
35
+ } catch (err) {
36
+ this.auditLogger.auditLog({
37
+ message: "Failed to load newer policies from database",
38
+ eventName: auditLogger.PoliciesData.FAILED_TO_FETCH_NEWER_PERMISSIONS,
39
+ stage: auditLogger.FETCH_NEWER_PERMISSIONS_STAGE,
40
+ status: "failed",
41
+ errors: [err]
42
+ });
43
+ } finally {
44
+ this.semaphore--;
45
+ this.loadPolicyPromise = null;
46
+ }
47
+ })();
48
+ return this.loadPolicyPromise;
49
+ }
50
+ async waitForEditOperationsToFinish() {
51
+ await Promise.all(this.editOperationsQueue);
52
+ }
53
+ async execOperation(operation) {
54
+ this.editOperationsQueue.push(operation);
55
+ let result;
56
+ try {
57
+ result = await operation;
58
+ } catch (err) {
59
+ throw err;
60
+ } finally {
61
+ const index = this.editOperationsQueue.indexOf(operation);
62
+ if (index !== -1) {
63
+ this.editOperationsQueue.splice(index, 1);
64
+ }
65
+ }
66
+ return result;
67
+ }
20
68
  on(event, listener) {
21
69
  this.roleEventEmitter.on(event, listener);
22
70
  return this;
@@ -119,110 +167,128 @@ class EnforcerDelegate {
119
167
  }
120
168
  }
121
169
  async addPolicies(policies, externalTrx) {
122
- if (policies.length === 0) {
123
- return;
170
+ if (this.loadPolicyPromise) {
171
+ await this.loadPolicyPromise;
124
172
  }
125
- const trx = externalTrx || await this.knex.transaction();
126
- try {
127
- const ok = await this.enforcer.addPolicies(policies);
128
- if (!ok) {
129
- throw new Error(
130
- `Failed to store policies ${helper.policiesToString(policies)}`
131
- );
132
- }
133
- if (!externalTrx) {
134
- await trx.commit();
135
- }
136
- } catch (err) {
137
- if (!externalTrx) {
138
- await trx.rollback(err);
173
+ const addPoliciesOperation = (async () => {
174
+ if (policies.length === 0) {
175
+ return;
176
+ }
177
+ const trx = externalTrx || await this.knex.transaction();
178
+ try {
179
+ const ok = await this.enforcer.addPolicies(policies);
180
+ if (!ok) {
181
+ throw new Error(
182
+ `Failed to store policies ${helper.policiesToString(policies)}`
183
+ );
184
+ }
185
+ if (!externalTrx) {
186
+ await trx.commit();
187
+ }
188
+ } catch (err) {
189
+ if (!externalTrx) {
190
+ await trx.rollback(err);
191
+ }
192
+ throw err;
139
193
  }
140
- throw err;
141
- }
194
+ })();
195
+ await this.execOperation(addPoliciesOperation);
142
196
  }
143
197
  async addGroupingPolicy(policy, roleMetadata, externalTrx) {
144
- const trx = externalTrx ?? await this.knex.transaction();
145
- const entityRef = roleMetadata.roleEntityRef;
146
- if (await this.hasGroupingPolicy(...policy)) {
147
- return;
198
+ if (this.loadPolicyPromise) {
199
+ await this.loadPolicyPromise;
148
200
  }
149
- try {
150
- let currentMetadata;
151
- if (entityRef.startsWith(`role:`)) {
152
- currentMetadata = await this.roleMetadataStorage.findRoleMetadata(
153
- entityRef,
154
- trx
155
- );
156
- }
157
- if (currentMetadata) {
158
- await this.roleMetadataStorage.updateRoleMetadata(
159
- helper.mergeRoleMetadata(currentMetadata, roleMetadata),
160
- entityRef,
161
- trx
162
- );
163
- } else {
164
- const currentDate = /* @__PURE__ */ new Date();
165
- roleMetadata.createdAt = currentDate.toUTCString();
166
- roleMetadata.lastModified = currentDate.toUTCString();
167
- await this.roleMetadataStorage.createRoleMetadata(roleMetadata, trx);
168
- }
169
- const ok = await this.enforcer.addGroupingPolicy(...policy);
170
- if (!ok) {
171
- throw new Error(`failed to create policy ${helper.policyToString(policy)}`);
172
- }
173
- if (!externalTrx) {
174
- await trx.commit();
175
- }
176
- if (!currentMetadata) {
177
- this.roleEventEmitter.emit("roleAdded", roleMetadata.roleEntityRef);
178
- }
179
- } catch (err) {
180
- if (!externalTrx) {
181
- await trx.rollback(err);
201
+ const addGroupingPolicyOperation = (async () => {
202
+ const trx = externalTrx ?? await this.knex.transaction();
203
+ const entityRef = roleMetadata.roleEntityRef;
204
+ if (await this.hasGroupingPolicy(...policy)) {
205
+ return;
206
+ }
207
+ try {
208
+ let currentMetadata;
209
+ if (entityRef.startsWith(`role:`)) {
210
+ currentMetadata = await this.roleMetadataStorage.findRoleMetadata(
211
+ entityRef,
212
+ trx
213
+ );
214
+ }
215
+ if (currentMetadata) {
216
+ await this.roleMetadataStorage.updateRoleMetadata(
217
+ helper.mergeRoleMetadata(currentMetadata, roleMetadata),
218
+ entityRef,
219
+ trx
220
+ );
221
+ } else {
222
+ const currentDate = /* @__PURE__ */ new Date();
223
+ roleMetadata.createdAt = currentDate.toUTCString();
224
+ roleMetadata.lastModified = currentDate.toUTCString();
225
+ await this.roleMetadataStorage.createRoleMetadata(roleMetadata, trx);
226
+ }
227
+ const ok = await this.enforcer.addGroupingPolicy(...policy);
228
+ if (!ok) {
229
+ throw new Error(`failed to create policy ${helper.policyToString(policy)}`);
230
+ }
231
+ if (!externalTrx) {
232
+ await trx.commit();
233
+ }
234
+ if (!currentMetadata) {
235
+ this.roleEventEmitter.emit("roleAdded", roleMetadata.roleEntityRef);
236
+ }
237
+ } catch (err) {
238
+ if (!externalTrx) {
239
+ await trx.rollback(err);
240
+ }
241
+ throw err;
182
242
  }
183
- throw err;
184
- }
243
+ })();
244
+ await this.execOperation(addGroupingPolicyOperation);
185
245
  }
186
246
  async addGroupingPolicies(policies, roleMetadata, externalTrx) {
187
- if (policies.length === 0) {
188
- return;
247
+ if (this.loadPolicyPromise) {
248
+ await this.loadPolicyPromise;
189
249
  }
190
- const trx = externalTrx ?? await this.knex.transaction();
191
- try {
192
- const currentRoleMetadata = await this.roleMetadataStorage.findRoleMetadata(
193
- roleMetadata.roleEntityRef,
194
- trx
195
- );
196
- if (currentRoleMetadata) {
197
- await this.roleMetadataStorage.updateRoleMetadata(
198
- helper.mergeRoleMetadata(currentRoleMetadata, roleMetadata),
250
+ const addGroupingPoliciesOperation = (async () => {
251
+ if (policies.length === 0) {
252
+ return;
253
+ }
254
+ const trx = externalTrx ?? await this.knex.transaction();
255
+ try {
256
+ const currentRoleMetadata = await this.roleMetadataStorage.findRoleMetadata(
199
257
  roleMetadata.roleEntityRef,
200
258
  trx
201
259
  );
202
- } else {
203
- const currentDate = /* @__PURE__ */ new Date();
204
- roleMetadata.createdAt = currentDate.toUTCString();
205
- roleMetadata.lastModified = currentDate.toUTCString();
206
- await this.roleMetadataStorage.createRoleMetadata(roleMetadata, trx);
207
- }
208
- const ok = await this.enforcer.addGroupingPolicies(policies);
209
- if (!ok) {
210
- throw new Error(
211
- `Failed to store policies ${helper.policiesToString(policies)}`
212
- );
213
- }
214
- if (!externalTrx) {
215
- await trx.commit();
216
- }
217
- if (!currentRoleMetadata) {
218
- this.roleEventEmitter.emit("roleAdded", roleMetadata.roleEntityRef);
219
- }
220
- } catch (err) {
221
- if (!externalTrx) {
222
- await trx.rollback(err);
260
+ if (currentRoleMetadata) {
261
+ await this.roleMetadataStorage.updateRoleMetadata(
262
+ helper.mergeRoleMetadata(currentRoleMetadata, roleMetadata),
263
+ roleMetadata.roleEntityRef,
264
+ trx
265
+ );
266
+ } else {
267
+ const currentDate = /* @__PURE__ */ new Date();
268
+ roleMetadata.createdAt = currentDate.toUTCString();
269
+ roleMetadata.lastModified = currentDate.toUTCString();
270
+ await this.roleMetadataStorage.createRoleMetadata(roleMetadata, trx);
271
+ }
272
+ const ok = await this.enforcer.addGroupingPolicies(policies);
273
+ if (!ok) {
274
+ throw new Error(
275
+ `Failed to store policies ${helper.policiesToString(policies)}`
276
+ );
277
+ }
278
+ if (!externalTrx) {
279
+ await trx.commit();
280
+ }
281
+ if (!currentRoleMetadata) {
282
+ this.roleEventEmitter.emit("roleAdded", roleMetadata.roleEntityRef);
283
+ }
284
+ } catch (err) {
285
+ if (!externalTrx) {
286
+ await trx.rollback(err);
287
+ }
288
+ throw err;
223
289
  }
224
- throw err;
225
- }
290
+ })();
291
+ await this.execOperation(addGroupingPoliciesOperation);
226
292
  }
227
293
  async updateGroupingPolicies(oldRole, newRole, newRoleMetadata) {
228
294
  const oldRoleName = oldRole.at(0)?.at(1);
@@ -255,110 +321,134 @@ class EnforcerDelegate {
255
321
  }
256
322
  }
257
323
  async removePolicy(policy, externalTrx) {
258
- const trx = externalTrx ?? await this.knex.transaction();
259
- try {
260
- const ok = await this.enforcer.removePolicy(...policy);
261
- if (!ok) {
262
- throw new Error(`fail to delete policy ${policy}`);
263
- }
264
- if (!externalTrx) {
265
- await trx.commit();
266
- }
267
- } catch (err) {
268
- if (!externalTrx) {
269
- await trx.rollback(err);
270
- }
271
- throw err;
324
+ if (this.loadPolicyPromise) {
325
+ await this.loadPolicyPromise;
272
326
  }
327
+ const removePolicyOperation = (async () => {
328
+ const trx = externalTrx ?? await this.knex.transaction();
329
+ try {
330
+ const ok = await this.enforcer.removePolicy(...policy);
331
+ if (!ok) {
332
+ throw new Error(`fail to delete policy ${policy}`);
333
+ }
334
+ if (!externalTrx) {
335
+ await trx.commit();
336
+ }
337
+ } catch (err) {
338
+ if (!externalTrx) {
339
+ await trx.rollback(err);
340
+ }
341
+ throw err;
342
+ }
343
+ })();
344
+ await this.execOperation(removePolicyOperation);
273
345
  }
274
346
  async removePolicies(policies, externalTrx) {
275
- const trx = externalTrx ?? await this.knex.transaction();
276
- try {
277
- const ok = await this.enforcer.removePolicies(policies);
278
- if (!ok) {
279
- throw new Error(
280
- `Failed to delete policies ${helper.policiesToString(policies)}`
281
- );
282
- }
283
- if (!externalTrx) {
284
- await trx.commit();
285
- }
286
- } catch (err) {
287
- if (!externalTrx) {
288
- await trx.rollback(err);
289
- }
290
- throw err;
347
+ if (this.loadPolicyPromise) {
348
+ await this.loadPolicyPromise;
291
349
  }
350
+ const removePoliciesOperation = (async () => {
351
+ const trx = externalTrx ?? await this.knex.transaction();
352
+ try {
353
+ const ok = await this.enforcer.removePolicies(policies);
354
+ if (!ok) {
355
+ throw new Error(
356
+ `Failed to delete policies ${helper.policiesToString(policies)}`
357
+ );
358
+ }
359
+ if (!externalTrx) {
360
+ await trx.commit();
361
+ }
362
+ } catch (err) {
363
+ if (!externalTrx) {
364
+ await trx.rollback(err);
365
+ }
366
+ throw err;
367
+ }
368
+ })();
369
+ await this.execOperation(removePoliciesOperation);
292
370
  }
293
371
  async removeGroupingPolicy(policy, roleMetadata, isUpdate, externalTrx) {
294
- const trx = externalTrx ?? await this.knex.transaction();
295
- const roleEntity = policy[1];
296
- try {
297
- const ok = await this.enforcer.removeGroupingPolicy(...policy);
298
- if (!ok) {
299
- throw new Error(`Failed to delete policy ${helper.policyToString(policy)}`);
300
- }
301
- if (!isUpdate) {
302
- const currentRoleMetadata = await this.roleMetadataStorage.findRoleMetadata(roleEntity, trx);
303
- const remainingGroupPolicies = await this.getFilteredGroupingPolicy(
304
- 1,
305
- roleEntity
306
- );
307
- if (currentRoleMetadata && remainingGroupPolicies.length === 0 && roleEntity !== adminCreation.ADMIN_ROLE_NAME) {
308
- await this.roleMetadataStorage.removeRoleMetadata(roleEntity, trx);
309
- } else if (currentRoleMetadata) {
310
- await this.roleMetadataStorage.updateRoleMetadata(
311
- helper.mergeRoleMetadata(currentRoleMetadata, roleMetadata),
312
- roleEntity,
313
- trx
372
+ if (this.loadPolicyPromise) {
373
+ await this.loadPolicyPromise;
374
+ }
375
+ const removeGroupingPolicyOperation = (async () => {
376
+ const trx = externalTrx ?? await this.knex.transaction();
377
+ const roleEntity = policy[1];
378
+ try {
379
+ const ok = await this.enforcer.removeGroupingPolicy(...policy);
380
+ if (!ok) {
381
+ throw new Error(`Failed to delete policy ${helper.policyToString(policy)}`);
382
+ }
383
+ if (!isUpdate) {
384
+ const currentRoleMetadata = await this.roleMetadataStorage.findRoleMetadata(roleEntity, trx);
385
+ const remainingGroupPolicies = await this.getFilteredGroupingPolicy(
386
+ 1,
387
+ roleEntity
314
388
  );
389
+ if (currentRoleMetadata && remainingGroupPolicies.length === 0 && roleEntity !== adminCreation.ADMIN_ROLE_NAME) {
390
+ await this.roleMetadataStorage.removeRoleMetadata(roleEntity, trx);
391
+ } else if (currentRoleMetadata) {
392
+ await this.roleMetadataStorage.updateRoleMetadata(
393
+ helper.mergeRoleMetadata(currentRoleMetadata, roleMetadata),
394
+ roleEntity,
395
+ trx
396
+ );
397
+ }
315
398
  }
399
+ if (!externalTrx) {
400
+ await trx.commit();
401
+ }
402
+ } catch (err) {
403
+ if (!externalTrx) {
404
+ await trx.rollback(err);
405
+ }
406
+ throw err;
316
407
  }
317
- if (!externalTrx) {
318
- await trx.commit();
319
- }
320
- } catch (err) {
321
- if (!externalTrx) {
322
- await trx.rollback(err);
323
- }
324
- throw err;
325
- }
408
+ })();
409
+ await this.execOperation(removeGroupingPolicyOperation);
326
410
  }
327
411
  async removeGroupingPolicies(policies, roleMetadata, isUpdate, externalTrx) {
328
- const trx = externalTrx ?? await this.knex.transaction();
329
- const roleEntity = roleMetadata.roleEntityRef;
330
- try {
331
- const ok = await this.enforcer.removeGroupingPolicies(policies);
332
- if (!ok) {
333
- throw new Error(
334
- `Failed to delete grouping policies: ${helper.policiesToString(policies)}`
335
- );
336
- }
337
- if (!isUpdate) {
338
- const currentRoleMetadata = await this.roleMetadataStorage.findRoleMetadata(roleEntity, trx);
339
- const remainingGroupPolicies = await this.getFilteredGroupingPolicy(
340
- 1,
341
- roleEntity
342
- );
343
- if (currentRoleMetadata && remainingGroupPolicies.length === 0 && roleEntity !== adminCreation.ADMIN_ROLE_NAME) {
344
- await this.roleMetadataStorage.removeRoleMetadata(roleEntity, trx);
345
- } else if (currentRoleMetadata) {
346
- await this.roleMetadataStorage.updateRoleMetadata(
347
- helper.mergeRoleMetadata(currentRoleMetadata, roleMetadata),
348
- roleEntity,
349
- trx
412
+ if (this.loadPolicyPromise) {
413
+ await this.loadPolicyPromise;
414
+ }
415
+ const removeGroupingPolicyOperation = (async () => {
416
+ const trx = externalTrx ?? await this.knex.transaction();
417
+ const roleEntity = roleMetadata.roleEntityRef;
418
+ try {
419
+ const ok = await this.enforcer.removeGroupingPolicies(policies);
420
+ if (!ok) {
421
+ throw new Error(
422
+ `Failed to delete grouping policies: ${helper.policiesToString(policies)}`
350
423
  );
351
424
  }
425
+ if (!isUpdate) {
426
+ const currentRoleMetadata = await this.roleMetadataStorage.findRoleMetadata(roleEntity, trx);
427
+ const remainingGroupPolicies = await this.getFilteredGroupingPolicy(
428
+ 1,
429
+ roleEntity
430
+ );
431
+ if (currentRoleMetadata && remainingGroupPolicies.length === 0 && roleEntity !== adminCreation.ADMIN_ROLE_NAME) {
432
+ await this.roleMetadataStorage.removeRoleMetadata(roleEntity, trx);
433
+ } else if (currentRoleMetadata) {
434
+ await this.roleMetadataStorage.updateRoleMetadata(
435
+ helper.mergeRoleMetadata(currentRoleMetadata, roleMetadata),
436
+ roleEntity,
437
+ trx
438
+ );
439
+ }
440
+ }
441
+ if (!externalTrx) {
442
+ await trx.commit();
443
+ }
444
+ } catch (err) {
445
+ if (!externalTrx) {
446
+ await trx.rollback(err);
447
+ }
448
+ throw err;
352
449
  }
353
- if (!externalTrx) {
354
- await trx.commit();
355
- }
356
- } catch (err) {
357
- if (!externalTrx) {
358
- await trx.rollback(err);
359
- }
360
- throw err;
361
- }
450
+ })();
451
+ await this.execOperation(removeGroupingPolicyOperation);
362
452
  }
363
453
  /**
364
454
  * enforce aims to enforce a particular permission policy based on the user that it receives.
@@ -374,7 +464,8 @@ class EnforcerDelegate {
374
464
  * The temporary enforcer has lazy loading of the permission policies enabled to reduce the amount
375
465
  * of time it takes to initialize the temporary enforcer.
376
466
  * The justification for lazy loading is because permission policies are already present in the
377
- * role manager / database and it will be filtered and loaded whenever `loadFilteredPolicy` is called.
467
+ * role manager / database and it will be filtered and loaded whenever `getFilteredPolicy` is called
468
+ * and permissions / roles are applied to the temp enforcer
378
469
  * @param entityRef The user to enforce
379
470
  * @param resourceType The resource type / name of the permission policy
380
471
  * @param action The action of the permission policy
@@ -383,31 +474,49 @@ class EnforcerDelegate {
383
474
  * @returns True if the user is allowed based on the particular permission
384
475
  */
385
476
  async enforce(entityRef, resourceType, action, roles) {
386
- const filter = [];
387
- if (roles.length > 0) {
388
- roles.forEach((role) => {
389
- filter.push({ ptype: "p", v0: role, v1: resourceType, v2: action });
390
- });
391
- } else {
392
- filter.push({ ptype: "p", v1: resourceType, v2: action });
477
+ if (this.loadPolicyPromise) {
478
+ await this.loadPolicyPromise;
393
479
  }
394
- const adapt = this.enforcer.getAdapter();
395
- const roleManager = this.enforcer.getRoleManager();
396
- const tempEnforcer = new casbin.Enforcer();
397
- await tempEnforcer.initWithModelAndAdapter(
398
- casbin.newModelFromString(permissionModel.MODEL),
399
- adapt,
400
- true
401
- );
402
- tempEnforcer.setRoleManager(roleManager);
403
- await tempEnforcer.loadFilteredPolicy(filter);
404
- return await tempEnforcer.enforce(entityRef, resourceType, action);
480
+ const evaluatePermissionOperation = (async () => {
481
+ const filter = [];
482
+ if (roles.length > 0) {
483
+ roles.forEach((role) => {
484
+ filter.push({ ptype: "p", v0: role, v1: resourceType, v2: action });
485
+ });
486
+ } else {
487
+ filter.push({ ptype: "p", v1: resourceType, v2: action });
488
+ }
489
+ const adapt = this.enforcer.getAdapter();
490
+ const roleManager = this.enforcer.getRoleManager();
491
+ const tempEnforcer = new casbin.Enforcer();
492
+ await tempEnforcer.initWithModelAndAdapter(
493
+ casbin.newModelFromString(permissionModel.MODEL),
494
+ adapt,
495
+ true
496
+ );
497
+ tempEnforcer.setRoleManager(roleManager);
498
+ await tempEnforcer.loadFilteredPolicy(filter);
499
+ return await tempEnforcer.enforce(entityRef, resourceType, action);
500
+ })();
501
+ return await this.execOperation(evaluatePermissionOperation);
405
502
  }
406
503
  async getImplicitPermissionsForUser(user) {
407
- return this.enforcer.getImplicitPermissionsForUser(user);
504
+ if (this.loadPolicyPromise) {
505
+ await this.loadPolicyPromise;
506
+ }
507
+ const getPermissionsForUserOperation = (async () => {
508
+ return this.enforcer.getImplicitPermissionsForUser(user);
509
+ })();
510
+ return await this.execOperation(getPermissionsForUserOperation);
408
511
  }
409
512
  async getAllRoles() {
410
- return this.enforcer.getAllRoles();
513
+ if (this.loadPolicyPromise) {
514
+ await this.loadPolicyPromise;
515
+ }
516
+ const getRolesOperation = (async () => {
517
+ return this.enforcer.getAllRoles();
518
+ })();
519
+ return await this.execOperation(getRolesOperation);
411
520
  }
412
521
  }
413
522