Skip to content

Commit

Permalink
feat(rbac): list roles (#937)
Browse files Browse the repository at this point in the history
  • Loading branch information
debsmita1 authored Nov 28, 2023
1 parent e71905c commit 8722056
Show file tree
Hide file tree
Showing 18 changed files with 1,038 additions and 46 deletions.
2 changes: 1 addition & 1 deletion packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"@backstage/plugin-search-backend-module-pg": "^0.5.15",
"@backstage/plugin-search-backend-node": "^1.2.10",
"@backstage/plugin-techdocs-backend": "^1.8.0",
"@janus-idp/backstage-plugin-rbac-backend": "^1.0.0",
"@janus-idp/backstage-plugin-rbac-backend": "^1.6.4",
"better-sqlite3": "^9.0.0",
"dockerode": "^4.0.0",
"express": "^4.18.2",
Expand Down
120 changes: 118 additions & 2 deletions plugins/rbac/dev/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import {
} from '@backstage/plugin-permission-react';
import { TestApiProvider } from '@backstage/test-utils';

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

import { RBACAPI, rbacApiRef } from '../src/api/RBACBackendClient';
import { RbacPage, rbacPlugin } from '../src/plugin';

class MockPermissionApi implements PermissionApi {
Expand All @@ -21,12 +24,125 @@ class MockPermissionApi implements PermissionApi {
}
}

const mockApi = new MockPermissionApi({ result: 'ALLOW' });
class MockRBACApi implements RBACAPI {
readonly resources;

constructor(fixtureData: Role[]) {
this.resources = fixtureData;
}
async getRoles(): Promise<Role[]> {
return this.resources;
}
async getPolicies(): Promise<RoleBasedPolicy[]> {
return [
{
entityReference: 'role:default/guests',
permission: 'catalog-entity',
policy: 'read',
effect: 'deny',
},
{
entityReference: 'role:default/guests',
permission: 'catalog.entity.create',
policy: 'use',
effect: 'deny',
},
{
entityReference: 'role:default/guests',
permission: 'catalog-entity',
policy: 'read',
effect: 'allow',
},
{
entityReference: 'role:default/guests',
permission: 'catalog.entity.create',
policy: 'use',
effect: 'allow',
},
{
entityReference: 'role:default/guests',
permission: 'policy-entity',
policy: 'create',
effect: 'allow',
},
{
entityReference: 'role:default/guests',
permission: 'policy-entity',
policy: 'read',
effect: 'allow',
},
{
entityReference: 'role:default/guests',
permission: 'policy.entity.read',
policy: 'use',
effect: 'allow',
},
{
entityReference: 'role:default/guests',
permission: 'policy-entity',
policy: 'delete',
effect: 'allow',
},
{
entityReference: 'role:default/rbac_admin',
permission: 'policy-entity',
policy: 'read',
effect: 'allow',
},
{
entityReference: 'role:default/rbac_admin',
permission: 'policy-entity',
policy: 'create',
effect: 'allow',
},
{
entityReference: 'role:default/rbac_admin',
permission: 'policy-entity',
policy: 'delete',
effect: 'allow',
},
{
entityReference: 'role:default/rbac_admin',
permission: 'policy-entity',
policy: 'update',
effect: 'allow',
},
];
}

async getUserAuthorization(): Promise<{ status: string }> {
return {
status: 'Authorized',
};
}

async deleteRole(_roleName: string): Promise<any> {
return { status: 204 };
}
}

const mockPermissionApi = new MockPermissionApi({ result: 'ALLOW' });
const mockRBACApi = new MockRBACApi([
{
memberReferences: ['user:default/guest'],
name: 'role:default/guests',
},
{
memberReferences: ['user:default/xyz', 'group:default/admins'],
name: 'role:default/rbac_admin',
},
]);

createDevApp()
.registerPlugin(rbacPlugin)
.addPage({
element: (
<TestApiProvider apis={[[permissionApiRef, mockApi]]}>
<TestApiProvider
apis={[
[permissionApiRef, mockPermissionApi],
[rbacApiRef, mockRBACApi],
]}
>
<RbacPage />
</TestApiProvider>
),
Expand Down
10 changes: 6 additions & 4 deletions plugins/rbac/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,18 @@
"tsc": "tsc"
},
"dependencies": {
"@backstage/catalog-model": "^1.4.3",
"@backstage/core-components": "^0.13.6",
"@backstage/core-plugin-api": "^1.7.0",
"@backstage/plugin-permission-react": "^0.4.16",
"@backstage/theme": "^0.4.3",
"@janus-idp/backstage-plugin-rbac-common": "1.1.0",
"@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.11.3",
"@material-ui/lab": "^4.0.0-alpha.45",
"react-use": "^17.4.0",
"@backstage/plugin-permission-react": "^0.4.16",
"@janus-idp/backstage-plugin-rbac-common": "1.1.0",
"@mui/icons-material": "5.14.11"
"@mui/icons-material": "5.14.11",
"@mui/material": "^5.14.18",
"react-use": "^17.4.0"
},
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0"
Expand Down
38 changes: 37 additions & 1 deletion plugins/rbac/src/api/RBACBackendClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import {
IdentityApi,
} from '@backstage/core-plugin-api';

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

// @public
export type RBACAPI = {
getUserAuthorization: () => Promise<{ status: string }>;
getRoles: () => Promise<any>;
getRoles: () => Promise<Role[]>;
getPolicies: () => Promise<RoleBasedPolicy[]>;
deleteRole: (role: string) => Promise<Response>;
};

export type Options = {
Expand Down Expand Up @@ -51,4 +55,36 @@ export class RBACBackendClient implements RBACAPI {
});
return jsonResponse.json();
}

async getPolicies() {
const { token: idToken } = await this.identityApi.getCredentials();
const backendUrl = this.configApi.getString('backend.baseUrl');
const jsonResponse = await fetch(`${backendUrl}/api/permission/policies`, {
headers: {
...(idToken && { Authorization: `Bearer ${idToken}` }),
},
});
return jsonResponse.json();
}

async deleteRole(role: string) {
const { token: idToken } = await this.identityApi.getCredentials();
const backendUrl = this.configApi.getString('backend.baseUrl');
const str = role.split(':');
const kind = str[0];
const namespace = str[1].split('/')[0];
const name = str[1].split('/')[1];
const jsonResponse = await fetch(
`${backendUrl}/api/permission/roles/${kind}/${namespace}/${name}`,
{
headers: {
...(idToken && { Authorization: `Bearer ${idToken}` }),
'Content-Type': 'application/json',
Accept: 'application/json',
},
method: 'DELETE',
},
);
return jsonResponse;
}
}
31 changes: 31 additions & 0 deletions plugins/rbac/src/components/DeleteDialogContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, { createContext, useContext } from 'react';

