From 86893c7009a0d2f9ed6cc1390feaef242fd5edb0 Mon Sep 17 00:00:00 2001 From: debsmita1 Date: Mon, 20 Nov 2023 15:19:53 +0530 Subject: [PATCH] feat(rbac): role overview --- plugins/rbac-common/src/types.ts | 5 + plugins/rbac/dev/index.tsx | 357 +++++++++++++++++- plugins/rbac/package.json | 4 +- plugins/rbac/src/api/RBACBackendClient.ts | 64 +++- plugins/rbac/src/components/AboutCard.tsx | 69 ++++ .../rbac/src/components/DeleteRoleDialog.tsx | 4 +- .../rbac/src/components/MembersCard.test.tsx | 86 +++++ plugins/rbac/src/components/MembersCard.tsx | 61 +++ .../src/components/MembersListColumns.tsx | 48 +++ .../src/components/PermissionCard.test.tsx | 65 ++++ .../rbac/src/components/PermissionsCard.tsx | 65 ++++ .../src/components/PermissionsListColumns.tsx | 33 ++ .../rbac/src/components/RoleOverviewPage.tsx | 35 ++ .../rbac/src/components/RolesList.test.tsx | 10 +- plugins/rbac/src/components/RolesList.tsx | 13 +- .../rbac/src/components/RolesListColumns.tsx | 5 +- plugins/rbac/src/components/Router.tsx | 17 + plugins/rbac/src/components/index.ts | 1 + plugins/rbac/src/hooks/useMembers.ts | 54 +++ .../rbac/src/hooks/usePermissionPolicies.ts | 33 ++ plugins/rbac/src/plugin.ts | 5 +- plugins/rbac/src/routes.ts | 8 +- plugins/rbac/src/types.ts | 22 ++ plugins/rbac/src/utils/rbac-utils.test.ts | 149 +++++++- plugins/rbac/src/utils/rbac-utils.ts | 141 ++++++- 25 files changed, 1301 insertions(+), 53 deletions(-) create mode 100644 plugins/rbac/src/components/AboutCard.tsx create mode 100644 plugins/rbac/src/components/MembersCard.test.tsx create mode 100644 plugins/rbac/src/components/MembersCard.tsx create mode 100644 plugins/rbac/src/components/MembersListColumns.tsx create mode 100644 plugins/rbac/src/components/PermissionCard.test.tsx create mode 100644 plugins/rbac/src/components/PermissionsCard.tsx create mode 100644 plugins/rbac/src/components/PermissionsListColumns.tsx create mode 100644 plugins/rbac/src/components/RoleOverviewPage.tsx create mode 100644 plugins/rbac/src/components/Router.tsx create mode 100644 plugins/rbac/src/hooks/useMembers.ts create mode 100644 plugins/rbac/src/hooks/usePermissionPolicies.ts diff --git a/plugins/rbac-common/src/types.ts b/plugins/rbac-common/src/types.ts index b4067159203..b4fbba82720 100644 --- a/plugins/rbac-common/src/types.ts +++ b/plugins/rbac-common/src/types.ts @@ -17,3 +17,8 @@ export type UpdatePolicy = { oldPolicy: Policy; newPolicy: Policy; }; + +export type PermissionPolicy = { + pluginId?: string; + policies?: Policy[]; +}; diff --git a/plugins/rbac/dev/index.tsx b/plugins/rbac/dev/index.tsx index b8988a4fd9f..799ede4d717 100644 --- a/plugins/rbac/dev/index.tsx +++ b/plugins/rbac/dev/index.tsx @@ -7,10 +7,16 @@ import { } from '@backstage/plugin-permission-react'; import { TestApiProvider } from '@backstage/test-utils'; -import { Role, RoleBasedPolicy } from '@janus-idp/backstage-plugin-rbac-common'; +import { + PermissionPolicy, + Policy, + Role, + RoleBasedPolicy, +} from '@janus-idp/backstage-plugin-rbac-common'; import { RBACAPI, rbacApiRef } from '../src/api/RBACBackendClient'; import { RbacPage, rbacPlugin } from '../src/plugin'; +import { MemberEntity } from '../src/types'; class MockPermissionApi implements PermissionApi { readonly result; @@ -116,9 +122,358 @@ class MockRBACApi implements RBACAPI { }; } + async getRole(role: string): Promise { + const roleresource = this.resources.find(res => res.name === role); + return roleresource ? [roleresource] : []; + } + async deleteRole(_roleName: string): Promise { return { status: 204 }; } + + async getMembers(): Promise { + return [ + { + metadata: { + namespace: 'default', + annotations: {}, + name: 'team-d', + description: 'Team D', + }, + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + spec: { + type: 'team', + profile: { + displayName: 'Team D', + }, + parent: 'boxoffice', + children: [], + }, + relations: [ + { + type: 'childOf', + targetRef: 'group:default/boxoffice', + }, + { + type: 'hasMember', + targetRef: 'user:default/eva.macdowell', + }, + { + type: 'hasMember', + targetRef: 'user:default/lucy.sheehan', + }, + ], + }, + { + metadata: { + namespace: 'default', + annotations: {}, + name: 'infrastructure', + description: 'The infra department', + }, + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + spec: { + type: 'department', + parent: 'acme-corp', + children: ['backstage', 'boxoffice'], + }, + relations: [], + }, + { + metadata: { + namespace: 'default', + annotations: {}, + name: 'guest', + }, + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + spec: { + profile: { + displayName: 'Guest User', + }, + memberOf: ['team-a'], + }, + relations: [ + { + type: 'memberOf', + targetRef: 'group:default/team-a', + }, + ], + }, + { + metadata: { + namespace: 'default', + annotations: {}, + name: 'janus-authors', + title: 'Janus-IDP Authors', + }, + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + spec: { + type: 'team', + children: [], + }, + relations: [], + }, + { + metadata: { + namespace: 'default', + annotations: {}, + name: 'team-a', + description: 'Team A', + }, + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + spec: { + type: 'team', + profile: {}, + parent: 'backstage', + children: [], + }, + relations: [ + { + type: 'childOf', + targetRef: 'group:default/backstage', + }, + { + type: 'hasMember', + targetRef: 'user:default/breanna.davison', + }, + { + type: 'hasMember', + targetRef: 'user:default/guest', + }, + { + type: 'hasMember', + targetRef: 'user:default/janelle.dawe', + }, + { + type: 'hasMember', + targetRef: 'user:default/nigel.manning', + }, + ], + }, + { + metadata: { + namespace: 'default', + annotations: {}, + name: 'backstage', + description: 'The backstage sub-department', + }, + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + spec: { + type: 'sub-department', + profile: { + displayName: 'Backstage', + }, + parent: 'infrastructure', + children: ['team-a', 'team-b'], + }, + relations: [], + }, + { + metadata: { + namespace: 'default', + annotations: {}, + name: 'team-b', + description: 'Team B', + }, + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + spec: { + type: 'team', + profile: { + displayName: 'Team B', + }, + parent: 'backstage', + children: [], + }, + relations: [ + { + type: 'hasMember', + targetRef: 'user:default/amelia.park', + }, + { + type: 'hasMember', + targetRef: 'user:default/colette.brock', + }, + { + type: 'hasMember', + targetRef: 'user:default/jenny.doe', + }, + { + type: 'hasMember', + targetRef: 'user:default/jonathon.page', + }, + { + type: 'hasMember', + targetRef: 'user:default/justine.barrow', + }, + ], + }, + { + metadata: { + namespace: 'default', + annotations: {}, + name: 'lucy.sheehan', + }, + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + spec: { + profile: { + displayName: 'Lucy Sheehan', + }, + memberOf: ['team-d'], + }, + relations: [ + { + type: 'memberOf', + targetRef: 'group:default/team-d', + }, + ], + }, + { + metadata: { + namespace: 'default', + annotations: {}, + name: 'boxoffice', + description: 'The boxoffice sub-department', + }, + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + spec: { + type: 'sub-department', + profile: { + displayName: 'Box Office', + }, + parent: 'infrastructure', + children: ['team-c', 'team-d'], + }, + relations: [ + { + type: 'childOf', + targetRef: 'group:default/infrastructure', + }, + { + type: 'parentOf', + targetRef: 'group:default/team-c', + }, + { + type: 'parentOf', + targetRef: 'group:default/team-d', + }, + ], + }, + { + metadata: { + namespace: 'default', + annotations: {}, + name: 'amelia.park', + }, + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + spec: { + profile: { + displayName: 'Amelia Park', + }, + memberOf: ['team-b'], + }, + relations: [ + { + type: 'memberOf', + targetRef: 'group:default/team-b', + }, + ], + }, + ]; + } + + async listPermissions(): Promise { + return [ + { + pluginId: 'catalog', + policies: [ + { + permission: 'catalog-entity', + policy: 'read', + }, + { + permission: 'catalog.entity.create', + policy: 'create', + }, + { + permission: 'catalog-entity', + policy: 'delete', + }, + { + permission: 'catalog-entity', + policy: 'update', + }, + { + permission: 'catalog.location.read', + policy: 'read', + }, + { + permission: 'catalog.location.create', + policy: 'create', + }, + { + permission: 'catalog.location.delete', + policy: 'delete', + }, + ], + }, + { + pluginId: 'scaffolder', + policies: [ + { + permission: 'scaffolder-template', + policy: 'read', + }, + { + permission: 'scaffolder-template', + policy: 'read', + }, + { + permission: 'scaffolder-action', + policy: 'use', + }, + ], + }, + { + pluginId: 'permission', + policies: [ + { + permission: 'policy-entity', + policy: 'read', + }, + { + permission: 'policy-entity', + policy: 'create', + }, + { + permission: 'policy-entity', + policy: 'delete', + }, + { + permission: 'policy-entity', + policy: 'update', + }, + ], + }, + ]; + } + + async deletePolicy( + _entityRef: string, + _permission: string, + _policies: Policy[], + ): Promise { + return 204; + } } const mockPermissionApi = new MockPermissionApi({ result: 'ALLOW' }); diff --git a/plugins/rbac/package.json b/plugins/rbac/package.json index ee9f5bcc68d..a13ab15f4cd 100644 --- a/plugins/rbac/package.json +++ b/plugins/rbac/package.json @@ -28,6 +28,7 @@ "@backstage/catalog-model": "^1.4.3", "@backstage/core-components": "^0.13.6", "@backstage/core-plugin-api": "^1.7.0", + "@backstage/plugin-catalog": "^1.15.1", "@backstage/plugin-permission-react": "^0.4.16", "@backstage/theme": "^0.4.3", "@janus-idp/backstage-plugin-rbac-common": "1.1.0", @@ -39,7 +40,8 @@ "react-use": "^17.4.0" }, "peerDependencies": { - "react": "^16.13.1 || ^17.0.0" + "react": "^16.13.1 || ^17.0.0", + "react-router-dom": "^6.20.0" }, "devDependencies": { "@backstage/cli": "0.23.0", diff --git a/plugins/rbac/src/api/RBACBackendClient.ts b/plugins/rbac/src/api/RBACBackendClient.ts index 508e8b11190..1d7bd6a6658 100644 --- a/plugins/rbac/src/api/RBACBackendClient.ts +++ b/plugins/rbac/src/api/RBACBackendClient.ts @@ -4,7 +4,14 @@ import { IdentityApi, } from '@backstage/core-plugin-api'; -import { Role, RoleBasedPolicy } from '@janus-idp/backstage-plugin-rbac-common'; +import { + PermissionPolicy, + Role, + RoleBasedPolicy, +} from '@janus-idp/backstage-plugin-rbac-common'; + +import { MemberEntity } from '../types'; +import { getKindNamespaceName } from '../utils/rbac-utils'; // @public export type RBACAPI = { @@ -12,6 +19,9 @@ export type RBACAPI = { getRoles: () => Promise; getPolicies: () => Promise; deleteRole: (role: string) => Promise; + getRole: (role: string) => Promise; + getMembers: () => Promise; + listPermissions: () => Promise; }; export type Options = { @@ -70,21 +80,63 @@ export class RBACBackendClient implements RBACAPI { 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 { kind, namespace, name } = getKindNamespaceName(role); 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; } + + async getRole(role: string) { + const { token: idToken } = await this.identityApi.getCredentials(); + const backendUrl = this.configApi.getString('backend.baseUrl'); + const { kind, namespace, name } = getKindNamespaceName(role); + const jsonResponse = await fetch( + `${backendUrl}/api/permission/roles/${kind}/${namespace}/${name}`, + { + headers: { + ...(idToken && { Authorization: `Bearer ${idToken}` }), + 'Content-Type': 'application/json', + }, + }, + ); + return jsonResponse.json(); + } + + async getMembers() { + const { token: idToken } = await this.identityApi.getCredentials(); + const backendUrl = this.configApi.getString('backend.baseUrl'); + const jsonResponse = await fetch( + `${backendUrl}/api/catalog/entities?filter=kind=user&filter=kind=group`, + { + headers: { + ...(idToken && { Authorization: `Bearer ${idToken}` }), + 'Content-Type': 'application/json', + }, + }, + ); + return jsonResponse.json(); + } + + async listPermissions() { + const { token: idToken } = await this.identityApi.getCredentials(); + const backendUrl = this.configApi.getString('backend.baseUrl'); + const jsonResponse = await fetch( + `${backendUrl}/api/permission/plugins/policies`, + { + headers: { + ...(idToken && { Authorization: `Bearer ${idToken}` }), + 'Content-Type': 'application/json', + }, + }, + ); + return jsonResponse.json(); + } } diff --git a/plugins/rbac/src/components/AboutCard.tsx b/plugins/rbac/src/components/AboutCard.tsx new file mode 100644 index 00000000000..ab7997c20f3 --- /dev/null +++ b/plugins/rbac/src/components/AboutCard.tsx @@ -0,0 +1,69 @@ +import React from 'react'; + +import { MarkdownContent } from '@backstage/core-components'; +import { AboutField } from '@backstage/plugin-catalog'; + +import { + Card, + CardContent, + CardHeader, + Grid, + makeStyles, +} from '@material-ui/core'; + +const useStyles = makeStyles({ + gridItemCard: { + display: 'flex', + flexDirection: 'column', + height: 'calc(100% - 10px)', // for pages without content header + marginBottom: '10px', + }, + fullHeightCard: { + display: 'flex', + flexDirection: 'column', + height: '100%', + }, + gridItemCardContent: { + flex: 1, + }, + fullHeightCardContent: { + flex: 1, + }, + text: { + wordBreak: 'break-word', + }, +}); + +export const AboutCard = () => { + const classes = useStyles(); + const cardClass = classes.gridItemCard; + const cardContentClass = classes.gridItemCardContent; + + return ( + + + + + + + + + + + + + + + + + ); +}; diff --git a/plugins/rbac/src/components/DeleteRoleDialog.tsx b/plugins/rbac/src/components/DeleteRoleDialog.tsx index 99f20e75ad2..f046a545489 100644 --- a/plugins/rbac/src/components/DeleteRoleDialog.tsx +++ b/plugins/rbac/src/components/DeleteRoleDialog.tsx @@ -133,9 +133,8 @@ const DeleteRoleDialog = ({ >{`${propOptions.permissions} permission policies`}{' '} specified in this role.
-
-