Skip to content

Commit 592498f

Browse files
fix(rbac): add additional validation for permission policies (#1908)
* fix(rbac): add additional validation for permission policies * fix(rbac): remove duplication permission action values (#1939) Signed-off-by: Oleksandr Andriienko <[email protected]> --------- Signed-off-by: Oleksandr Andriienko <[email protected]> Co-authored-by: Oleksandr Andriienko <[email protected]>
1 parent 8f1b079 commit 592498f

File tree

4 files changed

+52
-19
lines changed

4 files changed

+52
-19
lines changed

plugins/rbac-backend/src/service/policies-rest-api.test.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,7 +1240,7 @@ describe('REST policies api', () => {
12401240
effect: 'allow',
12411241
},
12421242
],
1243-
newPolicy: [{ permission: 'policy-entity', policy: 'write' }],
1243+
newPolicy: [{ permission: 'policy-entity', policy: 'create' }],
12441244
});
12451245

12461246
expect(result.statusCode).toEqual(400);
@@ -1261,7 +1261,7 @@ describe('REST policies api', () => {
12611261
effect: 'unknown',
12621262
},
12631263
],
1264-
newPolicy: [{ permission: 'policy-entity', policy: 'write' }],
1264+
newPolicy: [{ permission: 'policy-entity', policy: 'create' }],
12651265
});
12661266

