diff --git a/plugins/rbac/dev/index.tsx b/plugins/rbac/dev/index.tsx index 5318d901f5..dc9acc00af 100644 --- a/plugins/rbac/dev/index.tsx +++ b/plugins/rbac/dev/index.tsx @@ -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; @@ -111,6 +114,25 @@ class MockRBACApi implements RBACAPI { ): Promise { return { status: 200 } as Response; } + + async getRoleConditions( + roleRef: string, + ): Promise[] | Response> { + return mockConditions.filter(mc => mc.roleEntityRef === roleRef); + } + + async updateConditionalPolicies( + _conditionId: number, + _data: RoleBasedConditions, + ): Promise { + return { status: 200 } as Response; + } + + async deleteConditionalPolicies( + _conditionId: number, + ): Promise { + return { status: 204 } as Response; + } } const mockPermissionApi = new MockPermissionApi(); diff --git a/plugins/rbac/src/__fixtures__/mockConditions.ts b/plugins/rbac/src/__fixtures__/mockConditions.ts new file mode 100644 index 0000000000..604c135135 --- /dev/null +++ b/plugins/rbac/src/__fixtures__/mockConditions.ts @@ -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[] = + [ + { + 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'], + }, + ]; diff --git a/plugins/rbac/src/__fixtures__/mockFormValues.tsx b/plugins/rbac/src/__fixtures__/mockFormValues.tsx new file mode 100644 index 0000000000..85569901da --- /dev/null +++ b/plugins/rbac/src/__fixtures__/mockFormValues.tsx @@ -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', + }, + }, + }, + ], +}; diff --git a/plugins/rbac/src/api/RBACBackendClient.ts b/plugins/rbac/src/api/RBACBackendClient.ts index 55d9055a6e..7fb7a2e451 100644 --- a/plugins/rbac/src/api/RBACBackendClient.ts +++ b/plugins/rbac/src/api/RBACBackendClient.ts @@ -5,9 +5,11 @@ import { } from '@backstage/core-plugin-api'; import { + PermissionAction, PermissionPolicy, Role, RoleBasedPolicy, + RoleConditionalPolicyDecision, } from '@janus-idp/backstage-plugin-rbac-common'; import { @@ -46,6 +48,16 @@ export type RBACAPI = { createConditionalPermission: ( conditionalPermission: RoleBasedConditions, ) => Promise; + getRoleConditions: ( + roleRef: string, + ) => Promise[] | Response>; + updateConditionalPolicies: ( + conditionId: number, + data: RoleBasedConditions, + ) => Promise; + deleteConditionalPolicies: ( + conditionId: number, + ) => Promise; }; export type Options = { @@ -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; + } } diff --git a/plugins/rbac/src/components/ConditionalAccess/ConditionalAccessSidebar.tsx b/plugins/rbac/src/components/ConditionalAccess/ConditionalAccessSidebar.tsx index c3c91f9dd4..b29ddcc982 100644 --- a/plugins/rbac/src/components/ConditionalAccess/ConditionalAccessSidebar.tsx +++ b/plugins/rbac/src/components/ConditionalAccess/ConditionalAccessSidebar.tsx @@ -43,7 +43,6 @@ type ConditionalAccessSidebarProps = { open: boolean; onClose: () => void; onSave: (conditions: ConditionsData) => void; - onRemoveAll: () => void; selPluginResourceType: string; conditionRulesData?: RulesData; conditionsFormVal?: ConditionsData; @@ -53,7 +52,6 @@ export const ConditionalAccessSidebar = ({ open, onClose, onSave, - onRemoveAll, selPluginResourceType, conditionRulesData, conditionsFormVal, @@ -100,7 +98,6 @@ export const ConditionalAccessSidebar = ({ conditionsFormVal={conditionsFormVal} onClose={onClose} onSave={onSave} - onRemoveAll={onRemoveAll} /> diff --git a/plugins/rbac/src/components/ConditionalAccess/ConditionsForm.tsx b/plugins/rbac/src/components/ConditionalAccess/ConditionsForm.tsx index fe0d3bcca1..4e6393bf09 100644 --- a/plugins/rbac/src/components/ConditionalAccess/ConditionsForm.tsx +++ b/plugins/rbac/src/components/ConditionalAccess/ConditionsForm.tsx @@ -34,7 +34,6 @@ type ConditionFormProps = { selPluginResourceType: string; onClose: () => void; onSave: (conditions: ConditionsData) => void; - onRemoveAll: () => void; }; export const ConditionsForm = ({ @@ -43,7 +42,6 @@ export const ConditionsForm = ({ conditionsFormVal, onClose, onSave, - onRemoveAll, }: ConditionFormProps) => { const classes = useStyles(); const [conditions, setConditions] = React.useState( @@ -124,7 +122,6 @@ export const ConditionsForm = ({ params: {}, }, }); - onRemoveAll(); }} > Remove all diff --git a/plugins/rbac/src/components/ConditionalAccess/ConditionsFormRow.tsx b/plugins/rbac/src/components/ConditionalAccess/ConditionsFormRow.tsx index 18b8449820..f297895590 100644 --- a/plugins/rbac/src/components/ConditionalAccess/ConditionsFormRow.tsx +++ b/plugins/rbac/src/components/ConditionalAccess/ConditionsFormRow.tsx @@ -30,6 +30,7 @@ const useStyles = makeStyles(theme => ({ criteriaButton: { width: '100%', textTransform: 'none', + color: theme.palette.grey[700], }, addRuleButton: { color: theme.palette.primary.light, @@ -126,11 +127,7 @@ export const ConditionsFormRow = ({ return ( - + {conditionButtons.map(({ val, label }) => (