@backstage-community/plugin-rbac-backend 5.2.3

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.
Files changed (70) hide show
  1. package/CHANGELOG.md +671 -0
  2. package/README.md +220 -0
  3. package/config.d.ts +68 -0
  4. package/dist/admin-permissions/admin-creation.cjs.js +117 -0
  5. package/dist/admin-permissions/admin-creation.cjs.js.map +1 -0
  6. package/dist/audit-log/audit-logger.cjs.js +108 -0
  7. package/dist/audit-log/audit-logger.cjs.js.map +1 -0
  8. package/dist/audit-log/rest-errors-interceptor.cjs.js +100 -0
  9. package/dist/audit-log/rest-errors-interceptor.cjs.js.map +1 -0
  10. package/dist/conditional-aliases/alias-resolver.cjs.js +76 -0
  11. package/dist/conditional-aliases/alias-resolver.cjs.js.map +1 -0
  12. package/dist/database/casbin-adapter-factory.cjs.js +87 -0
  13. package/dist/database/casbin-adapter-factory.cjs.js.map +1 -0
  14. package/dist/database/conditional-storage.cjs.js +172 -0
  15. package/dist/database/conditional-storage.cjs.js.map +1 -0
  16. package/dist/database/migration.cjs.js +21 -0
  17. package/dist/database/migration.cjs.js.map +1 -0
  18. package/dist/database/role-metadata.cjs.js +89 -0
  19. package/dist/database/role-metadata.cjs.js.map +1 -0
  20. package/dist/file-permissions/csv-file-watcher.cjs.js +407 -0
  21. package/dist/file-permissions/csv-file-watcher.cjs.js.map +1 -0
  22. package/dist/file-permissions/file-watcher.cjs.js +46 -0
  23. package/dist/file-permissions/file-watcher.cjs.js.map +1 -0
  24. package/dist/file-permissions/yaml-conditional-file-watcher.cjs.js +208 -0
  25. package/dist/file-permissions/yaml-conditional-file-watcher.cjs.js.map +1 -0
  26. package/dist/helper.cjs.js +171 -0
  27. package/dist/helper.cjs.js.map +1 -0
  28. package/dist/index.cjs.js +14 -0
  29. package/dist/index.cjs.js.map +1 -0
  30. package/dist/index.d.ts +45 -0
  31. package/dist/plugin.cjs.js +79 -0
  32. package/dist/plugin.cjs.js.map +1 -0
  33. package/dist/policies/allow-all-policy.cjs.js +12 -0
  34. package/dist/policies/allow-all-policy.cjs.js.map +1 -0
  35. package/dist/policies/permission-policy.cjs.js +243 -0
  36. package/dist/policies/permission-policy.cjs.js.map +1 -0
  37. package/dist/providers/connect-providers.cjs.js +211 -0
  38. package/dist/providers/connect-providers.cjs.js.map +1 -0
  39. package/dist/role-manager/ancestor-search-memo.cjs.js +159 -0
  40. package/dist/role-manager/ancestor-search-memo.cjs.js.map +1 -0
  41. package/dist/role-manager/member-list.cjs.js +101 -0
  42. package/dist/role-manager/member-list.cjs.js.map +1 -0
  43. package/dist/role-manager/role-manager.cjs.js +281 -0
  44. package/dist/role-manager/role-manager.cjs.js.map +1 -0
  45. package/dist/service/enforcer-delegate.cjs.js +353 -0
  46. package/dist/service/enforcer-delegate.cjs.js.map +1 -0
  47. package/dist/service/permission-model.cjs.js +21 -0
  48. package/dist/service/permission-model.cjs.js.map +1 -0
  49. package/dist/service/plugin-endpoints.cjs.js +121 -0
  50. package/dist/service/plugin-endpoints.cjs.js.map +1 -0
  51. package/dist/service/policies-rest-api.cjs.js +949 -0
  52. package/dist/service/policies-rest-api.cjs.js.map +1 -0
  53. package/dist/service/policy-builder.cjs.js +134 -0
  54. package/dist/service/policy-builder.cjs.js.map +1 -0
  55. package/dist/service/router.cjs.js +24 -0
  56. package/dist/service/router.cjs.js.map +1 -0
  57. package/dist/validation/condition-validation.cjs.js +107 -0
  58. package/dist/validation/condition-validation.cjs.js.map +1 -0
  59. package/dist/validation/policies-validation.cjs.js +194 -0
  60. package/dist/validation/policies-validation.cjs.js.map +1 -0
  61. package/migrations/20231015161232_migrations.js +41 -0
  62. package/migrations/20231212224526_migrations.js +84 -0
  63. package/migrations/20231221113214_migrations.js +60 -0
  64. package/migrations/20240201144429_migrations.js +37 -0
  65. package/migrations/20240215154456_migrations.js +143 -0
  66. package/migrations/20240308134410_migrations.js +31 -0
  67. package/migrations/20240308134941_migrations.js +43 -0
  68. package/migrations/20240404111242_migrations.js +53 -0
  69. package/migrations/20240611092136_migrations.js +29 -0
  70. package/package.json +98 -0
