Skip to content

Commit

Permalink
feat(rbac): nested condition
Browse files Browse the repository at this point in the history
Signed-off-by: Yi Cai <yicai@redhat.com>
  • Loading branch information
ciiay committed Jul 1, 2024
1 parent 4dcf429 commit e9807aa
Show file tree
Hide file tree
Showing 8 changed files with 1,103 additions and 167 deletions.
81 changes: 81 additions & 0 deletions plugins/rbac/src/__fixtures__/mockConditions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,85 @@ export const mockConditions: RoleConditionalPolicyDecision<PermissionAction>[] =
roleEntityRef: 'role:default/rbac_admin',
permissionMapping: ['delete', 'update'],
},
{
id: 3,
result: AuthorizeResult.CONDITIONAL,
pluginId: 'catalog',
resourceType: 'catalog-entity',
conditions: {
anyOf: [
{
rule: 'IS_ENTITY_OWNER',
resourceType: 'catalog-entity',
params: {
claims: ['user:default/ciiay'],
},
},
{
rule: 'IS_ENTITY_KIND',
resourceType: 'catalog-entity',
params: { kinds: ['Group'] },
},
{
allOf: [
{
rule: 'IS_ENTITY_OWNER',
resourceType: 'catalog-entity',
params: {
claims: ['user:default/ciiay'],
},
},
{
rule: 'IS_ENTITY_KIND',
resourceType: 'catalog-entity',
params: {
kinds: ['User'],
},
},
],
},
],
},
roleEntityRef: 'role:default/rbac_admin',
permissionMapping: ['read', 'delete', 'update'],
},
{
id: 4,
result: AuthorizeResult.CONDITIONAL,
pluginId: 'catalog',
resourceType: 'catalog-entity',
conditions: {
not: {
rule: 'HAS_LABEL',
resourceType: 'catalog-entity',
params: { label: 'temp' },
},
},
roleEntityRef: 'role:default/rbac_admin',
permissionMapping: ['delete', 'update'],
},
{
id: 5,
result: AuthorizeResult.CONDITIONAL,
pluginId: 'scaffolder',
resourceType: 'scaffolder-template',
conditions: {
not: {
anyOf: [
{
rule: 'HAS_TAG',
resourceType: 'scaffolder-template',
params: { tag: 'dev' },
},
{
rule: 'HAS_TAG',
resourceType: 'scaffolder-template',
params: { tag: 'test' },
},
],
},
},
roleEntityRef: 'role:default/rbac_admin',
permissionMapping: ['read'],
},
];
92 changes: 84 additions & 8 deletions plugins/rbac/src/components/ConditionalAccess/ConditionsForm.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import React from 'react';

import { PermissionCondition } from '@backstage/plugin-permission-common';

import { makeStyles } from '@material-ui/core';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import { RJSFValidationError } from '@rjsf/utils';

import { ConditionsFormRow } from './ConditionsFormRow';
import { criterias } from './const';
import { ConditionsData, RuleParamsErrors, RulesData } from './types';
import {
Condition,
ConditionsData,
RuleParamsErrors,
RulesData,
} from './types';

const useStyles = makeStyles(theme => ({
form: {
Expand Down Expand Up @@ -58,30 +66,98 @@ export const ConditionsForm = ({
);
const [errors, setErrors] = React.useState<RuleParamsErrors>();

const handleSetErrors = (
newErrors: RJSFValidationError[],
currentCriteria: string,
nestedCriteria?: string,
nestedConditionIndex?: number,
ruleIndex?: number,
removeErrors = false,
) => {
setErrors(prevErrors => {
const updatedErrors: RuleParamsErrors = { ...prevErrors };

let baseErrorKey = currentCriteria;
if (nestedConditionIndex !== undefined) {
baseErrorKey += `.${nestedConditionIndex}`;
}
if (nestedCriteria !== undefined) {
baseErrorKey += `.${nestedCriteria}`;
}
if (ruleIndex !== undefined) {
baseErrorKey += `.${ruleIndex}`;
}

if (removeErrors || newErrors.length === 0) {
delete updatedErrors[baseErrorKey];
Object.keys(updatedErrors).forEach(key => {
if (key.startsWith(`${baseErrorKey}.`)) {
delete updatedErrors[key];
}
});
} else {
updatedErrors[baseErrorKey] = newErrors;
}

return updatedErrors;
});
};

const [removeAllClicked, setRemoveAllClicked] =
React.useState<boolean>(false);

const flattenConditions = (
conditionData: Condition[],
): PermissionCondition[] => {
const flatConditions: PermissionCondition[] = [];

const processCondition = (condition: Condition) => {
if ('rule' in condition) {
flatConditions.push(condition);
} else {
if (condition.allOf) {
condition.allOf.forEach(processCondition);
}
if (condition.anyOf) {
condition.anyOf.forEach(processCondition);
}
if (condition.not) {
if ('rule' in condition.not) {
flatConditions.push(condition.not);
} else {
processCondition(condition.not);
}
}
}
};
conditionData.forEach(processCondition);
return flatConditions;
};

const isNoRuleSelected = () => {
switch (criteria) {
case criterias.condition: {
case criterias.condition:
return !conditions.condition?.rule;
}
case criterias.not: {
return !conditions.not?.rule;
const flatConditions = flattenConditions([conditions.not as Condition]);
return flatConditions.some(c => !c.rule);
}
case criterias.allOf: {
return !!conditions.allOf?.find(c => !c.rule);
const flatConditions = flattenConditions(conditions.allOf || []);
return flatConditions.some(c => !c.rule);
}
case criterias.anyOf: {
return !!conditions.anyOf?.find(c => !c.rule);
const flatConditions = flattenConditions(conditions.anyOf || []);
return flatConditions.some(c => !c.rule);
}
default:
return true;
}
};

const isSaveDisabled = () => {
const hasErrors = !!errors?.[criteria]?.length;
const hasErrors =
errors && Object.keys(errors).some(key => errors[key].length > 0);

if (removeAllClicked) return false;

Expand All @@ -102,7 +178,7 @@ export const ConditionsForm = ({
selPluginResourceType={selPluginResourceType}
onRuleChange={newCondition => setConditions(newCondition)}
setCriteria={setCriteria}
setErrors={setErrors}
handleSetErrors={handleSetErrors}
setRemoveAllClicked={setRemoveAllClicked}
/>
</Box>
Expand Down
Loading

0 comments on commit e9807aa

Please sign in to comment.