Skip to content

Commit

Permalink
feat(rbac): support for updating/deleting conditional permissions (ja…
Browse files Browse the repository at this point in the history
  • Loading branch information
divyanshiGupta committed May 15, 2024
1 parent 9bf3039 commit 2bb8308
Show file tree
Hide file tree
Showing 22 changed files with 953 additions and 23 deletions.
24 changes: 23 additions & 1 deletion plugins/rbac/dev/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,21 @@ import {
import { createDevAppThemes } from '@redhat-developer/red-hat-developer-hub-theme';

import {
PermissionAction,
PermissionPolicy,
Role,
RoleBasedPolicy,
RoleConditionalPolicyDecision,
} from '@janus-idp/backstage-plugin-rbac-common';

import { mockConditionRules } from '../src/__fixtures__/mockConditionRules';
import { mockConditions } from '../src/__fixtures__/mockConditions';
import { mockMembers } from '../src/__fixtures__/mockMembers';
import { mockPermissionPolicies } from '../src/__fixtures__/mockPermissionPolicies';
import { mockPolicies } from '../src/__fixtures__/mockPolicies';
import { RBACAPI, rbacApiRef } from '../src/api/RBACBackendClient';
import { RbacPage, rbacPlugin } from '../src/plugin';
import { MemberEntity, RoleBasedConditions } from '../src/types';
import { MemberEntity, RoleBasedConditions, RoleError } from '../src/types';

class MockRBACApi implements RBACAPI {
readonly resources;
Expand Down Expand Up @@ -111,6 +114,25 @@ class MockRBACApi implements RBACAPI {
): Promise<Response> {
return { status: 200 } as Response;
}

async getRoleConditions(
roleRef: string,
): Promise<RoleConditionalPolicyDecision<PermissionAction>[] | Response> {
return mockConditions.filter(mc => mc.roleEntityRef === roleRef);
}

async updateConditionalPolicies(
_conditionId: number,
_data: RoleBasedConditions,
): Promise<RoleError | Response> {
return { status: 200 } as Response;
}

async deleteConditionalPolicies(
_conditionId: number,
): Promise<RoleError | Response> {
return { status: 204 } as Response;
}
}

const mockPermissionApi = new MockPermissionApi();
Expand Down
45 changes: 45 additions & 0 deletions plugins/rbac/src/__fixtures__/mockConditions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { AuthorizeResult } from '@backstage/plugin-permission-common';

import {
PermissionAction,
RoleConditionalPolicyDecision,
} from '@janus-idp/backstage-plugin-rbac-common';

export const mockConditions: RoleConditionalPolicyDecision<PermissionAction>[] =
[
{
id: 1,
result: AuthorizeResult.CONDITIONAL,
pluginId: 'catalog',
resourceType: 'catalog-entity',
conditions: {
rule: 'HAS_ANNOTATION',
resourceType: 'catalog-entity',
params: { annotation: 'temp' },
},
roleEntityRef: 'role:default/rbac_admin',
permissionMapping: ['read'],
},
{
id: 2,
result: AuthorizeResult.CONDITIONAL,
pluginId: 'catalog',
resourceType: 'catalog-entity',
conditions: {
allOf: [
{
rule: 'HAS_LABEL',
resourceType: 'catalog-entity',
params: { label: 'temp' },
},
{
rule: 'HAS_METADATA',
resourceType: 'catalog-entity',
params: { key: 'status' },
},
],
},
roleEntityRef: 'role:default/rbac_admin',
permissionMapping: ['delete', 'update'],
},
];
48 changes: 48 additions & 0 deletions plugins/rbac/src/__fixtures__/mockFormValues.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export const mockFormCurrentValues = {
kind: 'user',
name: 'div',
namespace: 'default',
selectedMembers: [],
permissionPoliciesRows: [
{
permission: 'catalog-entity',
policies: [{ policy: 'read', effect: 'allow' }],
isResourced: true,
plugin: 'catalog',
conditions: {
condition: {
rule: 'HAS_LABEL',
params: {
label: 'temp',
},
resourceType: 'catalog-entity',
},
},
},
],
};

export const mockFormInitialValues = {
kind: 'user',
name: 'div',
namespace: 'default',
selectedMembers: [],
permissionPoliciesRows: [
{
id: 1,
permission: 'catalog-entity',
policies: [{ policy: 'read', effect: 'allow' }],
isResourced: true,
plugin: 'catalog',
conditions: {
condition: {
rule: 'HAS_LABEL',
params: {
label: 'temp',
},
resourceType: 'catalog-entity',
},
},
},
],
};
79 changes: 79 additions & 0 deletions plugins/rbac/src/api/RBACBackendClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import {
} from '@backstage/core-plugin-api';

import {
PermissionAction,
PermissionPolicy,
Role,
RoleBasedPolicy,
RoleConditionalPolicyDecision,
} from '@janus-idp/backstage-plugin-rbac-common';

import {
Expand Down Expand Up @@ -46,6 +48,16 @@ export type RBACAPI = {
createConditionalPermission: (
conditionalPermission: RoleBasedConditions,
) => Promise<RoleError | Response>;
getRoleConditions: (
roleRef: string,
) => Promise<RoleConditionalPolicyDecision<PermissionAction>[] | Response>;
updateConditionalPolicies: (
conditionId: number,
data: RoleBasedConditions,
) => Promise<RoleError | Response>;
deleteConditionalPolicies: (
conditionId: number,
) => Promise<RoleError | Response>;
};

export type Options = {
Expand Down Expand Up @@ -362,4 +374,71 @@ export class RBACBackendClient implements RBACAPI {
}
return jsonResponse;
}

async getRoleConditions(roleRef: string) {
const { token: idToken } = await this.identityApi.getCredentials();
const backendUrl = this.configApi.getString('backend.baseUrl');
const jsonResponse = await fetch(
`${backendUrl}/api/permission/roles/conditions?roleEntityRef=${roleRef}`,
{
headers: {
...(idToken && { Authorization: `Bearer ${idToken}` }),
'Content-Type': 'application/json',
},
},
);
if (jsonResponse.status !== 200) {
return jsonResponse;
}
return jsonResponse.json();
}

async updateConditionalPolicies(
conditionId: number,
data: RoleBasedConditions,
) {
const { token: idToken } = await this.identityApi.getCredentials();
const backendUrl = this.configApi.getString('backend.baseUrl');
const jsonResponse = await fetch(
`${backendUrl}/api/permission/roles/conditions/${conditionId}}`,
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
...(idToken && { Authorization: `Bearer ${idToken}` }),
},
body: JSON.stringify(data),
},
);
if (jsonResponse.status !== 200 && jsonResponse.status !== 201) {
return jsonResponse.json();
}
return jsonResponse;
}

async deleteConditionalPolicies(conditionId: number) {
const { token: idToken } = await this.identityApi.getCredentials();
const backendUrl = this.configApi.getString('backend.baseUrl');
const jsonResponse = await fetch(
`${backendUrl}/api/permission/roles/conditions/${conditionId}`,
{
headers: {
...(idToken && { Authorization: `Bearer ${idToken}` }),
'Content-Type': 'application/json',
Accept: 'application/json',
},
method: 'DELETE',
},
);

if (
jsonResponse.status !== 200 &&
jsonResponse.status !== 201 &&
jsonResponse.status !== 204
) {
return jsonResponse.json();
}
return jsonResponse;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ type ConditionalAccessSidebarProps = {
open: boolean;
onClose: () => void;
onSave: (conditions: ConditionsData) => void;
onRemoveAll: () => void;
selPluginResourceType: string;
conditionRulesData?: RulesData;
conditionsFormVal?: ConditionsData;
Expand All @@ -53,7 +52,6 @@ export const ConditionalAccessSidebar = ({
open,
onClose,
onSave,
onRemoveAll,
selPluginResourceType,
conditionRulesData,
conditionsFormVal,
Expand Down Expand Up @@ -100,7 +98,6 @@ export const ConditionalAccessSidebar = ({
conditionsFormVal={conditionsFormVal}
onClose={onClose}
onSave={onSave}
onRemoveAll={onRemoveAll}
/>
</Box>
</Drawer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ type ConditionFormProps = {
selPluginResourceType: string;
onClose: () => void;
onSave: (conditions: ConditionsData) => void;
onRemoveAll: () => void;
};

export const ConditionsForm = ({
Expand All @@ -43,7 +42,6 @@ export const ConditionsForm = ({
conditionsFormVal,
onClose,
onSave,
onRemoveAll,
}: ConditionFormProps) => {
const classes = useStyles();
const [conditions, setConditions] = React.useState<ConditionsData>(
Expand Down Expand Up @@ -124,7 +122,6 @@ export const ConditionsForm = ({
params: {},
},
});
onRemoveAll();
}}
>
Remove all
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const useStyles = makeStyles(theme => ({
criteriaButton: {
width: '100%',
textTransform: 'none',
color: theme.palette.grey[700],
},
addRuleButton: {
color: theme.palette.primary.light,
Expand Down Expand Up @@ -126,11 +127,7 @@ export const ConditionsFormRow = ({

return (
<Box className={classes.conditionRow}>
<ButtonGroup
size="large"
className={classes.criteriaButtonGroup}
color="info"
>
<ButtonGroup size="large" className={classes.criteriaButtonGroup}>
{conditionButtons.map(({ val, label }) => (
<Button
variant="outlined"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ beforeEach(() => {
usePermissionPoliciesMock.mockReturnValue({
loading: false,
data: usePermissionPoliciesMockData,
conditionsData: [],
retry: { policiesRetry: jest.fn(), permissionPoliciesRetry: jest.fn() },
error: new Error(''),
});
Expand Down
4 changes: 2 additions & 2 deletions plugins/rbac/src/components/CreateRole/EditRolePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const EditRolePage = () => {
roleName ? `${roleKind}:${roleNamespace}/${roleName}` : '',
);

const { data: permissionPolicies } = usePermissionPolicies(
const { data: permissionPolicies, conditionsData } = usePermissionPolicies(
`${roleKind}:${roleNamespace}/${roleName}`,
);

Expand All @@ -35,7 +35,7 @@ export const EditRolePage = () => {
kind: roleKind || 'role',
description: role?.metadata?.description ?? '',
selectedMembers,
permissionPoliciesRows: permissionPolicies,
permissionPoliciesRows: [...conditionsData, ...permissionPolicies],
};
const renderPage = () => {
if (loading) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export const PermissionPoliciesFormRow = ({
className={classes.removeButton}
onClick={() => onRemove()}
disabled={rowCount === 1}
data-testid={`${rowName}-remove`}
>
<RemoveIcon id={`${rowName}-remove`} />
</IconButton>
Expand All @@ -207,7 +208,6 @@ export const PermissionPoliciesFormRow = ({
onAddConditions(conditions);
setSidebarOpen(false);
}}
onRemoveAll={() => onAddConditions(undefined)}
conditionsFormVal={permissionPoliciesRowData.conditions}
selPluginResourceType={permissionPoliciesRowData.permission}
conditionRulesData={
Expand Down
Loading

0 comments on commit 2bb8308

Please sign in to comment.