type DeleteDialogContextType = {
deleteRoleName: string;
setDeleteRoleName: (name: string) => void;
openDialog: boolean;
setOpenDialog: (open: boolean) => void;
};

export const DeleteDialogContext = createContext<DeleteDialogContextType>({
deleteRoleName: '',
setDeleteRoleName: () => {},
openDialog: false,
setOpenDialog: () => {},
});

export const DeleteDialogContextProvider = (props: any) => {
const [openDialog, setOpenDialog] = React.useState(false);
const [deleteRoleName, setDeleteRoleName] = React.useState('');

const deleteDialogContextProviderValue = React.useMemo(
() => ({ openDialog, setOpenDialog, deleteRoleName, setDeleteRoleName }),
[openDialog, setOpenDialog, deleteRoleName, setDeleteRoleName],
);
return (
<DeleteDialogContext.Provider value={deleteDialogContextProviderValue}>
{props.children}
</DeleteDialogContext.Provider>
);
};
export const useDeleteDialog = () => useContext(DeleteDialogContext);
43 changes: 43 additions & 0 deletions plugins/rbac/src/components/DeleteRole.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';

import { IconButton, Tooltip } from '@material-ui/core';
import Delete from '@mui/icons-material/Delete';

import { useDeleteDialog } from './DeleteDialogContext';

type DeleteRoleProps = {
roleName: string;
disable: boolean;
tooltip?: string;
dataTestId: string;
};

const DeleteRole = ({
roleName,
tooltip,
disable,
dataTestId,
}: DeleteRoleProps) => {
const { setDeleteRoleName, setOpenDialog } = useDeleteDialog();

const openDialog = (name: string) => {
setDeleteRoleName(name);
setOpenDialog(true);
};

return (
<Tooltip title={tooltip || ''}>
<span data-testid={dataTestId}>
<IconButton
onClick={() => openDialog(roleName)}
aria-label="Delete"
disabled={disable}
title={tooltip || 'Delete Role'}
>
<Delete />
</IconButton>
</span>
</Tooltip>
);
};
export default DeleteRole;
Loading

0 comments on commit 8722056

Please sign in to comment.