Skip to content

Commit 03a678d

Browse files
fix(rbac): split policies and roles by source (#1042)
* fix(rbac): split policies by source Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): imporove code Rename location to source Add grouping validation for policy file. Don't allow to modify roles and permission policy from permission policy file. Fix some bugs. Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): add transactions for role and policies metadata Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): cleanup policies which gone from csv policies file Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): handle preexisting policies by adding a legacy source * fix(rbac): add unit tests for role-metadata.ts Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): fix rest api unit tests Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): fmt code Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): fix failing tests Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): update knex-mock-client to align with knex 3.0 from main Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): avoid nested transactions for sqlite3 Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): handle code review feedback. Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): handle code review feedback Move predefined policies validation from permission-policy.ts file to policies-validation.ts. Add validation for admin users defined in the application config file. Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): improve clean up csv file policies and add tests around that Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): handle code review feedback about has method for Set Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): don't allow to modify configuration permission policies Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): add more unit tests Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): add the ability to update legacy policies * fix(rbac): fix validation mistake Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): complete fix bug #1103 Complete fix bug, when after removing admin from app configuration, admin still present. Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): fix compilation and tests after rebase Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): fix legacy migration for admin group policies Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): fix sonarcloud issues * fix(rbac): fix migration issue with multiple admins * fix(rbac): remove console log * fix(rbac): fix bug with hanging transaction on sqlite3 Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): fix tests after rebase Signed-off-by: Oleksandr Andriienko <[email protected]> --------- Signed-off-by: Oleksandr Andriienko <[email protected]> Co-authored-by: Patrick Knight <[email protected]>
1 parent f56d072 commit 03a678d

21 files changed

+3407
-318
lines changed

