Skip to content

Commit 0454509

Browse files
feat(rbac): save role modification information to the metadata (#1280)
* feat(rbac): save role modification information to the metadata Signed-off-by: Oleksandr Andriienko <[email protected]>
1 parent 57dee0c commit 0454509

18 files changed

+1609
-239
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* @param { import("knex").Knex } knex
3+
* @returns { Promise<void> }
4+
*/
5+
exports.up = async function up(knex) {
6+
const isRoleMetaDataExist = await knex.schema.hasTable('role-metadata');
7+
if (isRoleMetaDataExist) {
8+
await knex.schema.alterTable('role-metadata', table => {
9+
table.string('author');
10+
table.string('modifiedBy');
11+
table.dateTime('createdAt');
12+
table.dateTime('lastModified');
13+
});
14+
15+
await knex('role-metadata')
16+
.update({
17+
description:
18+
'The default permission policy for the admin role allows for the creation, deletion, updating, and reading of roles and permission policies.',
19+
author: 'application configuration',
20+
modifiedBy: 'application configuration',
21+
lastModified: new Date().toUTCString(),
22+
})
23+
.where('roleEntityRef', 'role:default/rbac_admin');
24+
}
25+
};
26+
27+
/**
28+
* @param { import("knex").Knex } knex
29+
* @returns { Promise<void> }
30+
*/
31+
exports.down = async function down(knex) {
32+
const isRoleMetaDataExist = await knex.schema.hasTable('role-metadata');
33+
if (isRoleMetaDataExist) {
34+
await knex.schema.alterTable('role-metadata', table => {
35+
table.dropColumn('author');
36+
table.dropColumn('modifiedBy');
37+
table.dropColumn('createdAt');
38+
table.dropColumn('lastModified');
39+
});
40+
}
41+
};

plugins/rbac-backend/src/database/policy-metadata-storage.test.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,6 @@ describe('policy-metadata-db-table', () => {
3131
}),
3232
migrations: { skip: false },
3333
};
34-
await knex.schema.createTable('casbin_rule', table => {
35-
table.increments('id').primary();
36-
table.string('ptype');
37-
table.string('v0');
38-
table.string('v1');
39-
table.string('v2');
40-
table.string('v3');
41-
table.string('v4');
42-
table.string('v5');
43-
table.string('v6');
44-
});
4534
await migrate(databaseManagerMock);
4635
return {
4736
knex,

plugins/rbac-backend/src/database/role-metadata.test.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,6 @@ describe('role-metadata-db-table', () => {
2626
}),
2727
migrations: { skip: false },
2828
};
29-
await knex.schema.createTable('casbin_rule', table => {
30-
table.increments('id').primary();
31-
table.string('ptype');
32-
table.string('v0');
33-
table.string('v1');
34-
table.string('v2');
35-
table.string('v3');
36-
table.string('v4');
37-
table.string('v5');
38-
table.string('v6');
39-
});
4029
await migrate(databaseManagerMock);
4130
return {
4231
knex,
@@ -81,8 +70,12 @@ describe('role-metadata-db-table', () => {
8170
);
8271
await trx.commit();
8372
expect(roleMetadata).toEqual({
73+
author: null,
74+
createdAt: null,
8475
description: null,
8576
id: 1,
77+
lastModified: null,
78+
modifiedBy: null,
8679
roleEntityRef: 'role:default/some-super-important-role',
8780
source: 'rest',
8881
});
@@ -122,9 +115,13 @@ describe('role-metadata-db-table', () => {
122115
);
123116
expect(metadata.length).toEqual(1);
124117
expect(metadata[0]).toEqual({
118+
author: null,
119+
createdAt: null,
125120
roleEntityRef: 'role:default/some-super-important-role',
126121
description: null,
127122
id: 1,
123+
lastModified: null,
124+
modifiedBy: null,
128125
source: 'configuration',
129126
});
130127
},
@@ -274,10 +271,14 @@ describe('role-metadata-db-table', () => {
274271
);
275272
expect(metadata.length).toEqual(1);
276273
expect(metadata[0]).toEqual({
274+
author: null,
275+
createdAt: null,
277276
description: null,
278277
source: 'rest',
279278
roleEntityRef: 'role:default/some-super-important-role',
280279
id: 1,
280+
lastModified: null,
281+
modifiedBy: null,
281282
});
282283
},
283284
);
@@ -344,10 +345,14 @@ describe('role-metadata-db-table', () => {
344345
);
345346
expect(metadata.length).toEqual(1);
346347
expect(metadata[0]).toEqual({
348+
author: null,
349+
createdAt: null,
347350
description: null,
348351
source: 'configuration',
349352
roleEntityRef: 'role:default/important-role',
350353
id: 1,
354+
lastModified: null,
355+
modifiedBy: null,
351356
});
352357
},
353358
);