12671267
expect(result.statusCode).toEqual(400);
@@ -1285,7 +1285,7 @@ describe('REST policies api', () => {
12851285
newPolicy: [
12861286
{
12871287
permission: 'policy-entity',
1288-
policy: 'write',
1288+
policy: 'create',
12891289
effect: 'allow',
12901290
},
12911291
],
@@ -1344,7 +1344,7 @@ describe('REST policies api', () => {
13441344
newPolicy: [
13451345
{
13461346
permission: 'policy-entity',
1347-
policy: 'write',
1347+
policy: 'create',
13481348
effect: 'allow',
13491349
},
13501350
],
@@ -1353,7 +1353,7 @@ describe('REST policies api', () => {
13531353
expect(result.statusCode).toEqual(409);
13541354
expect(result.body.error).toEqual({
13551355
name: 'ConflictError',
1356-
message: `Policy '[user:default/permission_admin, policy-entity, write, allow]' has been already stored`,
1356+
message: `Policy '[user:default/permission_admin, policy-entity, create, allow]' has been already stored`,
13571357
});
13581358
});
13591359

@@ -1465,7 +1465,7 @@ describe('REST policies api', () => {
14651465
mockEnforcer.hasPolicy = jest
14661466
.fn()
14671467
.mockImplementation(async (...param: string[]): Promise<boolean> => {
1468-
if (param[2] === 'write') {
1468+
if (param[2] === 'create') {
14691469
return false;
14701470
}
14711471
return true;
@@ -1489,7 +1489,7 @@ describe('REST policies api', () => {
14891489
newPolicy: [
14901490
{
14911491
permission: 'policy-entity',
1492-
policy: 'write',
1492+
policy: 'create',
14931493
effect: 'allow',
14941494
},
14951495
],
@@ -1506,7 +1506,7 @@ describe('REST policies api', () => {
15061506
mockEnforcer.hasPolicy = jest
15071507
.fn()
15081508
.mockImplementation(async (...param: string[]): Promise<boolean> => {
1509-
if (param[2] === 'write') {
1509+
if (param[2] === 'create') {
15101510
return false;
15111511
}
15121512
return true;
@@ -1532,7 +1532,7 @@ describe('REST policies api', () => {
15321532
newPolicy: [
15331533
{
15341534
permission: 'policy-entity',
1535-
policy: 'write',
1535+
policy: 'create',
15361536
effect: 'allow',
15371537
},
15381538
],
@@ -1549,7 +1549,7 @@ describe('REST policies api', () => {
15491549
mockEnforcer.hasPolicy = jest
15501550
.fn()
15511551
.mockImplementation(async (...param: string[]): Promise<boolean> => {
1552-
if (param[2] === 'write') {
1552+
if (param[2] === 'create') {
15531553
return false;
15541554
}
15551555
return true;
@@ -1569,7 +1569,7 @@ describe('REST policies api', () => {
15691569
newPolicy: [
15701570
{
15711571
permission: 'policy-entity',
1572-
policy: 'write',
1572+
policy: 'create',
15731573
effect: 'allow',
15741574
},
15751575
],
@@ -1582,7 +1582,7 @@ describe('REST policies api', () => {
15821582
mockEnforcer.hasPolicy = jest
15831583
.fn()
15841584
.mockImplementation(async (...param: string[]): Promise<boolean> => {
1585-
if (param[2] === 'write') {
1585+
if (param[2] === 'create') {
15861586
return false;
15871587
}
15881588
return true;
@@ -1606,7 +1606,7 @@ describe('REST policies api', () => {
16061606
newPolicy: [
16071607
{
16081608
permission: 'policy-entity',
1609-
policy: 'write',
1609+
policy: 'create',
16101610
effect: 'allow',
16111611
},
16121612
{
@@ -1628,7 +1628,7 @@ describe('REST policies api', () => {
16281628
mockEnforcer.hasPolicy = jest
16291629
.fn()
16301630
.mockImplementation(async (...param: string[]): Promise<boolean> => {
1631-
if (param[2] === 'write') {
1631+
if (param[2] === 'update') {
16321632
return false;
16331633
}
16341634
return true;
@@ -1652,12 +1652,12 @@ describe('REST policies api', () => {
16521652
newPolicy: [
16531653
{
16541654
permission: 'policy-entity',
1655-
policy: 'write',
1655+
policy: 'update',
16561656
effect: 'allow',
16571657
},
16581658
{
16591659
permission: 'policy-entity',
1660-
policy: 'write',
1660+
policy: 'update',
16611661
effect: 'allow',
16621662
},
16631663
],
@@ -1666,7 +1666,7 @@ describe('REST policies api', () => {
16661666
expect(result.statusCode).toBe(409);
16671667
expect(result.body.error).toEqual({
16681668
name: 'ConflictError',
1669-
message: `Duplicate polices found; user:default/permission_admin, policy-entity, write, allow is a duplicate`,
1669+
message: `Duplicate polices found; user:default/permission_admin, policy-entity, update, allow is a duplicate`,
16701670
});
16711671
});
16721672

@@ -3649,7 +3649,7 @@ describe('REST policies api', () => {
36493649
mockEnforcer.hasPolicy = jest
36503650
.fn()
36513651
.mockImplementation(async (...param: string[]): Promise<boolean> => {
3652-
if (param[2] === 'write') {
3652+
if (param[2] === 'create') {
36533653
return false;
36543654
}
36553655
return true;

plugins/rbac-backend/src/validation/policies-validation.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,20 @@ describe('rest data validation', () => {
6868
expect(err?.message).toEqual(`'policy' field must not be empty`);
6969
});
7070

71+
it('should return an error when policy has an invalid value', () => {
72+
const policy: RoleBasedPolicy = {
73+
entityReference: 'user:default/guest',
74+
permission: 'catalog-entity',
75+
policy: 'invalid-policy',
76+
effect: 'allow',
77+
};
78+
const err = validatePolicy(policy);
79+
expect(err).toBeTruthy();
80+
expect(err?.message).toEqual(
81+
`'policy' has invalid value: 'invalid-policy'. It should be one of: create, read, update, delete, use`,
82+
);
83+
});
84+
7185
it('should return an error when effect is empty', () => {
7286
const policy: RoleBasedPolicy = {
7387
entityReference: 'user:default/guest',

plugins/rbac-backend/src/validation/policies-validation.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { AuthorizeResult } from '@backstage/plugin-permission-common';
55
import { Enforcer } from 'casbin';
66

77
import {
8+
isValidPermissionAction,
9+
PermissionActionValues,
810
Role,
911
RoleBasedPolicy,
1012
Source,
@@ -58,6 +60,10 @@ export function validatePolicy(policy: RoleBasedPolicy): Error | undefined {
5860

5961
if (!policy.policy) {
6062
return new Error(`'policy' field must not be empty`);
63+
} else if (!isValidPermissionAction(policy.policy)) {
64+
return new Error(
65+
`'policy' has invalid value: '${policy.policy}'. It should be one of: ${PermissionActionValues.join(', ')}`,
66+
);
6167
}
6268

6369
if (!policy.effect) {

plugins/rbac-common/src/types.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,24 @@ export type NonEmptyArray<T> = [T, ...T[]];
7373
// Permission framework attributes action has values: 'create' | 'read' | 'update' | 'delete' | undefined.
7474
// But we are introducing an action named "use" when action does not exist('undefined') to avoid
7575
// a more complicated model with multiple policy and request shapes.
76-
export type PermissionAction = 'create' | 'read' | 'update' | 'delete' | 'use';
76+
export const PermissionActionValues = [
77+
'create',
78+
'read',
79+
'update',
80+
'delete',
81+
'use',
82+
] as const;
83+
export type PermissionAction = (typeof PermissionActionValues)[number];
7784
export const toPermissionAction = (
7885
attr: PermissionAttributes,
7986
): PermissionAction => attr.action ?? 'use';
8087

88+
export function isValidPermissionAction(
89+
action: string,
90+
): action is PermissionAction {
91+
return (PermissionActionValues as readonly string[]).includes(action);
92+
}
93+
8194
export type PermissionInfo = {
8295
name: string;
8396
action: PermissionAction;

0 commit comments

Comments
 (0)