Skip to content

Commit

Permalink
feat(rbac): add conditional access button and sidebar
Browse files Browse the repository at this point in the history
  • Loading branch information
divyanshiGupta committed Apr 3, 2024
1 parent 1df3592 commit a33e169
Show file tree
Hide file tree
Showing 10 changed files with 566 additions and 66 deletions.
5 changes: 5 additions & 0 deletions plugins/rbac/dev/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
RoleBasedPolicy,
} from '@janus-idp/backstage-plugin-rbac-common';

import { mockConditionRules } from '../src/__fixtures__/mockConditionRules';
import { mockMembers } from '../src/__fixtures__/mockMembers';
import { mockPermissionPolicies } from '../src/__fixtures__/mockPermissionPolicies';
import { mockPolicies } from '../src/__fixtures__/mockPolicies';
Expand Down Expand Up @@ -108,6 +109,10 @@ class MockRBACApi implements RBACAPI {
async createPolicies(_policies: RoleBasedPolicy[]): Promise<Response> {
return { status: 200 } as Response;
}

async getPluginsConditionRules(): Promise<any | Response> {
return mockConditionRules;
}
}

const mockPermissionApi = new MockPermissionApi({ result: 'ALLOW' });
Expand Down
227 changes: 227 additions & 0 deletions plugins/rbac/src/__fixtures__/mockConditionRules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
export const mockConditionRules = [
{
pluginId: 'catalog',
rules: [
{
name: 'HAS_ANNOTATION',
description: 'Allow entities with the specified annotation',
resourceType: 'catalog-entity',
paramsSchema: {
type: 'object',
properties: {
annotation: {
type: 'string',
description: 'Name of the annotation to match on',
},
value: {
type: 'string',
description: 'Value of the annotation to match on',
},
},
required: ['annotation'],
additionalProperties: false,
$schema: 'http://json-schema.org/draft-07/schema#',
},
},
{
name: 'HAS_LABEL',
description: 'Allow entities with the specified label',
resourceType: 'catalog-entity',
paramsSchema: {
type: 'object',
properties: {
label: {
type: 'string',
description: 'Name of the label to match on',
},
},
required: ['label'],
additionalProperties: false,
$schema: 'http://json-schema.org/draft-07/schema#',
},
},
{
name: 'HAS_METADATA',
description: 'Allow entities with the specified metadata subfield',
resourceType: 'catalog-entity',
paramsSchema: {
type: 'object',
properties: {
key: {
type: 'string',
description: 'Property within the entities metadata to match on',
},
value: {
type: 'string',
description: 'Value of the given property to match on',
},
},
required: ['key'],
additionalProperties: false,
$schema: 'http://json-schema.org/draft-07/schema#',
},
},
{
name: 'HAS_SPEC',
description: 'Allow entities with the specified spec subfield',
resourceType: 'catalog-entity',
paramsSchema: {
type: 'object',
properties: {
key: {
type: 'string',
description: 'Property within the entities spec to match on',
},
value: {
type: 'string',
description: 'Value of the given property to match on',
},
},
required: ['key'],
additionalProperties: false,
$schema: 'http://json-schema.org/draft-07/schema#',
},
},
{
name: 'IS_ENTITY_KIND',
description: 'Allow entities matching a specified kind',
resourceType: 'catalog-entity',
paramsSchema: {
type: 'object',
properties: {
kinds: {
type: 'array',
items: { type: 'string' },
description: 'List of kinds to match at least one of',
},
},
required: ['kinds'],
additionalProperties: false,
$schema: 'http://json-schema.org/draft-07/schema#',
},
},
{
name: 'IS_ENTITY_OWNER',
description: 'Allow entities owned by a specified claim',
resourceType: 'catalog-entity',
paramsSchema: {
type: 'object',
properties: {
claims: {
type: 'array',
items: { type: 'string' },
description:
'List of claims to match at least one on within ownedBy',
},
},
required: ['claims'],
additionalProperties: false,
$schema: 'http://json-schema.org/draft-07/schema#',
},
},
],
},
{
pluginId: 'scaffolder',
rules: [
{
name: 'HAS_TAG',
description: 'Match parameters or steps with the given tag',
resourceType: 'scaffolder-template',
paramsSchema: {
type: 'object',
properties: {
tag: {
type: 'string',
description: 'Name of the tag to match on',
},
},
required: ['tag'],
additionalProperties: false,
$schema: 'http://json-schema.org/draft-07/schema#',
},
},
{
name: 'HAS_ACTION_ID',
description: 'Match actions with the given actionId',
resourceType: 'scaffolder-action',
paramsSchema: {
type: 'object',
properties: {
actionId: {
type: 'string',
description: 'Name of the actionId to match on',
},
},
required: ['actionId'],
additionalProperties: false,
$schema: 'http://json-schema.org/draft-07/schema#',
},
},
{
name: 'HAS_BOOLEAN_PROPERTY',
description: 'Allow actions with the specified property',
resourceType: 'scaffolder-action',
paramsSchema: {
type: 'object',
properties: {
key: {
type: 'string',
description: 'Property within the action parameters to match on',
},
value: {
type: 'boolean',
description: 'Value of the given property to match on',
},
},
required: ['key', 'value'],
additionalProperties: false,
$schema: 'http://json-schema.org/draft-07/schema#',
},
},
{
name: 'HAS_NUMBER_PROPERTY',
description: 'Allow actions with the specified property',
resourceType: 'scaffolder-action',
paramsSchema: {
type: 'object',
properties: {
key: {
type: 'string',
description: 'Property within the action parameters to match on',
},
value: {
type: 'number',
description: 'Value of the given property to match on',
},
},
required: ['key', 'value'],
additionalProperties: false,
$schema: 'http://json-schema.org/draft-07/schema#',
},
},
{
name: 'HAS_STRING_PROPERTY',
'descriptio* Connection #0 to host localhost left intactn':
'Allow actions with the specified property',
resourceType: 'scaffolder-action',
paramsSchema: {
type: 'object',
properties: {
key: {
type: 'string',
description: 'Property within the action parameters to match on',
},
value: {
type: 'string',
description: 'Value of the given property to match on',
},
},
required: ['key', 'value'],
additionalProperties: false,
$schema: 'http://json-schema.org/draft-07/schema#',
},
},
],
},
];
19 changes: 19 additions & 0 deletions plugins/rbac/src/api/RBACBackendClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export type RBACAPI = {
entityReference: string,
polices: RoleBasedPolicy[],
) => Promise<RoleError | Response>;
getPluginsConditionRules: () => Promise<any | Response>;
};