plugins/rbac-backend/config.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export interface Config {
22
permission: {
33
rbac: {
4+
'policies-csv-file'?: string;
45
/**
56
* Optional configuration for admins, can declare individual users and / or groups
67
* @visibility frontend
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* @param { import("knex").Knex } knex
3+
* @returns { Promise<void> }
4+
*/
5+
exports.up = async function up(knex) {
6+
const casbinDoesExist = await knex.schema.hasTable('casbin_rule');
7+
let policies = [];
8+
let groupPolicies = [];
9+
10+
if (casbinDoesExist) {
11+
policies = await knex
12+
.select('*')
13+
.from('casbin_rule')
14+
.where('ptype', 'p')
15+
.then(listPolicies => {
16+
const allPolicies = [];
17+
for (const policy of listPolicies) {
18+
const { v0, v1, v2, v3 } = policy;
19+
allPolicies.push(`[${v0}, ${v1}, ${v2}, ${v3}]`);
20+
}
21+
return allPolicies;
22+
});
23+
groupPolicies = await knex
24+
.select('*')
25+
.from('casbin_rule')
26+
.where('ptype', 'g')
27+
.then(listGroupPolicies => {
28+
const allGroupPolicies = [];
29+
for (const groupPolicy of listGroupPolicies) {
30+
const { v0, v1 } = groupPolicy;
31+
allGroupPolicies.push(`[${v0}, ${v1}]`);
32+
}
33+
return allGroupPolicies;
34+
});
35+
}
36+
37+
await knex.schema
38+
.createTable('policy-metadata', table => {
39+
table.increments('id').primary();
40+
table.string('policy').primary();
41+
table.string('source');
42+
})
43+
.then(async () => {
44+
for (const policy of policies) {
45+
await knex
46+
.table('policy-metadata')
47+
.insert({ source: 'legacy', policy: policy });
48+
}
49+
})
50+
.then(async () => {
51+
for (const groupPolicy of groupPolicies) {
52+
await knex
53+
.table('policy-metadata')
54+
.insert({ source: 'legacy', policy: groupPolicy });
55+
}
56+
});
57+
};
58+
59+
/**
60+
* @param { import("knex").Knex } knex
61+
* @returns { Promise<void> }
62+
*/
63+
exports.down = async function down(knex) {
64+
await knex.schema.dropTable('policy-metadata');
65+
};
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* @param { import("knex").Knex } knex
3+
* @returns { Promise<void> }
4+
*/
5+
exports.up = async function up(knex) {
6+
const casbinDoesExist = await knex.schema.hasTable('casbin_rule');
7+
let groupPolicies = [];
8+
9+
if (casbinDoesExist) {
10+
groupPolicies = await knex
11+
.select('*')
12+
.from('casbin_rule')
13+
.where('ptype', 'g')
14+
.then(listGroupPolicies => {
15+
const allGroupPolicies = [];
16+
let rbacFlag = false;
17+
for (const groupPolicy of listGroupPolicies) {
18+
const { v1 } = groupPolicy;
19+
if (v1 === 'role:default/rbac_admin') {
20+
rbacFlag = true;
21+
continue;
22+
}
23+
allGroupPolicies.push(v1);
24+
}
25+
if (rbacFlag) {
26+
allGroupPolicies.push('role:default/rbac_admin');
27+
}
28+
return allGroupPolicies;
29+
});
30+
}
31+
32+
await knex.schema
33+
.createTable('role-metadata', table => {
34+
table.increments('id').primary();
35+
table.string('roleEntityRef').primary();
36+
table.string('source');
37+
})
38+
.then(async () => {
39+
for (const groupPolicy of groupPolicies) {
40+
await knex
41+
.table('role-metadata')
42+
.insert({ source: 'legacy', roleEntityRef: groupPolicy });
43+
}
44+
});
45+
};
46+
47+
/**
48+
* @param { import("knex").Knex } knex
49+
* @returns { Promise<void> }
50+
*/
51+
exports.down = async function down(knex) {
52+
await knex.schema.dropTable('role-metadata');
53+
};

plugins/rbac-backend/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
},
2525
"dependencies": {
2626
"@backstage/backend-common": "^0.19.8",
27-
"@backstage/backend-plugin-api": "^0.5.4",
27+
"@backstage/backend-plugin-api": "^0.6.6",
28+
"@backstage/backend-test-utils": "^0.2.7",
2829
"@backstage/catalog-client": "^1.4.5",
2930
"@backstage/catalog-model": "^1.4.3",
3031
"@backstage/config": "^1.1.1",
@@ -51,7 +52,7 @@
5152
"@types/express": "4.17.20",
5253
"@types/node": "18.18.5",
5354
"@types/supertest": "2.0.16",
54-
"knex-mock-client": "2.0.0",
55+
"knex-mock-client": "2.0.1",
5556
"msw": "1.3.2",
5657
"supertest": "6.3.3"
5758
},

plugins/rbac-backend/src/database/conditional-storage.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import {
2-
PluginDatabaseManager,
3-
resolvePackagePath,
4-
} from '@backstage/backend-common';
51
import { ConflictError, InputError, NotFoundError } from '@backstage/errors';
62
import {
73
AuthorizeResult,
@@ -11,10 +7,6 @@ import {
117
import { Knex } from 'knex';
128

139
const CONDITIONAL_TABLE = 'policy-conditions';
14-
const migrationsDir = resolvePackagePath(
15-
'@janus-idp/backstage-plugin-rbac-backend', // Package name
16-
'migrations', // Migrations directory
17-
);
1810

1911
interface ConditionalPolicyDecisionDAO {
2012
result: AuthorizeResult.CONDITIONAL;
@@ -46,20 +38,6 @@ export interface ConditionalStorage {
4638
export class DataBaseConditionalStorage implements ConditionalStorage {
4739
public constructor(private readonly knex: Knex<any, any[]>) {}
4840

49-
static async create(
50-
databaseManager: PluginDatabaseManager,
51-
): Promise<ConditionalStorage> {
52-
const knex = await databaseManager.getClient();
53-
54-
if (!databaseManager.migrations?.skip) {
55-
await knex.migrate.latest({
56-
directory: migrationsDir,
57-
});
58-
}
59-
60-
return new DataBaseConditionalStorage(knex);
61-
}
62-
6341
async getConditions(
6442
pluginId: string,
6543
resourceType: string,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {
2+
PluginDatabaseManager,
3+
resolvePackagePath,
4+
} from '@backstage/backend-common';
5+
6+
const migrationsDir = resolvePackagePath(
7+
'@janus-idp/backstage-plugin-rbac-backend', // Package name
8+
'migrations', // Migrations directory
9+
);
10+
11+
export async function migrate(databaseManager: PluginDatabaseManager) {
12+
const knex = await databaseManager.getClient();
13+
14+
if (!databaseManager.migrations?.skip) {
15+
await knex.migrate.latest({
16+
directory: migrationsDir,
17+
});
18+
}
19+
}

0 commit comments

Comments
 (0)