Skip to content

Commit 966b2b2

Browse files
fix(rbac): fix handling condition action conflicts (#1781)
* fix(rbac): fix handling condition action conflicts Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): refactor condition conflict check, add more unit test Signed-off-by: Oleksandr Andriienko <[email protected]> --------- Signed-off-by: Oleksandr Andriienko <[email protected]>
1 parent 206cbbc commit 966b2b2

File tree

4 files changed

+242
-75
lines changed

4 files changed

+242
-75
lines changed

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

Lines changed: 47 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ export interface ConditionalStorage {
3232
createCondition(
3333
conditionalDecision: RoleConditionalPolicyDecision<PermissionInfo>,
3434
): Promise<number>;
35-
findUniqueCondition(
35+
checkConflictedConditions(
3636
roleEntityRef: string,
3737
resourceType: string,
38+
pluginId: string,
3839
queryPermissionNames: string[],
39-
): Promise<RoleConditionalPolicyDecision<PermissionInfo> | undefined>;
40+
idToExclude?: number,
41+
): Promise<void>;
4042
getCondition(
4143
id: number,
4244
): Promise<RoleConditionalPolicyDecision<PermissionInfo> | undefined>;
@@ -57,7 +59,7 @@ export class DataBaseConditionalStorage implements ConditionalStorage {
5759
actions?: PermissionAction[],
5860
permissionNames?: string[],
5961
): Promise<RoleConditionalPolicyDecision<PermissionInfo>[]> {
60-
const daoRaws = await this.knex?.table(CONDITIONAL_TABLE).where(builder => {
62+
const daoRaws = await this.knex.table(CONDITIONAL_TABLE).where(builder => {
6163
if (pluginId) {
6264
builder.where('pluginId', pluginId);
6365
}
@@ -100,22 +102,16 @@ export class DataBaseConditionalStorage implements ConditionalStorage {
100102
async createCondition(
101103
conditionalDecision: RoleConditionalPolicyDecision<PermissionInfo>,
102104
): Promise<number> {
103-
const condition = await this.findUniqueCondition(
105+
await this.checkConflictedConditions(
104106
conditionalDecision.roleEntityRef,
105107
conditionalDecision.resourceType,
106-
conditionalDecision.permissionMapping.map(permInfo => permInfo.name),
108+
conditionalDecision.pluginId,
109+
conditionalDecision.permissionMapping.map(permInfo => permInfo.action),
107110
);
108-
if (condition) {
109-
throw new ConflictError(
110-
`A condition with resource type '${condition.resourceType}'` +
111-
` and permission '${JSON.stringify(condition.permissionMapping)}'` +
112-
` has already been stored for role '${conditionalDecision.roleEntityRef}'`,
113-
);
114-
}
115111

116112
const conditionRaw = this.toDAO(conditionalDecision);
117113
const result = await this.knex
118-
?.table(CONDITIONAL_TABLE)
114+
.table(CONDITIONAL_TABLE)
119115
.insert<ConditionalPolicyDecisionDAO>(conditionRaw)
120116
.returning('id');
121117
if (result && result?.length > 0) {
@@ -125,42 +121,53 @@ export class DataBaseConditionalStorage implements ConditionalStorage {
125121
throw new Error(`Failed to create the condition.`);
126122
}
127123

128-
async findUniqueCondition(
124+
async checkConflictedConditions(
129125
roleEntityRef: string,
130126
resourceType: string,
131-
queryPermissionNames: string[],
132-
): Promise<RoleConditionalPolicyDecision<PermissionInfo> | undefined> {
133-
const daoRaws = await this.knex
134-
?.table(CONDITIONAL_TABLE)
135-
.where('roleEntityRef', roleEntityRef)
136-
.where('resourceType', resourceType);
127+
pluginId: string,
128+
queryConditionActions: PermissionAction[],
129+
idToExclude?: number,
130+
): Promise<void> {
131+
let conditionsForTheSameResource = await this.filterConditions(
132+
roleEntityRef,
133+
pluginId,
134+
resourceType,
135+
);
136+
conditionsForTheSameResource = conditionsForTheSameResource.filter(
137+
c => c.id !== idToExclude,
138+
);
137139

138-
if (daoRaws) {
139-
const conditions = daoRaws.map(daoRaw =>
140-
this.daoToConditionalDecision(daoRaw),
140+
if (conditionsForTheSameResource) {
141+
const conflictedCondition = conditionsForTheSameResource.find(
142+
condition => {
143+
const conditionActions = condition.permissionMapping.map(
144+
permInfo => permInfo.action,
145+
);
146+
return queryConditionActions.some(action =>
147+
conditionActions.includes(action),
148+
);
149+
},
141150
);
142151

143-
return conditions.find(condition => {
144-
const conditionPermissionNames = condition.permissionMapping.map(
145-
permInfo => permInfo.name,
152+
if (conflictedCondition) {
153+
const conflictedActions = queryConditionActions.filter(action =>
154+
conflictedCondition.permissionMapping.some(p => p.action === action),
146155
);
147-
const isContainTheSamePermissions = queryPermissionNames.every(name =>
148-
conditionPermissionNames.includes(name),
156+
throw new ConflictError(
157+
`Found condition with conflicted permission action '${JSON.stringify(
158+
conflictedActions,
159+
)}'. Role could have multiple ` +
160+
`conditions for the same resource type '${conflictedCondition.resourceType}', but with different permission action sets.`,
149161
);
150-
return (
151-
isContainTheSamePermissions &&
152-
conditionPermissionNames.length === queryPermissionNames.length
153-
);
154-
});
162+
}
155163
}
156-
return undefined;
157164
}
158165

159166
async getCondition(
160167
id: number,
161168
): Promise<RoleConditionalPolicyDecision<PermissionInfo> | undefined> {
162169
const daoRaw = await this.knex
163-
?.table(CONDITIONAL_TABLE)
170+
.table(CONDITIONAL_TABLE)
164171
.where('id', id)
165172
.first();
166173

@@ -187,35 +194,18 @@ export class DataBaseConditionalStorage implements ConditionalStorage {
187194
throw new NotFoundError(`Condition with id ${id} was not found`);
188195
}
189196

190-
const conditionsForTheSameResource = await this.filterConditions(
197+
await this.checkConflictedConditions(
191198
conditionalDecision.roleEntityRef,
192-
conditionalDecision.pluginId,
193199
conditionalDecision.resourceType,
200+
conditionalDecision.pluginId,
201+
conditionalDecision.permissionMapping.map(perm => perm.action),
202+
id,
194203
);
195204

196-
for (const permission of conditionalDecision.permissionMapping) {
197-
for (const conditionToCompare of conditionsForTheSameResource) {
198-
if (conditionToCompare.id === id) {
199-
continue;
200-
}
201-
const conditionPermNames = conditionToCompare.permissionMapping.map(
202-
perm => perm.name,
203-
);
204-
if (conditionPermNames.includes(permission.name)) {
205-
throw new ConflictError(
206-
`Found condition with conflicted permission '${JSON.stringify(
207-
permission,
208-
)}'. Role could have multiple ` +
209-
`conditions for the same resource type '${conditionalDecision.resourceType}', but with different permission name and action sets.`,
210-
);
211-
}
212-
}
213-
}
214-
215205
const conditionRaw = this.toDAO(conditionalDecision);
216206
conditionRaw.id = id;
217207
const result = await this.knex
218-
?.table(CONDITIONAL_TABLE)
208+
.table(CONDITIONAL_TABLE)
219209
.where('id', conditionRaw.id)
220210
.update<ConditionalPolicyDecisionDAO>(conditionRaw)
221211
.returning('id');

0 commit comments

Comments
 (0)