export type Options = {
Expand Down Expand Up @@ -312,4 +313,22 @@ export class RBACBackendClient implements RBACAPI {
}
return jsonResponse;
}

async getPluginsConditionRules() {
const { token: idToken } = await this.identityApi.getCredentials();
const backendUrl = this.configApi.getString('backend.baseUrl');
const jsonResponse = await fetch(
`${backendUrl}/api/permission/plugins/condition-rules`,
{
headers: {
...(idToken && { Authorization: `Bearer ${idToken}` }),
'Content-Type': 'application/json',
},
},
);
if (jsonResponse.status !== 200) {
return jsonResponse;
}
return jsonResponse.json();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React from 'react';

import { makeStyles } from '@material-ui/core';
import CloseIcon from '@mui/icons-material/Close';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Drawer from '@mui/material/Drawer';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';

const useDrawerStyles = makeStyles(() => ({
paper: {
width: '40%',
gap: '3%',
},
}));

const useDrawerContentStyles = makeStyles(theme => ({
sidebar: {
display: 'flex',
flexFlow: 'column',
height: '100%',
justifyContent: 'space-between',
},
header: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'baseline',
borderBottom: `2px solid ${theme.palette.border}`,
padding: theme.spacing(2.5),
},
body: {
padding: theme.spacing(2.5),
paddingTop: theme.spacing(1),
paddingBottom: theme.spacing(1),
flexGrow: 1,
},
footer: {
display: 'flex',
flexDirection: 'row',
gap: '15px',
alignItems: 'baseline',
borderTop: `2px solid ${theme.palette.border}`,
padding: theme.spacing(2.5),
},
}));

type ConditionalAccessSidebarProps = {
open: boolean;
onClose: () => void;
selPlugin: string;
rules?: any;
error?: Error;
};

export const ConditionalAccessSidebar = ({
open,
onClose,
selPlugin,
rules,
error,
}: ConditionalAccessSidebarProps) => {
const classes = useDrawerStyles();
const contentClasses = useDrawerContentStyles();
return (
<Drawer
anchor="right"
open={open}
data-testid="rules-sidebar"
classes={{
paper: classes.paper,
}}
>
<Box className={contentClasses.sidebar}>
<Box className={contentClasses.header}>
<Typography variant="h5">
<span style={{ fontWeight: '500' }}>
Conditional access for the
</span>{' '}
{selPlugin} plugin
</Typography>
<IconButton
key="dismiss"
title="Close the drawer"
onClick={onClose}
color="inherit"
>
<CloseIcon fontSize="small" />
</IconButton>
</Box>
<Box className={contentClasses.body}>
{rules ? (
<>
<Typography variant="body2">Rules</Typography>
<ul>
{rules.map((rule: any) => (
<li key={rule.name}>{rule.name}</li>
))}
</ul>
</>
) : (
<Typography variant="body2">
{error ? error.message : 'No rules found'}
</Typography>
)}
</Box>
<Box className={contentClasses.footer}>
<Button variant="contained" disabled>
Save
</Button>{' '}
<Button variant="outlined" onClick={onClose}>
Cancel
</Button>
</Box>
</Box>
</Drawer>
);
};
Loading

0 comments on commit a33e169

Please sign in to comment.