@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.
- package/CHANGELOG.md +671 -0
- package/README.md +220 -0
- package/config.d.ts +68 -0
- package/dist/admin-permissions/admin-creation.cjs.js +117 -0
- package/dist/admin-permissions/admin-creation.cjs.js.map +1 -0
- package/dist/audit-log/audit-logger.cjs.js +108 -0
- package/dist/audit-log/audit-logger.cjs.js.map +1 -0
- package/dist/audit-log/rest-errors-interceptor.cjs.js +100 -0
- package/dist/audit-log/rest-errors-interceptor.cjs.js.map +1 -0
- package/dist/conditional-aliases/alias-resolver.cjs.js +76 -0
- package/dist/conditional-aliases/alias-resolver.cjs.js.map +1 -0
- package/dist/database/casbin-adapter-factory.cjs.js +87 -0
- package/dist/database/casbin-adapter-factory.cjs.js.map +1 -0
- package/dist/database/conditional-storage.cjs.js +172 -0
- package/dist/database/conditional-storage.cjs.js.map +1 -0
- package/dist/database/migration.cjs.js +21 -0
- package/dist/database/migration.cjs.js.map +1 -0
- package/dist/database/role-metadata.cjs.js +89 -0
- package/dist/database/role-metadata.cjs.js.map +1 -0
- package/dist/file-permissions/csv-file-watcher.cjs.js +407 -0
- package/dist/file-permissions/csv-file-watcher.cjs.js.map +1 -0
- package/dist/file-permissions/file-watcher.cjs.js +46 -0
- package/dist/file-permissions/file-watcher.cjs.js.map +1 -0
- package/dist/file-permissions/yaml-conditional-file-watcher.cjs.js +208 -0
- package/dist/file-permissions/yaml-conditional-file-watcher.cjs.js.map +1 -0
- package/dist/helper.cjs.js +171 -0
- package/dist/helper.cjs.js.map +1 -0
- package/dist/index.cjs.js +14 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/plugin.cjs.js +79 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/policies/allow-all-policy.cjs.js +12 -0
- package/dist/policies/allow-all-policy.cjs.js.map +1 -0
- package/dist/policies/permission-policy.cjs.js +243 -0
- package/dist/policies/permission-policy.cjs.js.map +1 -0
- package/dist/providers/connect-providers.cjs.js +211 -0
- package/dist/providers/connect-providers.cjs.js.map +1 -0
- package/dist/role-manager/ancestor-search-memo.cjs.js +159 -0
- package/dist/role-manager/ancestor-search-memo.cjs.js.map +1 -0
- package/dist/role-manager/member-list.cjs.js +101 -0
- package/dist/role-manager/member-list.cjs.js.map +1 -0
- package/dist/role-manager/role-manager.cjs.js +281 -0
- package/dist/role-manager/role-manager.cjs.js.map +1 -0
- package/dist/service/enforcer-delegate.cjs.js +353 -0
- package/dist/service/enforcer-delegate.cjs.js.map +1 -0
- package/dist/service/permission-model.cjs.js +21 -0
- package/dist/service/permission-model.cjs.js.map +1 -0
- package/dist/service/plugin-endpoints.cjs.js +121 -0
- package/dist/service/plugin-endpoints.cjs.js.map +1 -0
- package/dist/service/policies-rest-api.cjs.js +949 -0
- package/dist/service/policies-rest-api.cjs.js.map +1 -0
- package/dist/service/policy-builder.cjs.js +134 -0
- package/dist/service/policy-builder.cjs.js.map +1 -0
- package/dist/service/router.cjs.js +24 -0
- package/dist/service/router.cjs.js.map +1 -0
- package/dist/validation/condition-validation.cjs.js +107 -0
- package/dist/validation/condition-validation.cjs.js.map +1 -0
- package/dist/validation/policies-validation.cjs.js +194 -0
- package/dist/validation/policies-validation.cjs.js.map +1 -0
- package/migrations/20231015161232_migrations.js +41 -0
- package/migrations/20231212224526_migrations.js +84 -0
- package/migrations/20231221113214_migrations.js +60 -0
- package/migrations/20240201144429_migrations.js +37 -0
- package/migrations/20240215154456_migrations.js +143 -0
- package/migrations/20240308134410_migrations.js +31 -0
- package/migrations/20240308134941_migrations.js +43 -0
- package/migrations/20240404111242_migrations.js +53 -0
- package/migrations/20240611092136_migrations.js +29 -0
- 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
|