@backstage-community/plugin-rbac-backend 5.6.1 → 6.0.1

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 (31) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/admin-permissions/admin-creation.cjs.js +41 -27
  3. package/dist/admin-permissions/admin-creation.cjs.js.map +1 -1
  4. package/dist/auditor/auditor.cjs.js +65 -0
  5. package/dist/auditor/auditor.cjs.js.map +1 -0
  6. package/dist/auditor/rest-interceptor.cjs.js +130 -0
  7. package/dist/auditor/rest-interceptor.cjs.js.map +1 -0
  8. package/dist/file-permissions/csv-file-watcher.cjs.js +90 -92
  9. package/dist/file-permissions/csv-file-watcher.cjs.js.map +1 -1
  10. package/dist/file-permissions/yaml-conditional-file-watcher.cjs.js +40 -51
  11. package/dist/file-permissions/yaml-conditional-file-watcher.cjs.js.map +1 -1
  12. package/dist/helper.cjs.js +22 -19
  13. package/dist/helper.cjs.js.map +1 -1
  14. package/dist/index.d.ts +2 -1
  15. package/dist/plugin.cjs.js +3 -0
  16. package/dist/plugin.cjs.js.map +1 -1
  17. package/dist/policies/permission-policy.cjs.js +32 -70
  18. package/dist/policies/permission-policy.cjs.js.map +1 -1
  19. package/dist/providers/connect-providers.cjs.js +75 -68
  20. package/dist/providers/connect-providers.cjs.js.map +1 -1
  21. package/dist/service/enforcer-delegate.cjs.js +8 -10
  22. package/dist/service/enforcer-delegate.cjs.js.map +1 -1
  23. package/dist/service/policies-rest-api.cjs.js +449 -519
  24. package/dist/service/policies-rest-api.cjs.js.map +1 -1
  25. package/dist/service/policy-builder.cjs.js +4 -10
  26. package/dist/service/policy-builder.cjs.js.map +1 -1
  27. package/package.json +3 -5
  28. package/dist/audit-log/audit-logger.cjs.js +0 -114
  29. package/dist/audit-log/audit-logger.cjs.js.map +0 -1
  30. package/dist/audit-log/rest-errors-interceptor.cjs.js +0 -100
  31. package/dist/audit-log/rest-errors-interceptor.cjs.js.map +0 -1
@@ -6,22 +6,21 @@ var pluginPermissionCommon = require('@backstage/plugin-permission-common');
6
6
  var pluginPermissionNode = require('@backstage/plugin-permission-node');
7
7
  var lodash = require('lodash');
8
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');
9
+ var restInterceptor = require('../auditor/rest-interceptor.cjs.js');
11
10
  var roleMetadata = require('../database/role-metadata.cjs.js');
12
11
  var helper = require('../helper.cjs.js');
13
12
  var conditionValidation = require('../validation/condition-validation.cjs.js');
14
13
  var policiesValidation = require('../validation/policies-validation.cjs.js');
15
14
 