@@ -0,0 +1,949 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+ var pluginPermissionBackend = require('@backstage/plugin-permission-backend');
5
+ var pluginPermissionCommon = require('@backstage/plugin-permission-common');
6
+ var pluginPermissionNode = require('@backstage/plugin-permission-node');
7
+ var lodash = require('lodash');
8
+ var pluginRbacCommon = require('@backstage-community/plugin-rbac-common');
9
+ var auditLogger = require('../audit-log/audit-logger.cjs.js');
10
+ var restErrorsInterceptor = require('../audit-log/rest-errors-interceptor.cjs.js');
11
+ var roleMetadata = require('../database/role-metadata.cjs.js');
12
+ var helper = require('../helper.cjs.js');
13
+ var conditionValidation = require('../validation/condition-validation.cjs.js');
14
+ var policiesValidation = require('../validation/policies-validation.cjs.js');
15
+
16
+ class PoliciesServer {
17
+ constructor(permissions, options, enforcer, conditionalStorage, pluginPermMetaData, roleMetadata, aLog, rbacProviders) {
18
+ this.permissions = permissions;
19
+ this.options = options;
20
+ this.enforcer = enforcer;
21
+ this.conditionalStorage = conditionalStorage;
22
+ this.pluginPermMetaData = pluginPermMetaData;
23
+ this.roleMetadata = roleMetadata;
24
+ this.aLog = aLog;
25
+ this.rbacProviders = rbacProviders;
26
+ }
27
+ async authorize(request, permission) {
28
+ const credentials = await this.options.httpAuth.credentials(request, {
29
+ allow: ["user", "service"]
30
+ });
31
+ if (this.options.auth.isPrincipal(credentials, "service") && permission !== pluginRbacCommon.policyEntityReadPermission) {
32
+ throw new errors.NotAllowedError(
33
+ `Only creadential principal with type 'user' permitted to modify permissions`
34
+ );
35
+ }
36
+ const decision = (await this.permissions.authorize(
37
+ [{ permission, resourceRef: permission.resourceType }],
38
+ { credentials }
39
+ ))[0];
40
+ return decision;
41
+ }
42
+ async serve() {
43
+ const router = await pluginPermissionBackend.createRouter(this.options);
44
+ const { httpAuth } = this.options;
45
+ if (!httpAuth) {
46
+ throw new errors.ServiceUnavailableError(
47
+ "httpAuth not found, ensure the correct configuration for the RBAC plugin"
48
+ );
49
+ }
50
+ const permissionsIntegrationRouter = pluginPermissionNode.createPermissionIntegrationRouter({
51
+ resourceType: pluginRbacCommon.RESOURCE_TYPE_POLICY_ENTITY,
52
+ permissions: pluginRbacCommon.policyEntityPermissions
53
+ });
54
+ router.use(permissionsIntegrationRouter);
55
+ const isPluginEnabled = this.options.config.getOptionalBoolean("permission.enabled");
56
+ if (!isPluginEnabled) {
57
+ return router;
58
+ }
59
+ router.get("/", async (request, response) => {
60
+ const decision = await this.authorize(
61
+ request,
62
+ pluginRbacCommon.policyEntityReadPermission
63
+ );
64
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
65
+ throw new errors.NotAllowedError();
66
+ }
67
+ response.send({ status: "Authorized" });
68
+ });
69
+ router.get("/policies", async (request, response) => {
70
+ const decision = await this.authorize(
71
+ request,
72
+ pluginRbacCommon.policyEntityReadPermission
73
+ );
74
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
75
+ throw new errors.NotAllowedError();
76
+ }
77
+ let policies;
78
+ if (this.isPolicyFilterEnabled(request)) {
79
+ const entityRef = this.getFirstQuery(request.query.entityRef);
80
+ const permission = this.getFirstQuery(request.query.permission);
81
+ const policy = this.getFirstQuery(request.query.policy);
82
+ const effect = this.getFirstQuery(request.query.effect);
83
+ const filter = [entityRef, permission, policy, effect];
84
+ policies = await this.enforcer.getFilteredPolicy(0, ...filter);
85
+ } else {
86
+ policies = await this.enforcer.getPolicy();
87
+ }
88
+ const body = await this.transformPolicyArray(...policies);
89
+ await this.aLog.auditLog({
90
+ message: `Return list permission policies`,
91
+ eventName: auditLogger.PermissionEvents.GET_POLICY,
92
+ stage: auditLogger.SEND_RESPONSE_STAGE,
93
+ status: "succeeded",
94
+ request,
95
+ response: { status: 200, body }
96
+ });
97
+ response.json(body);
98
+ });
99
+ router.get(
100
+ "/policies/:kind/:namespace/:name",
101
+ async (request, response) => {
102
+ const decision = await this.authorize(
103
+ request,
104
+ pluginRbacCommon.policyEntityReadPermission
105
+ );
106
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
107
+ throw new errors.NotAllowedError();
108
+ }
109
+ const entityRef = this.getEntityReference(request);
110
+ const policy = await this.enforcer.getFilteredPolicy(0, entityRef);
111
+ if (policy.length !== 0) {
112
+ const body = await this.transformPolicyArray(...policy);
113
+ await this.aLog.auditLog({
114
+ message: `Return permission policy`,
115
+ eventName: auditLogger.PermissionEvents.GET_POLICY,
116
+ stage: auditLogger.SEND_RESPONSE_STAGE,
117
+ status: "succeeded",
118
+ request,
119
+ response: { status: 200, body }
120
+ });
121
+ response.json(body);
122
+ } else {
123
+ throw new errors.NotFoundError();
124
+ }
125
+ }
126
+ );
127
+ router.delete(
128
+ "/policies/:kind/:namespace/:name",
129
+ async (request, response) => {
130
+ const decision = await this.authorize(
131
+ request,
132
+ pluginRbacCommon.policyEntityDeletePermission
133
+ );
134
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
135
+ throw new errors.NotAllowedError();
136
+ }
137
+ const entityRef = this.getEntityReference(request);
138
+ const policyRaw = request.body;
139
+ if (lodash.isEmpty(policyRaw)) {
140
+ throw new errors.InputError(`permission policy must be present`);
141
+ }
142
+ policyRaw.forEach((element) => {
143
+ element.entityReference = entityRef;
144
+ });
145
+ const processedPolicies = await this.processPolicies(policyRaw, true);
146
+ await this.enforcer.removePolicies(processedPolicies);
147
+ await this.aLog.auditLog({
148
+ message: `Deleted permission policies`,
149
+ eventName: auditLogger.PermissionEvents.DELETE_POLICY,
150
+ metadata: { policies: processedPolicies, source: "rest" },
151
+ stage: auditLogger.SEND_RESPONSE_STAGE,
152
+ status: "succeeded",
153
+ request,
154
+ response: { status: 204 }
155
+ });
156
+ response.status(204).end();
157
+ }
158
+ );
159
+ router.post("/policies", async (request, response) => {
160
+ const decision = await this.authorize(
161
+ request,
162
+ pluginRbacCommon.policyEntityCreatePermission
163
+ );
164
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
165
+ throw new errors.NotAllowedError();
166
+ }
167
+ const policyRaw = request.body;
168
+ if (lodash.isEmpty(policyRaw)) {
169
+ throw new errors.InputError(`permission policy must be present`);
170
+ }
171
+ const processedPolicies = await this.processPolicies(policyRaw);
172
+ const entityRef = processedPolicies[0][0];
173
+ const roleMetadata = await this.roleMetadata.findRoleMetadata(entityRef);
174
+ if (entityRef.startsWith("role:default") && !roleMetadata) {
175
+ throw new Error(`Corresponding role ${entityRef} was not found`);
176
+ }
177
+ await this.enforcer.addPolicies(processedPolicies);
178
+ await this.aLog.auditLog({
179
+ message: `Created permission policies`,
180
+ eventName: auditLogger.PermissionEvents.CREATE_POLICY,
181
+ metadata: { policies: processedPolicies, source: "rest" },
182
+ stage: auditLogger.SEND_RESPONSE_STAGE,
183
+ status: "succeeded",
184
+ request,
185
+ response: { status: 201 }
186
+ });
187
+ response.status(201).end();
188
+ });
189
+ router.put(
190
+ "/policies/:kind/:namespace/:name",
191
+ async (request, response) => {
192
+ const decision = await this.authorize(
193
+ request,
194
+ pluginRbacCommon.policyEntityUpdatePermission
195
+ );
196
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
197
+ throw new errors.NotAllowedError();
198
+ }
199
+ const entityRef = this.getEntityReference(request);
200
+ const oldPolicyRaw = request.body.oldPolicy;
201
+ if (lodash.isEmpty(oldPolicyRaw)) {
202
+ throw new errors.InputError(`'oldPolicy' object must be present`);
203
+ }
204
+ const newPolicyRaw = request.body.newPolicy;
205
+ if (lodash.isEmpty(newPolicyRaw)) {
206
+ throw new errors.InputError(`'newPolicy' object must be present`);
207
+ }
208
+ [...oldPolicyRaw, ...newPolicyRaw].forEach((element) => {
209
+ element.entityReference = entityRef;
210
+ });
211
+ const processedOldPolicy = await this.processPolicies(
212
+ oldPolicyRaw,
213
+ true,
214
+ "old policy"
215
+ );
216
+ oldPolicyRaw.sort(
217
+ (a, b) => a.permission === b.permission ? this.nameSort(a.policy, b.policy) : this.nameSort(a.permission, b.permission)
218
+ );
219
+ newPolicyRaw.sort(
220
+ (a, b) => a.permission === b.permission ? this.nameSort(a.policy, b.policy) : this.nameSort(a.permission, b.permission)
221
+ );
222
+ if (lodash.isEqual(oldPolicyRaw, newPolicyRaw) && !oldPolicyRaw.some(lodash.isEmpty)) {
223
+ response.status(204).end();
224
+ } else if (oldPolicyRaw.length > newPolicyRaw.length) {
225
+ throw new errors.InputError(
226
+ `'oldPolicy' object has more permission policies compared to 'newPolicy' object`
227
+ );
228
+ }
229
+ const processedNewPolicy = await this.processPolicies(
230
+ newPolicyRaw,
231
+ false,
232
+ "new policy"
233
+ );
234
+ const roleMetadata = await this.roleMetadata.findRoleMetadata(
235
+ entityRef
236
+ );
237
+ if (entityRef.startsWith("role:default") && !roleMetadata) {
238
+ throw new Error(`Corresponding role ${entityRef} was not found`);
239
+ }
240
+ await this.enforcer.updatePolicies(
241
+ processedOldPolicy,
242
+ processedNewPolicy
243
+ );
244
+ await this.aLog.auditLog({
245
+ message: `Updated permission policies`,
246
+ eventName: auditLogger.PermissionEvents.UPDATE_POLICY,
247
+ metadata: { policies: processedNewPolicy, source: "rest" },
248
+ stage: auditLogger.SEND_RESPONSE_STAGE,
249
+ status: "succeeded",
250
+ request,
251
+ response: { status: 200 }
252
+ });
253
+ response.status(200).end();
254
+ }
255
+ );
256
+ router.get("/roles", async (request, response) => {
257
+ const decision = await this.authorize(
258
+ request,
259
+ pluginRbacCommon.policyEntityReadPermission
260
+ );
261
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
262
+ throw new errors.NotAllowedError();
263
+ }
264
+ const roles = await this.enforcer.getGroupingPolicy();
265
+ const body = await this.transformRoleArray(...roles);
266
+ await this.aLog.auditLog({
267
+ message: `Return list roles`,
268
+ eventName: auditLogger.RoleEvents.GET_ROLE,
269
+ stage: auditLogger.SEND_RESPONSE_STAGE,
270
+ status: "succeeded",
271
+ request,
272
+ response: { status: 200, body }
273
+ });
274
+ response.json(body);
275
+ });
276
+ router.get("/roles/:kind/:namespace/:name", async (request, response) => {
277
+ const decision = await this.authorize(
278
+ request,
279
+ pluginRbacCommon.policyEntityReadPermission
280
+ );
281
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
282
+ throw new errors.NotAllowedError();
283
+ }
284
+ const roleEntityRef = this.getEntityReference(request, true);
285
+ const role = await this.enforcer.getFilteredGroupingPolicy(
286
+ 1,
287
+ roleEntityRef
288
+ );
289
+ if (role.length !== 0) {
290
+ const body = await this.transformRoleArray(...role);
291
+ await this.aLog.auditLog({
292
+ message: `Return ${body[0].name}`,
293
+ eventName: auditLogger.RoleEvents.GET_ROLE,
294
+ stage: auditLogger.SEND_RESPONSE_STAGE,
295
+ status: "succeeded",
296
+ request,
297
+ response: { status: 200, body }
298
+ });
299
+ response.json(body);
300
+ } else {
301
+ throw new errors.NotFoundError();
302
+ }
303
+ });
304
+ router.post("/roles", async (request, response) => {
305
+ const uniqueItems = /* @__PURE__ */ new Set();
306
+ const decision = await this.authorize(
307
+ request,
308
+ pluginRbacCommon.policyEntityCreatePermission
309
+ );
310
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
311
+ throw new errors.NotAllowedError();
312
+ }
313
+ const roleRaw = request.body;
314
+ let err = policiesValidation.validateRole(roleRaw);
315
+ if (err) {
316
+ throw new errors.InputError(
317
+ // 400
318
+ `Invalid role definition. Cause: ${err.message}`
319
+ );
320
+ }
321
+ const rMetadata = await this.roleMetadata.findRoleMetadata(roleRaw.name);
322
+ err = await policiesValidation.validateSource("rest", rMetadata);
323
+ if (err) {
324
+ throw new errors.NotAllowedError(`Unable to add role: ${err.message}`);
325
+ }
326
+ const roles = this.transformRoleToArray(roleRaw);
327
+ for (const role of roles) {
328
+ if (await this.enforcer.hasGroupingPolicy(...role)) {
329
+ throw new errors.ConflictError();
330
+ }
331
+ const roleString = JSON.stringify(role);
332
+ if (uniqueItems.has(roleString)) {
333
+ throw new errors.ConflictError(
334
+ `Duplicate role members found; ${role.at(0)}, ${role.at(
335
+ 1
336
+ )} is a duplicate`
337
+ );
338
+ } else {
339
+ uniqueItems.add(roleString);
340
+ }
341
+ }
342
+ const credentials = await httpAuth.credentials(request, {
343
+ allow: ["user"]
344
+ });
345
+ const modifiedBy = credentials.principal.userEntityRef;
346
+ const metadata = {
347
+ roleEntityRef: roleRaw.name,
348
+ source: "rest",
349
+ description: roleRaw.metadata?.description ?? "",
350
+ author: modifiedBy,
351
+ modifiedBy
352
+ };
353
+ await this.enforcer.addGroupingPolicies(roles, metadata);
354
+ await this.aLog.auditLog({
355
+ message: `Created ${metadata.roleEntityRef}`,
356
+ eventName: auditLogger.RoleEvents.CREATE_ROLE,
357
+ metadata: {
358
+ ...metadata,
359
+ members: roles.map((gp) => gp[0])
360
+ },
361
+ stage: auditLogger.SEND_RESPONSE_STAGE,
362
+ status: "succeeded",
363
+ request,
364
+ response: { status: 201 }
365
+ });
366
+ response.status(201).end();
367
+ });
368
+ router.put("/roles/:kind/:namespace/:name", async (request, response) => {
369
+ const uniqueItems = /* @__PURE__ */ new Set();
370
+ const decision = await this.authorize(
371
+ request,
372
+ pluginRbacCommon.policyEntityUpdatePermission
373
+ );
374
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
375
+ throw new errors.NotAllowedError();
376
+ }
377
+ const roleEntityRef = this.getEntityReference(request, true);
378
+ const oldRoleRaw = request.body.oldRole;
379
+ if (!oldRoleRaw) {
380
+ throw new errors.InputError(`'oldRole' object must be present`);
381
+ }
382
+ const newRoleRaw = request.body.newRole;
383
+ if (!newRoleRaw) {
384
+ throw new errors.InputError(`'newRole' object must be present`);
385
+ }
386
+ oldRoleRaw.name = roleEntityRef;
387
+ let err = policiesValidation.validateRole(oldRoleRaw);
388
+ if (err) {
389
+ throw new errors.InputError(
390
+ // 400
391
+ `Invalid old role object. Cause: ${err.message}`
392
+ );
393
+ }
394
+ err = policiesValidation.validateRole(newRoleRaw);
395
+ if (err) {
396
+ throw new errors.InputError(
397
+ // 400
398
+ `Invalid new role object. Cause: ${err.message}`
399
+ );
400
+ }
401
+ const oldRole = this.transformRoleToArray(oldRoleRaw);
402
+ const newRole = this.transformRoleToArray(newRoleRaw);
403
+ const credentials = await httpAuth.credentials(request, {
404
+ allow: ["user"]
405
+ });
406
+ const newMetadata = {
407
+ ...newRoleRaw.metadata,
408
+ source: newRoleRaw.metadata?.source ?? "rest",
409
+ roleEntityRef: newRoleRaw.name,
410
+ modifiedBy: credentials.principal.userEntityRef
411
+ };
412
+ const oldMetadata = await this.roleMetadata.findRoleMetadata(
413
+ roleEntityRef
414
+ );
415
+ if (!oldMetadata) {
416
+ throw new errors.NotFoundError(`Unable to find metadata for ${roleEntityRef}`);
417
+ }
418
+ err = await policiesValidation.validateSource("rest", oldMetadata);
419
+ if (err) {
420
+ throw new errors.NotAllowedError(`Unable to edit role: ${err.message}`);
421
+ }
422
+ if (lodash.isEqual(oldRole, newRole) && helper.deepSortedEqual(oldMetadata, newMetadata, [
423
+ "author",
424
+ "modifiedBy",
425
+ "createdAt",
426
+ "lastModified"
427
+ ])) {
428
+ response.status(204).end();
429
+ return;
430
+ }
431
+ for (const role of newRole) {
432
+ const hasRole = oldRole.some((element) => {
433
+ return lodash.isEqual(element, role);
434
+ });
435
+ if (await this.enforcer.hasGroupingPolicy(...role)) {
436
+ if (!hasRole) {
437
+ throw new errors.ConflictError();
438
+ }
439
+ }
440
+ const roleString = JSON.stringify(role);
441
+ if (uniqueItems.has(roleString)) {
442
+ throw new errors.ConflictError(
443
+ `Duplicate role members found; ${role.at(0)}, ${role.at(
444
+ 1
445
+ )} is a duplicate`
446
+ );
447
+ } else {
448
+ uniqueItems.add(roleString);
449
+ }
450
+ }
451
+ uniqueItems.clear();
452
+ for (const role of oldRole) {
453
+ if (!await this.enforcer.hasGroupingPolicy(...role)) {
454
+ throw new errors.NotFoundError(
455
+ `Member reference: ${role[0]} was not found for role ${roleEntityRef}`
456
+ );
457
+ }
458
+ const roleString = JSON.stringify(role);
459
+ if (uniqueItems.has(roleString)) {
460
+ throw new errors.ConflictError(
461
+ `Duplicate role members found; ${role.at(0)}, ${role.at(
462
+ 1
463
+ )} is a duplicate`
464
+ );
465
+ } else {
466
+ uniqueItems.add(roleString);
467
+ }
468
+ }
469
+ await this.enforcer.updateGroupingPolicies(oldRole, newRole, newMetadata);
470
+ let message = `Updated ${oldMetadata.roleEntityRef}.`;
471
+ if (newMetadata.roleEntityRef !== oldMetadata.roleEntityRef) {
472
+ message = `${message}. Role entity reference renamed to ${newMetadata.roleEntityRef}`;
473
+ }
474
+ await this.aLog.auditLog({
475
+ message,
476
+ eventName: auditLogger.RoleEvents.UPDATE_ROLE,
477
+ metadata: {
478
+ ...newMetadata,
479
+ members: newRole.map((gp) => gp[0])
480
+ },
481
+ stage: auditLogger.SEND_RESPONSE_STAGE,
482
+ status: "succeeded",
483
+ request,
484
+ response: { status: 200 }
485
+ });
486
+ response.status(200).end();
487
+ });
488
+ router.delete(
489
+ "/roles/:kind/:namespace/:name",
490
+ async (request, response) => {
491
+ const decision = await this.authorize(
492
+ request,
493
+ pluginRbacCommon.policyEntityDeletePermission
494
+ );
495
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
496
+ throw new errors.NotAllowedError();
497
+ }
498
+ const roleEntityRef = this.getEntityReference(request, true);
499
+ let roleMembers = [];
500
+ if (request.query.memberReferences) {
501
+ const memberReference = this.getFirstQuery(
502
+ request.query.memberReferences
503
+ );
504
+ const gp = await this.enforcer.getFilteredGroupingPolicy(
505
+ 0,
506
+ memberReference,
507
+ roleEntityRef
508
+ );
509
+ if (gp.length > 0) {
510
+ roleMembers.push(gp[0]);
511
+ } else {
512
+ throw new errors.NotFoundError(
513
+ `role member '${memberReference}' was not found`
514
+ );
515
+ }
516
+ } else {
517
+ roleMembers = await this.enforcer.getFilteredGroupingPolicy(
518
+ 1,
519
+ roleEntityRef
520
+ );
521
+ }
522
+ for (const role of roleMembers) {
523
+ if (!await this.enforcer.hasGroupingPolicy(...role)) {
524
+ throw new errors.NotFoundError(`role member '${role[0]}' was not found`);
525
+ }
526
+ }
527
+ const currentMetadata = await this.roleMetadata.findRoleMetadata(
528
+ roleEntityRef
529
+ );
530
+ const err = await policiesValidation.validateSource("rest", currentMetadata);
531
+ if (err) {
532
+ throw new errors.NotAllowedError(`Unable to delete role: ${err.message}`);
533
+ }
534
+ const credentials = await httpAuth.credentials(request, {
535
+ allow: ["user"]
536
+ });
537
+ const metadata = {
538
+ roleEntityRef,
539
+ source: "rest",
540
+ modifiedBy: credentials.principal.userEntityRef
541
+ };
542
+ await this.enforcer.removeGroupingPolicies(
543
+ roleMembers,
544
+ metadata,
545
+ false
546
+ );
547
+ await this.aLog.auditLog({
548
+ message: `Deleted ${metadata.roleEntityRef}`,
549
+ eventName: auditLogger.RoleEvents.DELETE_ROLE,
550
+ metadata: {
551
+ ...metadata,
552
+ members: roleMembers.map((gp) => gp[0])
553
+ },
554
+ stage: auditLogger.SEND_RESPONSE_STAGE,
555
+ status: "succeeded",
556
+ request,
557
+ response: { status: 204 }
558
+ });
559
+ response.status(204).end();
560
+ }
561
+ );
562
+ router.get("/plugins/policies", async (request, response) => {
563
+ const decision = await this.authorize(
564
+ request,
565
+ pluginRbacCommon.policyEntityReadPermission
566
+ );
567
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
568
+ throw new errors.NotAllowedError();
569
+ }
570
+ const body = await this.pluginPermMetaData.getPluginPolicies(
571
+ this.options.auth
572
+ );
573
+ await this.aLog.auditLog({
574
+ message: `Return list plugin policies`,
575
+ eventName: auditLogger.ListPluginPoliciesEvents.GET_PLUGINS_POLICIES,
576
+ stage: auditLogger.SEND_RESPONSE_STAGE,
577
+ status: "succeeded",
578
+ request,
579
+ response: { status: 200, body }
580
+ });
581
+ response.json(body);
582
+ });
583
+ router.get("/plugins/condition-rules", async (request, response) => {
584
+ const decision = await this.authorize(
585
+ request,
586
+ pluginRbacCommon.policyEntityReadPermission
587
+ );
588
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
589
+ throw new errors.NotAllowedError();
590
+ }
591
+ const body = await this.pluginPermMetaData.getPluginConditionRules(
592
+ this.options.auth
593
+ );
594
+ await this.aLog.auditLog({
595
+ message: `Return list conditional rules and schemas`,
596
+ eventName: auditLogger.ListConditionEvents.GET_CONDITION_RULES,
597
+ stage: auditLogger.SEND_RESPONSE_STAGE,
598
+ status: "succeeded",
599
+ request,
600
+ response: { status: 200, body }
601
+ });
602
+ response.json(body);
603
+ });
604
+ router.get("/roles/conditions", async (request, response) => {
605
+ const decision = await this.authorize(
606
+ request,
607
+ pluginRbacCommon.policyEntityReadPermission
608
+ );
609
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
610
+ throw new errors.NotAllowedError();
611
+ }
612
+ const conditions = await this.conditionalStorage.filterConditions(
613
+ this.getFirstQuery(request.query.roleEntityRef),
614
+ this.getFirstQuery(request.query.pluginId),
615
+ this.getFirstQuery(request.query.resourceType),
616
+ this.getActionQueries(request.query.actions)
617
+ );
618
+ const body = conditions.map((condition) => {
619
+ return {
620
+ ...condition,
621
+ permissionMapping: condition.permissionMapping.map((pm) => pm.action)
622
+ };
623
+ });
624
+ await this.aLog.auditLog({
625
+ message: `Return list conditional permission policies`,
626
+ eventName: auditLogger.ConditionEvents.GET_CONDITION,
627
+ stage: auditLogger.SEND_RESPONSE_STAGE,
628
+ status: "succeeded",
629
+ request,
630
+ response: { status: 200, body }
631
+ });
632
+ response.json(body);
633
+ });
634
+ router.post("/roles/conditions", async (request, response) => {
635
+ const decision = await this.authorize(
636
+ request,
637
+ pluginRbacCommon.policyEntityCreatePermission
638
+ );
639
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
640
+ throw new errors.NotAllowedError();
641
+ }
642
+ const roleConditionPolicy = request.body;
643
+ conditionValidation.validateRoleCondition(roleConditionPolicy);
644
+ const conditionToCreate = await helper.processConditionMapping(
645
+ roleConditionPolicy,
646
+ this.pluginPermMetaData,
647
+ this.options.auth
648
+ );
649
+ const id = await this.conditionalStorage.createCondition(
650
+ conditionToCreate
651
+ );
652
+ const body = { id };
653
+ await this.aLog.auditLog({
654
+ message: `Created conditional permission policy`,
655
+ eventName: auditLogger.ConditionEvents.CREATE_CONDITION,
656
+ metadata: { condition: roleConditionPolicy },
657
+ stage: auditLogger.SEND_RESPONSE_STAGE,
658
+ status: "succeeded",
659
+ request,
660
+ response: { status: 201, body }
661
+ });
662
+ response.status(201).json(body);
663
+ });
664
+ router.get("/roles/conditions/:id", async (request, response) => {
665
+ const decision = await this.authorize(
666
+ request,
667
+ pluginRbacCommon.policyEntityReadPermission
668
+ );
669
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
670
+ throw new errors.NotAllowedError();
671
+ }
672
+ const id = parseInt(request.params.id, 10);
673
+ if (isNaN(id)) {
674
+ throw new errors.InputError("Id is not a valid number.");
675
+ }
676
+ const condition = await this.conditionalStorage.getCondition(id);
677
+ if (!condition) {
678
+ throw new errors.NotFoundError();
679
+ }
680
+ const body = {
681
+ ...condition,
682
+ permissionMapping: condition.permissionMapping.map((pm) => pm.action)
683
+ };
684
+ await this.aLog.auditLog({
685
+ message: `Return conditional permission policy by id`,
686
+ eventName: auditLogger.ConditionEvents.GET_CONDITION,
687
+ stage: auditLogger.SEND_RESPONSE_STAGE,
688
+ status: "succeeded",
689
+ request,
690
+ response: { status: 200, body }
691
+ });
692
+ response.json(body);
693
+ });
694
+ router.delete("/roles/conditions/:id", async (request, response) => {
695
+ const decision = await this.authorize(
696
+ request,
697
+ pluginRbacCommon.policyEntityDeletePermission
698
+ );
699
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
700
+ throw new errors.NotAllowedError();
701
+ }
702
+ const id = parseInt(request.params.id, 10);
703
+ if (isNaN(id)) {
704
+ throw new errors.InputError("Id is not a valid number.");
705
+ }
706
+ const condition = await this.conditionalStorage.getCondition(id);
707
+ if (!condition) {
708
+ throw new errors.NotFoundError(`Condition with id ${id} was not found`);
709
+ }
710
+ const conditionToDelete = {
711
+ ...condition,
712
+ permissionMapping: condition.permissionMapping.map((pm) => pm.action)
713
+ };
714
+ await this.conditionalStorage.deleteCondition(id);
715
+ await this.aLog.auditLog({
716
+ message: `Deleted conditional permission policy`,
717
+ eventName: auditLogger.ConditionEvents.DELETE_CONDITION,
718
+ metadata: { condition: conditionToDelete },
719
+ stage: auditLogger.SEND_RESPONSE_STAGE,
720
+ status: "succeeded",
721
+ request,
722
+ response: { status: 204 }
723
+ });
724
+ response.status(204).end();
725
+ });
726
+ router.put("/roles/conditions/:id", async (request, response) => {
727
+ const decision = await this.authorize(
728
+ request,
729
+ pluginRbacCommon.policyEntityUpdatePermission
730
+ );
731
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
732
+ throw new errors.NotAllowedError();
733
+ }
734
+ const id = parseInt(request.params.id, 10);
735
+ if (isNaN(id)) {
736
+ throw new errors.InputError("Id is not a valid number.");
737
+ }
738
+ const roleConditionPolicy = request.body;
739
+ conditionValidation.validateRoleCondition(roleConditionPolicy);
740
+ const conditionToUpdate = await helper.processConditionMapping(
741
+ roleConditionPolicy,
742
+ this.pluginPermMetaData,
743
+ this.options.auth
744
+ );
745
+ await this.conditionalStorage.updateCondition(id, conditionToUpdate);
746
+ await this.aLog.auditLog({
747
+ message: `Updated conditional permission policy`,
748
+ eventName: auditLogger.ConditionEvents.UPDATE_CONDITION,
749
+ metadata: { condition: roleConditionPolicy },
750
+ stage: auditLogger.SEND_RESPONSE_STAGE,
751
+ status: "succeeded",
752
+ request,
753
+ response: { status: 200 }
754
+ });
755
+ response.status(200).end();
756
+ });
757
+ router.post("/refresh/:id", async (request, response) => {
758
+ const decision = await this.authorize(
759
+ request,
760
+ pluginRbacCommon.policyEntityCreatePermission
761
+ );
762
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
763
+ throw new errors.NotAllowedError();
764
+ }
765
+ if (!this.rbacProviders) {
766
+ throw new errors.NotFoundError(`No RBAC providers were found`);
767
+ }
768
+ const idProvider = this.rbacProviders.find((provider) => {
769
+ const id = provider.getProviderName();
770
+ return id === request.params.id;
771
+ });
772
+ if (!idProvider) {
773
+ throw new errors.NotFoundError(
774
+ `The RBAC provider ${request.params.id} was not found`
775
+ );
776
+ }
777
+ await idProvider.refresh();
778
+ response.status(200).end();
779
+ });
780
+ router.use(restErrorsInterceptor.auditError(this.aLog));
781
+ return router;
782
+ }
783
+ getEntityReference(request, role) {
784
+ const kind = request.params.kind;
785
+ const namespace = request.params.namespace;
786
+ const name = request.params.name;
787
+ const entityRef = `${kind}:${namespace}/${name}`;
788
+ const err = policiesValidation.validateEntityReference(entityRef, role);
789
+ if (err) {
790
+ throw new errors.InputError(err.message);
791
+ }
792
+ return entityRef;
793
+ }
794
+ async transformPolicyArray(...policies) {
795
+ const roleToSourceMap = await helper.buildRoleSourceMap(
796
+ policies,
797
+ this.roleMetadata
798
+ );
799
+ const roleBasedPolices = [];
800
+ for (const p of policies) {
801
+ const [entityReference, permission, policy, effect] = p;
802
+ roleBasedPolices.push({
803
+ entityReference,
804
+ permission,
805
+ policy,
806
+ effect,
807
+ metadata: { source: roleToSourceMap.get(entityReference) }
808
+ });
809
+ }
810
+ return roleBasedPolices;
811
+ }
812
+ async transformRoleArray(...roles) {
813
+ const combinedRoles = {};
814
+ roles.forEach(([value, role]) => {
815
+ if (combinedRoles.hasOwnProperty(role)) {
816
+ combinedRoles[role].push(value);
817
+ } else {
818
+ combinedRoles[role] = [value];
819
+ }
820
+ });
821
+ const result = await Promise.all(
822
+ Object.entries(combinedRoles).map(async ([role, value]) => {
823
+ const metadataDao = await this.roleMetadata.findRoleMetadata(role);
824
+ const metadata = metadataDao ? roleMetadata.daoToMetadata(metadataDao) : void 0;
825
+ return Promise.resolve({
826
+ memberReferences: value,
827
+ name: role,
828
+ metadata
829
+ });
830
+ })
831
+ );
832
+ return result;
833
+ }
834
+ transformPolicyToArray(policy) {
835
+ return [
836
+ policy.entityReference,
837
+ policy.permission,
838
+ policy.policy,
839
+ policy.effect
840
+ ];
841
+ }
842
+ transformRoleToArray(role) {
843
+ const roles = [];
844
+ for (const entity of role.memberReferences) {
845
+ roles.push([entity, role.name]);
846
+ }
847
+ return roles;
848
+ }
849
+ getActionQueries(queryValue) {
850
+ if (!queryValue) {
851
+ return void 0;
852
+ }
853
+ if (Array.isArray(queryValue)) {
854
+ const permissionNames = [];
855
+ for (const permissionQuery of queryValue) {
856
+ if (typeof permissionQuery === "string" && helper.isPermissionAction(permissionQuery)) {
857
+ permissionNames.push(permissionQuery);
858
+ } else {
859
+ throw new errors.InputError(
860
+ `Invalid permission action query value: ${permissionQuery}. Permission name should be string.`
861
+ );
862
+ }
863
+ }
864
+ return permissionNames;
865
+ }
866
+ if (typeof queryValue === "string" && helper.isPermissionAction(queryValue)) {
867
+ return [queryValue];
868
+ }
869
+ throw new errors.InputError(
870
+ `Invalid permission action query value: ${queryValue}. Permission name should be string.`
871
+ );
872
+ }
873
+ getFirstQuery(queryValue) {
874
+ if (!queryValue) {
875
+ return "";
876
+ }
877
+ if (Array.isArray(queryValue)) {
878
+ if (typeof queryValue[0] === "string") {
879
+ return queryValue[0].toString();
880
+ }
881
+ throw new errors.InputError(`This api doesn't support nested query`);
882
+ }
883
+ if (typeof queryValue === "string") {
884
+ return queryValue;
885
+ }
886
+ throw new errors.InputError(`This api doesn't support nested query`);
887
+ }
888
+ isPolicyFilterEnabled(request) {
889
+ return !!request.query.entityRef || !!request.query.permission || !!request.query.policy || !!request.query.effect;
890
+ }
891
+ async processPolicies(policyArray, isOld, errorMessage) {
892
+ const policies = [];
893
+ const uniqueItems = /* @__PURE__ */ new Set();
894
+ for (const policy of policyArray) {
895
+ let err = policiesValidation.validatePolicy(policy);
896
+ if (err) {
897
+ throw new errors.InputError(
898
+ `Invalid ${errorMessage ?? "policy"} definition. Cause: ${err.message}`
899
+ );
900
+ }
901
+ const metadata = await this.roleMetadata.findRoleMetadata(
902
+ policy.entityReference
903
+ );
904
+ let action = errorMessage ? "edit" : "delete";
905
+ action = isOld ? action : "add";
906
+ err = await policiesValidation.validateSource("rest", metadata);
907
+ if (err) {
908
+ throw new errors.NotAllowedError(
909
+ `Unable to ${action} policy ${policy.entityReference},${policy.permission},${policy.policy},${policy.effect}: ${err.message}`
910
+ );
911
+ }
912
+ const transformedPolicy = this.transformPolicyToArray(policy);
913
+ if (isOld && !await this.enforcer.hasPolicy(...transformedPolicy)) {
914
+ throw new errors.NotFoundError(
915
+ `Policy '${helper.policyToString(transformedPolicy)}' not found`
916
+ );
917
+ }
918
+ if (!isOld && await this.enforcer.hasPolicy(...transformedPolicy)) {
919
+ throw new errors.ConflictError(
920
+ `Policy '${helper.policyToString(
921
+ transformedPolicy
922
+ )}' has been already stored`
923
+ );
924
+ }
925
+ const rowString = JSON.stringify(transformedPolicy);
926
+ if (uniqueItems.has(rowString)) {
927
+ throw new errors.ConflictError(
928
+ `Duplicate polices found; ${policy.entityReference}, ${policy.permission}, ${policy.policy}, ${policy.effect} is a duplicate`
929
+ );
930
+ } else {
931
+ uniqueItems.add(rowString);
932
+ policies.push(transformedPolicy);
933
+ }
934
+ }
935
+ return policies;
936
+ }
937
+ nameSort(nameA, nameB) {
938
+ if (nameA.toLocaleUpperCase("en-US") < nameB.toLocaleUpperCase("en-US")) {
939
+ return -1;
940
+ }
941
+ if (nameA.toLocaleUpperCase("en-US") > nameB.toLocaleUpperCase("en-US")) {
942
+ return 1;
943
+ }
944
+ return 0;
945
+ }
946
+ }
947
+
948
+ exports.PoliciesServer = PoliciesServer;
949
+ //# sourceMappingURL=policies-rest-api.cjs.js.map