plugins/rbac-backend/src/database/role-metadata.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ export function daoToMetadata(dao: RoleMetadataDao): RoleMetadata {
133133
return {
134134
source: dao.source,
135135
description: dao.description,
136+
author: dao.author,
137+
modifiedBy: dao.modifiedBy,
138+
createdAt: dao.createdAt,
139+
lastModified: dao.lastModified,
136140
};
137141
}
138142

plugins/rbac-backend/src/file-permissions/csv.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,10 @@ describe('CSV file', () => {
768768
},
769769
);
770770

771-
await enfDelegate.addGroupingPolicy(duplicateRest, 'rest');
771+
await enfDelegate.addGroupingPolicy(duplicateRest, {
772+
source: 'rest',
773+
roleEntityRef: duplicateRest[1],
774+
});
772775

773776
const errorPolicyFile = resolve(
774777
__dirname,
@@ -962,7 +965,10 @@ describe('CSV file', () => {
962965
);
963966

964967
await enfDelegate.addPolicy(duplicatePolicyEnforcer, 'rest');
965-
await enfDelegate.addGroupingPolicy(duplicateRoleEnforcer, 'rest');
968+
await enfDelegate.addGroupingPolicy(duplicateRoleEnforcer, {
969+
source: 'rest',
970+
roleEntityRef: duplicateRoleEnforcer[1],
971+
});
966972

967973
const errorPolicyFile = resolve(
968974
__dirname,

plugins/rbac-backend/src/file-permissions/csv.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import { isEqual } from 'lodash';
33
import { Logger } from 'winston';
44

55
import { PolicyMetadataStorage } from '../database/policy-metadata-storage';
6-
import { RoleMetadataStorage } from '../database/role-metadata';
6+
import {
7+
RoleMetadataDao,
8+
RoleMetadataStorage,
9+
} from '../database/role-metadata';
710
import { metadataStringToPolicy, transformArrayToPolicy } from '../helper';
811
import { EnforcerDelegate } from '../service/enforcer-delegate';
912
import { MODEL } from '../service/permission-model';
@@ -15,6 +18,8 @@ import {
1518
validatePolicy,
1619
} from '../service/policies-validation';
1720

21+
export const CSV_PERMISSION_POLICY_FILE_AUTHOR = 'csv permission policy file';
22+
1823
const addPolicy = async (
1924
policy: string[],
2025
enf: EnforcerDelegate,
@@ -204,7 +209,8 @@ export const loadFilteredGroupingPoliciesFromCSV = async (
204209
logger.warn(duplicateError.message);
205210
}
206211

207-
const err = validateEntityReference(role[1]);
212+
const roleEntityRef = role[1];
213+
const err = validateEntityReference(roleEntityRef);
208214
if (err) {
209215
logger.warn(
210216
`Failed to validate role from file ${policyFile}. Cause: ${err.message}`,
@@ -216,7 +222,12 @@ export const loadFilteredGroupingPoliciesFromCSV = async (
216222

217223
// Role exists in the file but not the enforcer
218224
if (!(await enf.hasGroupingPolicy(...role))) {
219-
await enf.addOrUpdateGroupingPolicy(role, 'csv-file');
225+
await enf.addOrUpdateGroupingPolicy(role, {
226+
roleEntityRef,
227+
source: 'csv-file',
228+
author: CSV_PERMISSION_POLICY_FILE_AUTHOR,
229+
modifiedBy: CSV_PERMISSION_POLICY_FILE_AUTHOR,
230+
});
220231
} else if (roleSource?.source !== 'csv-file') {
221232
logger.warn(
222233
`Duplicate role: ${role[0]}, ${role[1]} found with the source ${roleSource?.source}`,
@@ -238,7 +249,17 @@ export const loadFilteredGroupingPoliciesFromCSV = async (
238249
!(await tempEnforcer.hasGroupingPolicy(...role)) &&
239250
enfRoleSource?.source === 'csv-file'
240251
) {
241-
await enf.removeGroupingPolicy(role, 'csv-file', true, true);
252+
await enf.removeGroupingPolicy(
253+
role,
254+
{
255+
roleEntityRef: role[1],
256+
source: 'csv-file',
257+
author: CSV_PERMISSION_POLICY_FILE_AUTHOR,
258+
modifiedBy: CSV_PERMISSION_POLICY_FILE_AUTHOR,
259+
},
260+
true,
261+
true,
262+
);
242263
}
243264
}
244265

@@ -305,7 +326,12 @@ export const removedOldPermissionPoliciesFileData = async (
305326
}
306327

307328
if (groupPoliciesToDelete.length > 0) {
308-
await enf.removeGroupingPolicies(groupPoliciesToDelete, 'csv-file', true);
329+
await enf.removeGroupingPolicies(
330+
groupPoliciesToDelete,
331+
'csv-file',
332+
CSV_PERMISSION_POLICY_FILE_AUTHOR,
333+
true,
334+
);
309335
}
310336
if (policiesToDelete.length > 0) {
311337
await enf.removePolicies(policiesToDelete, 'csv-file', true);
@@ -360,6 +386,12 @@ export const addPermissionPoliciesFileData = async (
360386
if (duplicateError) {
361387
logger.warn(duplicateError.message);
362388
}
363-
await enf.addOrUpdateGroupingPolicy(groupPolicy, 'csv-file', false);
389+
const metadata: RoleMetadataDao = {
390+
roleEntityRef: groupPolicy[1],
391+
source: 'csv-file',
392+
author: CSV_PERMISSION_POLICY_FILE_AUTHOR,
393+
modifiedBy: CSV_PERMISSION_POLICY_FILE_AUTHOR,
394+
};
395+
await enf.addOrUpdateGroupingPolicy(groupPolicy, metadata, false);
364396
}
365397
};

plugins/rbac-backend/src/helper.test.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from './helper';
1111
// Import the function to test
1212
import { EnforcerDelegate } from './service/enforcer-delegate';
13+
import { ADMIN_ROLE_AUTHOR } from './service/permission-policy';
1314

1415
describe('helper.ts', () => {
1516
describe('policyToString', () => {
@@ -87,11 +88,16 @@ describe('helper.ts', () => {
8788
source,
8889
roleName,
8990
mockEnforcerDelegate as EnforcerDelegate,
91+
ADMIN_ROLE_AUTHOR,
9092
);
9193

9294
expect(mockEnforcerDelegate.removeGroupingPolicy).toHaveBeenCalledWith(
9395
['user:default/admin', roleName],
94-
source,
96+
{
97+
modifiedBy: ADMIN_ROLE_AUTHOR,
98+
roleEntityRef: 'role:default/admin',
99+
source: 'rest',
100+
},
95101
false,
96102
);
97103
});
@@ -108,6 +114,7 @@ describe('helper.ts', () => {
108114
source,
109115
roleName,
110116
mockEnforcerDelegate as EnforcerDelegate,
117+
ADMIN_ROLE_AUTHOR,
111118
);
112119

113120
expect(mockEnforcerDelegate.removeGroupingPolicy).not.toHaveBeenCalled();
@@ -125,6 +132,7 @@ describe('helper.ts', () => {
125132
source,
126133
roleName,
127134
mockEnforcerDelegate as EnforcerDelegate,
135+
ADMIN_ROLE_AUTHOR,
128136
);
129137

130138
expect(mockEnforcerDelegate.removeGroupingPolicy).not.toHaveBeenCalled();
@@ -185,6 +193,34 @@ describe('helper.ts', () => {
185193
expect(deepSortedEqual(obj1, obj2)).toBe(true);
186194
});
187195

196+
it('should return true for identical objects with different ordering of top-level properties with exclude read only fields', () => {
197+
const obj1: RoleMetadataDao = {
198+
description: 'qa team',
199+
id: 1,
200+
roleEntityRef: 'role:default/qa',
201+
source: 'rest',
202+
// read only properties
203+
author: 'role:default/some-role',
204+
modifiedBy: 'role:default/some-role',
205+
createdAt: '2024-02-26 12:25:31+00',
206+
lastModified: '2024-02-26 12:25:31+00',
207+
};
208+
const obj2: RoleMetadataDao = {
209+
id: 1,
210+
description: 'qa team',
211+
source: 'rest',
212+
roleEntityRef: 'role:default/qa',
213+
};
214+
expect(
215+
deepSortedEqual(obj1, obj2, [
216+
'author',
217+
'modifiedBy',
218+
'createdAt',
219+
'lastModified',
220+
]),
221+
).toBe(true);
222+
});
223+
188224
it('should return false for objects with different values', () => {
189225
const obj1: RoleMetadataDao = {
190226
description: 'qa',

0 commit comments

Comments
 (0)