16
15
  class PoliciesServer {
17
- constructor(permissions, options, enforcer, conditionalStorage, pluginPermMetaData, roleMetadata, aLog, rbacProviders) {
16
+ constructor(permissions, options, enforcer, conditionalStorage, pluginPermMetaData, roleMetadata, auditor, rbacProviders) {
18
17
  this.permissions = permissions;
19
18
  this.options = options;
20
19
  this.enforcer = enforcer;
21
20
  this.conditionalStorage = conditionalStorage;
22
21
  this.pluginPermMetaData = pluginPermMetaData;
23
22
  this.roleMetadata = roleMetadata;
24
- this.aLog = aLog;
23
+ this.auditor = auditor;
25
24
  this.rbacProviders = rbacProviders;
26
25
  }
27
26
  async authorize(request, permission) {
@@ -66,38 +65,35 @@ class PoliciesServer {
66
65
  }
67
66
  response.send({ status: "Authorized" });
68
67
  });
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();
68
+ router.get(
69
+ "/policies",
70
+ restInterceptor.logAuditorEvent(this.auditor),
71
+ async (request, response) => {
72
+ const decision = await this.authorize(
73
+ request,
74
+ pluginRbacCommon.policyEntityReadPermission
75
+ );
76
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
77
+ throw new errors.NotAllowedError();
78
+ }
79
+ let policies;
80
+ if (this.isPolicyFilterEnabled(request)) {
81
+ const entityRef = this.getFirstQuery(request.query.entityRef);
82
+ const permission = this.getFirstQuery(request.query.permission);
83
+ const policy = this.getFirstQuery(request.query.policy);
84
+ const effect = this.getFirstQuery(request.query.effect);
85
+ const filter = [entityRef, permission, policy, effect];
86
+ policies = await this.enforcer.getFilteredPolicy(0, ...filter);
87
+ } else {
88
+ policies = await this.enforcer.getPolicy();
89
+ }
90
+ const body = await this.transformPolicyArray(...policies);
91
+ response.json(body);
87
92
  }
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
- });
93
+ );
99
94
  router.get(
100
95
  "/policies/:kind/:namespace/:name",
96
+ restInterceptor.logAuditorEvent(this.auditor),
101
97
  async (request, response) => {
102
98
  const decision = await this.authorize(
103
99
  request,
@@ -110,14 +106,6 @@ class PoliciesServer {
110
106
  const policy = await this.enforcer.getFilteredPolicy(0, entityRef);
111
107
  if (policy.length !== 0) {
112
108
  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
109
  response.json(body);
122
110
  } else {
123
111
  throw new errors.NotFoundError();
@@ -126,6 +114,7 @@ class PoliciesServer {
126
114
  );
127
115
  router.delete(
128
116
  "/policies/:kind/:namespace/:name",
117
+ restInterceptor.logAuditorEvent(this.auditor),
129
118
  async (request, response) => {
130
119
  const decision = await this.authorize(
131
120
  request,
@@ -144,50 +133,39 @@ class PoliciesServer {
144
133
  });
145
134
  const processedPolicies = await this.processPolicies(policyRaw, true);
146
135
  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
- });
136
+ response.locals.meta = { policies: processedPolicies };
156
137
  response.status(204).end();
157
138
  }
158
139
  );
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`);
140
+ router.post(
141
+ "/policies",
142
+ restInterceptor.logAuditorEvent(this.auditor),
143
+ async (request, response) => {
144
+ const decision = await this.authorize(
145
+ request,
146
+ pluginRbacCommon.policyEntityCreatePermission
147
+ );
148
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
149
+ throw new errors.NotAllowedError();
150
+ }
151
+ const policyRaw = request.body;
152
+ if (lodash.isEmpty(policyRaw)) {
153
+ throw new errors.InputError(`permission policy must be present`);
154
+ }
155
+ const processedPolicies = await this.processPolicies(policyRaw);
156
+ const entityRef = processedPolicies[0][0];
157
+ const roleMetadata = await this.roleMetadata.findRoleMetadata(entityRef);
158
+ if (entityRef.startsWith("role:default") && !roleMetadata) {
159
+ throw new Error(`Corresponding role ${entityRef} was not found`);
160
+ }
161
+ await this.enforcer.addPolicies(processedPolicies);
162
+ response.locals.meta = { policies: processedPolicies };
163
+ response.status(201).end();
176
164
  }
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
- });
165
+ );
189
166
  router.put(
190
167
  "/policies/:kind/:namespace/:name",
168
+ restInterceptor.logAuditorEvent(this.auditor),
191
169
  async (request, response) => {
192
170
  const decision = await this.authorize(
193
171
  request,
@@ -239,253 +217,235 @@ class PoliciesServer {
239
217
  processedOldPolicy,
240
218
  processedNewPolicy
241
219
  );
242
- await this.aLog.auditLog({
243
- message: `Updated permission policies`,
244
- eventName: auditLogger.PermissionEvents.UPDATE_POLICY,
245
- metadata: { policies: processedNewPolicy, source: "rest" },
246
- stage: auditLogger.SEND_RESPONSE_STAGE,
247
- status: "succeeded",
248
- request,
249
- response: { status: 200 }
250
- });
220
+ response.locals.meta = { policies: processedNewPolicy };
251
221
  response.status(200).end();
252
222
  }
253
223
  );
254
- router.get("/roles", async (request, response) => {
255
- const decision = await this.authorize(
256
- request,
257
- pluginRbacCommon.policyEntityReadPermission
258
- );
259
- if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
260
- throw new errors.NotAllowedError();
261
- }
262
- const roles = await this.enforcer.getGroupingPolicy();
263
- const body = await this.transformRoleArray(...roles);
264
- await this.aLog.auditLog({
265
- message: `Return list roles`,
266
- eventName: auditLogger.RoleEvents.GET_ROLE,
267
- stage: auditLogger.SEND_RESPONSE_STAGE,
268
- status: "succeeded",
269
- request,
270
- response: { status: 200, body }
271
- });
272
- response.json(body);
273
- });
274
- router.get("/roles/:kind/:namespace/:name", async (request, response) => {
275
- const decision = await this.authorize(
276
- request,
277
- pluginRbacCommon.policyEntityReadPermission
278
- );
279
- if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
280
- throw new errors.NotAllowedError();
281
- }
282
- const roleEntityRef = this.getEntityReference(request, true);
283
- const role = await this.enforcer.getFilteredGroupingPolicy(
284
- 1,
285
- roleEntityRef
286
- );
287
- if (role.length !== 0) {
288
- const body = await this.transformRoleArray(...role);
289
- await this.aLog.auditLog({
290
- message: `Return ${body[0].name}`,
291
- eventName: auditLogger.RoleEvents.GET_ROLE,
292
- stage: auditLogger.SEND_RESPONSE_STAGE,
293
- status: "succeeded",
224
+ router.get(
225
+ "/roles",
226
+ restInterceptor.logAuditorEvent(this.auditor),
227
+ async (request, response) => {
228
+ const decision = await this.authorize(
294
229
  request,
295
- response: { status: 200, body }
296
- });
230
+ pluginRbacCommon.policyEntityReadPermission
231
+ );
232
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
233
+ throw new errors.NotAllowedError();
234
+ }
235
+ const roles = await this.enforcer.getGroupingPolicy();
236
+ const body = await this.transformRoleArray(...roles);
297
237
  response.json(body);
298
- } else {
299
- throw new errors.NotFoundError();
300
- }
301
- });
302
- router.post("/roles", async (request, response) => {
303
- const uniqueItems = /* @__PURE__ */ new Set();
304
- const decision = await this.authorize(
305
- request,
306
- pluginRbacCommon.policyEntityCreatePermission
307
- );
308
- if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
309
- throw new errors.NotAllowedError();
310
238
  }
311
- const roleRaw = request.body;
312
- let err = policiesValidation.validateRole(roleRaw);
313
- if (err) {
314
- throw new errors.InputError(
315
- // 400
316
- `Invalid role definition. Cause: ${err.message}`
239
+ );
240
+ router.get(
241
+ "/roles/:kind/:namespace/:name",
242
+ restInterceptor.logAuditorEvent(this.auditor),
243
+ async (request, response) => {
244
+ const decision = await this.authorize(
245
+ request,
246
+ pluginRbacCommon.policyEntityReadPermission
317
247
  );
318
- }
319
- this.transformMemberReferencesToLowercase(roleRaw);
320
- const rMetadata = await this.roleMetadata.findRoleMetadata(roleRaw.name);
321
- err = await policiesValidation.validateSource("rest", rMetadata);
322
- if (err) {
323
- throw new errors.NotAllowedError(`Unable to add role: ${err.message}`);
324
- }
325
- const roles = this.transformRoleToArray(roleRaw);
326
- for (const role of roles) {
327
- if (await this.enforcer.hasGroupingPolicy(...role)) {
328
- throw new errors.ConflictError();
329
- }
330
- const roleString = JSON.stringify(role);
331
- if (uniqueItems.has(roleString)) {
332
- throw new errors.ConflictError(
333
- `Duplicate role members found; ${role.at(0)}, ${role.at(
334
- 1
335
- )} is a duplicate`
336
- );
248
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
249
+ throw new errors.NotAllowedError();
250
+ }
251
+ const roleEntityRef = this.getEntityReference(request, true);
252
+ const role = await this.enforcer.getFilteredGroupingPolicy(
253
+ 1,
254
+ roleEntityRef
255
+ );
256
+ if (role.length !== 0) {
257
+ const body = await this.transformRoleArray(...role);
258
+ response.json(body);
337
259
  } else {
338
- uniqueItems.add(roleString);
260
+ throw new errors.NotFoundError();
339
261
  }
340
262
  }
341
- const credentials = await httpAuth.credentials(request, {
342
- allow: ["user"]
343
- });
344
- const modifiedBy = credentials.principal.userEntityRef;
345
- const metadata = {
346
- roleEntityRef: roleRaw.name,
347
- source: "rest",
348
- description: roleRaw.metadata?.description ?? "",
349
- author: modifiedBy,
350
- modifiedBy
351
- };
352
- await this.enforcer.addGroupingPolicies(roles, metadata);
353
- await this.aLog.auditLog({
354
- message: `Created ${metadata.roleEntityRef}`,
355
- eventName: auditLogger.RoleEvents.CREATE_ROLE,
356
- metadata: {
357
- ...metadata,
358
- members: roles.map((gp) => gp[0])
359
- },
360
- stage: auditLogger.SEND_RESPONSE_STAGE,
361
- status: "succeeded",
362
- request,
363
- response: { status: 201 }
364
- });
365
- response.status(201).end();
366
- });
367
- router.put("/roles/:kind/:namespace/:name", async (request, response) => {
368
- const uniqueItems = /* @__PURE__ */ new Set();
369
- const decision = await this.authorize(
370
- request,
371
- pluginRbacCommon.policyEntityUpdatePermission
372
- );
373
- if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
374
- throw new errors.NotAllowedError();
375
- }
376
- const roleEntityRef = this.getEntityReference(request, true);
377
- const oldRoleRaw = request.body.oldRole;
378
- if (!oldRoleRaw) {
379
- throw new errors.InputError(`'oldRole' object must be present`);
380
- }
381
- const newRoleRaw = request.body.newRole;
382
- if (!newRoleRaw) {
383
- throw new errors.InputError(`'newRole' object must be present`);
384
- }
385
- oldRoleRaw.name = roleEntityRef;
386
- let err = policiesValidation.validateRole(oldRoleRaw);
387
- if (err) {
388
- throw new errors.InputError(
389
- // 400
390
- `Invalid old role object. Cause: ${err.message}`
263
+ );
264
+ router.post(
265
+ "/roles",
266
+ restInterceptor.logAuditorEvent(this.auditor),
267
+ async (request, response) => {
268
+ const uniqueItems = /* @__PURE__ */ new Set();
269
+ const decision = await this.authorize(
270
+ request,
271
+ pluginRbacCommon.policyEntityCreatePermission
391
272
  );
392
- }
393
- err = policiesValidation.validateRole(newRoleRaw);
394
- if (err) {
395
- throw new errors.InputError(
396
- // 400
397
- `Invalid new role object. Cause: ${err.message}`
273
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
274
+ throw new errors.NotAllowedError();
275
+ }
276
+ const roleRaw = request.body;
277
+ let err = policiesValidation.validateRole(roleRaw);
278
+ if (err) {
279
+ throw new errors.InputError(
280
+ // 400
281
+ `Invalid role definition. Cause: ${err.message}`
282
+ );
283
+ }
284
+ this.transformMemberReferencesToLowercase(roleRaw);
285
+ const rMetadata = await this.roleMetadata.findRoleMetadata(
286
+ roleRaw.name
398
287
  );
399
- }
400
- this.transformMemberReferencesToLowercase(oldRoleRaw);
401
- this.transformMemberReferencesToLowercase(newRoleRaw);
402
- const oldRole = this.transformRoleToArray(oldRoleRaw);
403
- const newRole = this.transformRoleToArray(newRoleRaw);
404
- const credentials = await httpAuth.credentials(request, {
405
- allow: ["user"]
406
- });
407
- const newMetadata = {
408
- ...newRoleRaw.metadata,
409
- source: newRoleRaw.metadata?.source ?? "rest",
410
- roleEntityRef: newRoleRaw.name,
411
- modifiedBy: credentials.principal.userEntityRef
412
- };
413
- const oldMetadata = await this.roleMetadata.findRoleMetadata(roleEntityRef);
414
- if (!oldMetadata) {
415
- throw new errors.NotFoundError(`Unable to find metadata for ${roleEntityRef}`);
416
- }
417
- err = await policiesValidation.validateSource("rest", oldMetadata);
418
- if (err) {
419
- throw new errors.NotAllowedError(`Unable to edit role: ${err.message}`);
420
- }
421
- if (lodash.isEqual(oldRole, newRole) && helper.deepSortedEqual(oldMetadata, newMetadata, [
422
- "author",
423
- "modifiedBy",
424
- "createdAt",
425
- "lastModified"
426
- ])) {
427
- response.status(204).end();
428
- return;
429
- }
430
- for (const role of newRole) {
431
- const hasRole = oldRole.some((element) => {
432
- return lodash.isEqual(element, role);
433
- });
434
- if (await this.enforcer.hasGroupingPolicy(...role)) {
435
- if (!hasRole) {
288
+ err = await policiesValidation.validateSource("rest", rMetadata);
289
+ if (err) {
290
+ throw new errors.NotAllowedError(`Unable to add role: ${err.message}`);
291
+ }
292
+ const roles = this.transformRoleToArray(roleRaw);
293
+ for (const role of roles) {
294
+ if (await this.enforcer.hasGroupingPolicy(...role)) {
436
295
  throw new errors.ConflictError();
437
296
  }
297
+ const roleString = JSON.stringify(role);
298
+ if (uniqueItems.has(roleString)) {
299
+ throw new errors.ConflictError(
300
+ `Duplicate role members found; ${role.at(0)}, ${role.at(
301
+ 1
302
+ )} is a duplicate`
303
+ );
304
+ } else {
305
+ uniqueItems.add(roleString);
306
+ }
438
307
  }
439
- const roleString = JSON.stringify(role);
440
- if (uniqueItems.has(roleString)) {
441
- throw new errors.ConflictError(
442
- `Duplicate role members found; ${role.at(0)}, ${role.at(
443
- 1
444
- )} is a duplicate`
308
+ const credentials = await httpAuth.credentials(request, {
309
+ allow: ["user"]
310
+ });
311
+ const modifiedBy = credentials.principal.userEntityRef;
312
+ const metadata = {
313
+ roleEntityRef: roleRaw.name,
314
+ source: "rest",
315
+ description: roleRaw.metadata?.description ?? "",
316
+ author: modifiedBy,
317
+ modifiedBy
318
+ };
319
+ await this.enforcer.addGroupingPolicies(roles, metadata);
320
+ response.locals.meta = { ...metadata, members: roles.map((gp) => gp[0]) };
321
+ response.status(201).end();
322
+ }
323
+ );
324
+ router.put(
325
+ "/roles/:kind/:namespace/:name",
326
+ restInterceptor.logAuditorEvent(this.auditor),
327
+ async (request, response) => {
328
+ const uniqueItems = /* @__PURE__ */ new Set();
329
+ const decision = await this.authorize(
330
+ request,
331
+ pluginRbacCommon.policyEntityUpdatePermission
332
+ );
333
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
334
+ throw new errors.NotAllowedError();
335
+ }
336
+ const roleEntityRef = this.getEntityReference(request, true);
337
+ const oldRoleRaw = request.body.oldRole;
338
+ if (!oldRoleRaw) {
339
+ throw new errors.InputError(`'oldRole' object must be present`);
340
+ }
341
+ const newRoleRaw = request.body.newRole;
342
+ if (!newRoleRaw) {
343
+ throw new errors.InputError(`'newRole' object must be present`);
344
+ }
345
+ oldRoleRaw.name = roleEntityRef;
346
+ let err = policiesValidation.validateRole(oldRoleRaw);
347
+ if (err) {
348
+ throw new errors.InputError(
349
+ // 400
350
+ `Invalid old role object. Cause: ${err.message}`
445
351
  );
446
- } else {
447
- uniqueItems.add(roleString);
448
352
  }
449
- }
450
- uniqueItems.clear();
451
- for (const role of oldRole) {
452
- if (!await this.enforcer.hasGroupingPolicy(...role)) {
453
- throw new errors.NotFoundError(
454
- `Member reference: ${role[0]} was not found for role ${roleEntityRef}`
353
+ err = policiesValidation.validateRole(newRoleRaw);
354
+ if (err) {
355
+ throw new errors.InputError(
356
+ // 400
357
+ `Invalid new role object. Cause: ${err.message}`
455
358
  );
456
359
  }
457
- const roleString = JSON.stringify(role);
458
- if (uniqueItems.has(roleString)) {
459
- throw new errors.ConflictError(
460
- `Duplicate role members found; ${role.at(0)}, ${role.at(
461
- 1
462
- )} is a duplicate`
360
+ this.transformMemberReferencesToLowercase(oldRoleRaw);
361
+ this.transformMemberReferencesToLowercase(newRoleRaw);
362
+ const oldRole = this.transformRoleToArray(oldRoleRaw);
363
+ const newRole = this.transformRoleToArray(newRoleRaw);
364
+ const credentials = await httpAuth.credentials(request, {
365
+ allow: ["user"]
366
+ });
367
+ const newMetadata = {
368
+ ...newRoleRaw.metadata,
369
+ source: newRoleRaw.metadata?.source ?? "rest",
370
+ roleEntityRef: newRoleRaw.name,
371
+ modifiedBy: credentials.principal.userEntityRef
372
+ };
373
+ const oldMetadata = await this.roleMetadata.findRoleMetadata(roleEntityRef);
374
+ if (!oldMetadata) {
375
+ throw new errors.NotFoundError(
376
+ `Unable to find metadata for ${roleEntityRef}`
463
377
  );
464
- } else {
465
- uniqueItems.add(roleString);
466
378
  }
467
- }
468
- await this.enforcer.updateGroupingPolicies(oldRole, newRole, newMetadata);
469
- let message = `Updated ${oldMetadata.roleEntityRef}.`;
470
- if (newMetadata.roleEntityRef !== oldMetadata.roleEntityRef) {
471
- message = `${message}. Role entity reference renamed to ${newMetadata.roleEntityRef}`;
472
- }
473
- await this.aLog.auditLog({
474
- message,
475
- eventName: auditLogger.RoleEvents.UPDATE_ROLE,
476
- metadata: {
379
+ err = await policiesValidation.validateSource("rest", oldMetadata);
380
+ if (err) {
381
+ throw new errors.NotAllowedError(`Unable to edit role: ${err.message}`);
382
+ }
383
+ if (lodash.isEqual(oldRole, newRole) && helper.deepSortedEqual(oldMetadata, newMetadata, [
384
+ "author",
385
+ "modifiedBy",
386
+ "createdAt",
387
+ "lastModified"
388
+ ])) {
389
+ response.status(204).end();
390
+ return;
391
+ }
392
+ for (const role of newRole) {
393
+ const hasRole = oldRole.some((element) => {
394
+ return lodash.isEqual(element, role);
395
+ });
396
+ if (await this.enforcer.hasGroupingPolicy(...role)) {
397
+ if (!hasRole) {
398
+ throw new errors.ConflictError();
399
+ }
400
+ }
401
+ const roleString = JSON.stringify(role);
402
+ if (uniqueItems.has(roleString)) {
403
+ throw new errors.ConflictError(
404
+ `Duplicate role members found; ${role.at(0)}, ${role.at(
405
+ 1
406
+ )} is a duplicate`
407
+ );
408
+ } else {
409
+ uniqueItems.add(roleString);
410
+ }
411
+ }
412
+ uniqueItems.clear();
413
+ for (const role of oldRole) {
414
+ if (!await this.enforcer.hasGroupingPolicy(...role)) {
415
+ throw new errors.NotFoundError(
416
+ `Member reference: ${role[0]} was not found for role ${roleEntityRef}`
417
+ );
418
+ }
419
+ const roleString = JSON.stringify(role);
420
+ if (uniqueItems.has(roleString)) {
421
+ throw new errors.ConflictError(
422
+ `Duplicate role members found; ${role.at(0)}, ${role.at(
423
+ 1
424
+ )} is a duplicate`
425
+ );
426
+ } else {
427
+ uniqueItems.add(roleString);
428
+ }
429
+ }
430
+ await this.enforcer.updateGroupingPolicies(
431
+ oldRole,
432
+ newRole,
433
+ newMetadata
434
+ );
435
+ let message = `Updated ${oldMetadata.roleEntityRef}.`;
436
+ if (newMetadata.roleEntityRef !== oldMetadata.roleEntityRef) {
437
+ message = `${message}. Role entity reference renamed to ${newMetadata.roleEntityRef}`;
438
+ }
439
+ response.locals.meta = {
477
440
  ...newMetadata,
478
441
  members: newRole.map((gp) => gp[0])
479
- },
480
- stage: auditLogger.SEND_RESPONSE_STAGE,
481
- status: "succeeded",
482
- request,
483
- response: { status: 200 }
484
- });
485
- response.status(200).end();
486
- });
442
+ };
443
+ response.status(200).end();
444
+ }
445
+ );
487
446
  router.delete(
488
447
  "/roles/:kind/:namespace/:name",
448
+ restInterceptor.logAuditorEvent(this.auditor),
489
449
  async (request, response) => {
490
450
  const decision = await this.authorize(
491
451
  request,
@@ -541,238 +501,208 @@ class PoliciesServer {
541
501
  metadata,
542
502
  false
543
503
  );
544
- await this.aLog.auditLog({
545
- message: `Deleted ${metadata.roleEntityRef}`,
546
- eventName: auditLogger.RoleEvents.DELETE_ROLE,
547
- metadata: {
548
- ...metadata,
549
- members: roleMembers.map((gp) => gp[0])
550
- },
551
- stage: auditLogger.SEND_RESPONSE_STAGE,
552
- status: "succeeded",
553
- request,
554
- response: { status: 204 }
555
- });
504
+ response.locals.meta = {
505
+ ...metadata,
506
+ members: roleMembers.map((gp) => gp[0])
507
+ };
556
508
  response.status(204).end();
557
509
  }
558
510
  );
559
- router.get("/plugins/policies", async (request, response) => {
560
- const decision = await this.authorize(
561
- request,
562
- pluginRbacCommon.policyEntityReadPermission
563
- );
564
- if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
565
- throw new errors.NotAllowedError();
511
+ router.get(
512
+ "/plugins/policies",
513
+ restInterceptor.logAuditorEvent(this.auditor),
514
+ async (request, response) => {
515
+ const decision = await this.authorize(
516
+ request,
517
+ pluginRbacCommon.policyEntityReadPermission
518
+ );
519
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
520
+ throw new errors.NotAllowedError();
521
+ }
522
+ const body = await this.pluginPermMetaData.getPluginPolicies(
523
+ this.options.auth
524
+ );
525
+ response.json(body);
566
526
  }
567
- const body = await this.pluginPermMetaData.getPluginPolicies(
568
- this.options.auth
569
- );
570
- await this.aLog.auditLog({
571
- message: `Return list plugin policies`,
572
- eventName: auditLogger.ListPluginPoliciesEvents.GET_PLUGINS_POLICIES,
573
- stage: auditLogger.SEND_RESPONSE_STAGE,
574
- status: "succeeded",
575
- request,
576
- response: { status: 200, body }
577
- });
578
- response.json(body);
579
- });
580
- router.get("/plugins/condition-rules", async (request, response) => {
581
- const decision = await this.authorize(
582
- request,
583
- pluginRbacCommon.policyEntityReadPermission
584
- );
585
- if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
586
- throw new errors.NotAllowedError();
527
+ );
528
+ router.get(
529
+ "/plugins/condition-rules",
530
+ restInterceptor.logAuditorEvent(this.auditor),
531
+ async (request, response) => {
532
+ const decision = await this.authorize(
533
+ request,
534
+ pluginRbacCommon.policyEntityReadPermission
535
+ );
536
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
537
+ throw new errors.NotAllowedError();
538
+ }
539
+ const body = await this.pluginPermMetaData.getPluginConditionRules(
540
+ this.options.auth
541
+ );
542
+ response.json(body);
587
543
  }
588
- const body = await this.pluginPermMetaData.getPluginConditionRules(
589
- this.options.auth
590
- );
591
- await this.aLog.auditLog({
592
- message: `Return list conditional rules and schemas`,
593
- eventName: auditLogger.ListConditionEvents.GET_CONDITION_RULES,
594
- stage: auditLogger.SEND_RESPONSE_STAGE,
595
- status: "succeeded",
596
- request,
597
- response: { status: 200, body }
598
- });
599
- response.json(body);
600
- });
601
- router.get("/roles/conditions", async (request, response) => {
602
- const decision = await this.authorize(
603
- request,
604
- pluginRbacCommon.policyEntityReadPermission
605
- );
606
- if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
607
- throw new errors.NotAllowedError();
544
+ );
545
+ router.get(
546
+ "/roles/conditions",
547
+ restInterceptor.logAuditorEvent(this.auditor),
548
+ async (request, response) => {
549
+ const decision = await this.authorize(
550
+ request,
551
+ pluginRbacCommon.policyEntityReadPermission
552
+ );
553
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
554
+ throw new errors.NotAllowedError();
555
+ }
556
+ const conditions = await this.conditionalStorage.filterConditions(
557
+ this.getFirstQuery(request.query.roleEntityRef),
558
+ this.getFirstQuery(request.query.pluginId),
559
+ this.getFirstQuery(request.query.resourceType),
560
+ this.getActionQueries(request.query.actions)
561
+ );
562
+ const body = conditions.map((condition) => {
563
+ return {
564
+ ...condition,
565
+ permissionMapping: condition.permissionMapping.map(
566
+ (pm) => pm.action
567
+ )
568
+ };
569
+ });
570
+ response.json(body);
608
571
  }
609
- const conditions = await this.conditionalStorage.filterConditions(
610
- this.getFirstQuery(request.query.roleEntityRef),
611
- this.getFirstQuery(request.query.pluginId),
612
- this.getFirstQuery(request.query.resourceType),
613
- this.getActionQueries(request.query.actions)
614
- );
615
- const body = conditions.map((condition) => {
616
- return {
572
+ );
573
+ router.post(
574
+ "/roles/conditions",
575
+ restInterceptor.logAuditorEvent(this.auditor),
576
+ async (request, response) => {
577
+ const decision = await this.authorize(
578
+ request,
579
+ pluginRbacCommon.policyEntityCreatePermission
580
+ );
581
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
582
+ throw new errors.NotAllowedError();
583
+ }
584
+ const roleConditionPolicy = request.body;
585
+ conditionValidation.validateRoleCondition(roleConditionPolicy);
586
+ const conditionToCreate = await helper.processConditionMapping(
587
+ roleConditionPolicy,
588
+ this.pluginPermMetaData,
589
+ this.options.auth
590
+ );
591
+ const id = await this.conditionalStorage.createCondition(conditionToCreate);
592
+ const body = { id };
593
+ response.locals.meta = { condition: roleConditionPolicy };
594
+ response.status(201).json(body);
595
+ }
596
+ );
597
+ router.get(
598
+ "/roles/conditions/:id",
599
+ restInterceptor.logAuditorEvent(this.auditor),
600
+ async (request, response) => {
601
+ const decision = await this.authorize(
602
+ request,
603
+ pluginRbacCommon.policyEntityReadPermission
604
+ );
605
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
606
+ throw new errors.NotAllowedError();
607
+ }
608
+ const id = parseInt(request.params.id, 10);
609
+ if (isNaN(id)) {
610
+ throw new errors.InputError("Id is not a valid number.");
611
+ }
612
+ const condition = await this.conditionalStorage.getCondition(id);
613
+ if (!condition) {
614
+ throw new errors.NotFoundError();
615
+ }
616
+ const body = {
617
617
  ...condition,
618
618
  permissionMapping: condition.permissionMapping.map((pm) => pm.action)
619
619
  };
620
- });
621
- await this.aLog.auditLog({
622
- message: `Return list conditional permission policies`,
623
- eventName: auditLogger.ConditionEvents.GET_CONDITION,
624
- stage: auditLogger.SEND_RESPONSE_STAGE,
625
- status: "succeeded",
626
- request,
627
- response: { status: 200, body }
628
- });
629
- response.json(body);
630
- });
631
- router.post("/roles/conditions", async (request, response) => {
632
- const decision = await this.authorize(
633
- request,
634
- pluginRbacCommon.policyEntityCreatePermission
635
- );
636
- if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
637
- throw new errors.NotAllowedError();
638
- }
639
- const roleConditionPolicy = request.body;
640
- conditionValidation.validateRoleCondition(roleConditionPolicy);
641
- const conditionToCreate = await helper.processConditionMapping(
642
- roleConditionPolicy,
643
- this.pluginPermMetaData,
644
- this.options.auth
645
- );
646
- const id = await this.conditionalStorage.createCondition(conditionToCreate);
647
- const body = { id };
648
- await this.aLog.auditLog({
649
- message: `Created conditional permission policy`,
650
- eventName: auditLogger.ConditionEvents.CREATE_CONDITION,
651
- metadata: { condition: roleConditionPolicy },
652
- stage: auditLogger.SEND_RESPONSE_STAGE,
653
- status: "succeeded",
654
- request,
655
- response: { status: 201, body }
656
- });
657
- response.status(201).json(body);
658
- });
659
- router.get("/roles/conditions/:id", async (request, response) => {
660
- const decision = await this.authorize(
661
- request,
662
- pluginRbacCommon.policyEntityReadPermission
663
- );
664
- if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
665
- throw new errors.NotAllowedError();
666
- }
667
- const id = parseInt(request.params.id, 10);
668
- if (isNaN(id)) {
669
- throw new errors.InputError("Id is not a valid number.");
670
- }
671
- const condition = await this.conditionalStorage.getCondition(id);
672
- if (!condition) {
673
- throw new errors.NotFoundError();
674
- }
675
- const body = {
676
- ...condition,
677
- permissionMapping: condition.permissionMapping.map((pm) => pm.action)
678
- };
679
- await this.aLog.auditLog({
680
- message: `Return conditional permission policy by id`,
681
- eventName: auditLogger.ConditionEvents.GET_CONDITION,
682
- stage: auditLogger.SEND_RESPONSE_STAGE,
683
- status: "succeeded",
684
- request,
685
- response: { status: 200, body }
686
- });
687
- response.json(body);
688
- });
689
- router.delete("/roles/conditions/:id", async (request, response) => {
690
- const decision = await this.authorize(
691
- request,
692
- pluginRbacCommon.policyEntityDeletePermission
693
- );
694
- if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
695
- throw new errors.NotAllowedError();
696
- }
697
- const id = parseInt(request.params.id, 10);
698
- if (isNaN(id)) {
699
- throw new errors.InputError("Id is not a valid number.");
700
- }
701
- const condition = await this.conditionalStorage.getCondition(id);
702
- if (!condition) {
703
- throw new errors.NotFoundError(`Condition with id ${id} was not found`);
704
- }
705
- const conditionToDelete = {
706
- ...condition,
707
- permissionMapping: condition.permissionMapping.map((pm) => pm.action)
708
- };
709
- await this.conditionalStorage.deleteCondition(id);
710
- await this.aLog.auditLog({
711
- message: `Deleted conditional permission policy`,
712
- eventName: auditLogger.ConditionEvents.DELETE_CONDITION,
713
- metadata: { condition: conditionToDelete },
714
- stage: auditLogger.SEND_RESPONSE_STAGE,
715
- status: "succeeded",
716
- request,
717
- response: { status: 204 }
718
- });
719
- response.status(204).end();
720
- });
721
- router.put("/roles/conditions/:id", async (request, response) => {
722
- const decision = await this.authorize(
723
- request,
724
- pluginRbacCommon.policyEntityUpdatePermission
725
- );
726
- if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
727
- throw new errors.NotAllowedError();
728
- }
729
- const id = parseInt(request.params.id, 10);
730
- if (isNaN(id)) {
731
- throw new errors.InputError("Id is not a valid number.");
620
+ response.json(body);
732
621
  }
733
- const roleConditionPolicy = request.body;
734
- conditionValidation.validateRoleCondition(roleConditionPolicy);
735
- const conditionToUpdate = await helper.processConditionMapping(
736
- roleConditionPolicy,
737
- this.pluginPermMetaData,
738
- this.options.auth
739
- );
740
- await this.conditionalStorage.updateCondition(id, conditionToUpdate);
741
- await this.aLog.auditLog({
742
- message: `Updated conditional permission policy`,
743
- eventName: auditLogger.ConditionEvents.UPDATE_CONDITION,
744
- metadata: { condition: roleConditionPolicy },
745
- stage: auditLogger.SEND_RESPONSE_STAGE,
746
- status: "succeeded",
747
- request,
748
- response: { status: 200 }
749
- });
750
- response.status(200).end();
751
- });
752
- router.post("/refresh/:id", async (request, response) => {
753
- const decision = await this.authorize(
754
- request,
755
- pluginRbacCommon.policyEntityCreatePermission
756
- );
757
- if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
758
- throw new errors.NotAllowedError();
622
+ );
623
+ router.delete(
624
+ "/roles/conditions/:id",
625
+ restInterceptor.logAuditorEvent(this.auditor),
626
+ async (request, response) => {
627
+ const decision = await this.authorize(
628
+ request,
629
+ pluginRbacCommon.policyEntityDeletePermission
630
+ );
631
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
632
+ throw new errors.NotAllowedError();
633
+ }
634
+ const id = parseInt(request.params.id, 10);
635
+ if (isNaN(id)) {
636
+ throw new errors.InputError("Id is not a valid number.");
637
+ }
638
+ const condition = await this.conditionalStorage.getCondition(id);
639
+ if (!condition) {
640
+ throw new errors.NotFoundError(`Condition with id ${id} was not found`);
641
+ }
642
+ const conditionToDelete = {
643
+ ...condition,
644
+ permissionMapping: condition.permissionMapping.map((pm) => pm.action)
645
+ };
646
+ await this.conditionalStorage.deleteCondition(id);
647
+ response.locals.meta = { condition: conditionToDelete };
648
+ response.status(204).end();
759
649
  }
760
- if (!this.rbacProviders) {
761
- throw new errors.NotFoundError(`No RBAC providers were found`);
650
+ );
651
+ router.put(
652
+ "/roles/conditions/:id",
653
+ restInterceptor.logAuditorEvent(this.auditor),
654
+ async (request, response) => {
655
+ const decision = await this.authorize(
656
+ request,
657
+ pluginRbacCommon.policyEntityUpdatePermission
658
+ );
659
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
660
+ throw new errors.NotAllowedError();
661
+ }
662
+ const id = parseInt(request.params.id, 10);
663
+ if (isNaN(id)) {
664
+ throw new errors.InputError("Id is not a valid number.");
665
+ }
666
+ const roleConditionPolicy = request.body;
667
+ conditionValidation.validateRoleCondition(roleConditionPolicy);
668
+ const conditionToUpdate = await helper.processConditionMapping(
669
+ roleConditionPolicy,
670
+ this.pluginPermMetaData,
671
+ this.options.auth
672
+ );
673
+ await this.conditionalStorage.updateCondition(id, conditionToUpdate);
674
+ response.locals.meta = { condition: roleConditionPolicy };
675
+ response.status(200).end();
762
676
  }
763
- const idProvider = this.rbacProviders.find((provider) => {
764
- const id = provider.getProviderName();
765
- return id === request.params.id;
766
- });
767
- if (!idProvider) {
768
- throw new errors.NotFoundError(
769
- `The RBAC provider ${request.params.id} was not found`
677
+ );
678
+ router.post(
679
+ "/refresh/:id",
680
+ restInterceptor.logAuditorEvent(this.auditor),
681
+ async (request, response) => {
682
+ const decision = await this.authorize(
683
+ request,
684
+ pluginRbacCommon.policyEntityCreatePermission
770
685
  );
686
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
687
+ throw new errors.NotAllowedError();
688
+ }
689
+ if (!this.rbacProviders) {
690
+ throw new errors.NotFoundError(`No RBAC providers were found`);
691
+ }
692
+ const idProvider = this.rbacProviders.find((provider) => {
693
+ const id = provider.getProviderName();
694
+ return id === request.params.id;
695
+ });
696
+ if (!idProvider) {
697
+ throw new errors.NotFoundError(
698
+ `The RBAC provider ${request.params.id} was not found`
699
+ );
700
+ }
701
+ await idProvider.refresh();
702
+ response.status(200).end();
771
703
  }
772
- await idProvider.refresh();
773
- response.status(200).end();
774
- });
775
- router.use(restErrorsInterceptor.auditError(this.aLog));
704
+ );
705
+ router.use(restInterceptor.setAuditorError());
776
706
  return router;
777
707
  }
778
708
  getEntityReference(request, role) {