Skip to content

Commit

Permalink
feat(rbac): cleanup policies when a role is deleted (#1018)
Browse files Browse the repository at this point in the history
  • Loading branch information
debsmita1 authored Dec 20, 2023
1 parent 9cd3936 commit fb0ee8c
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 26 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.6.4",
"@janus-idp/backstage-plugin-rbac-backend": "^2.0.0",
"better-sqlite3": "^9.0.0",
"dockerode": "^4.0.0",
"express": "^4.18.2",
Expand Down
19 changes: 11 additions & 8 deletions plugins/rbac/dev/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,16 @@ class MockRBACApi implements RBACAPI {
return this.resources;
}

async getPolicies(): Promise<RoleBasedPolicy[]> {
return mockPolicies;
}

async getAssociatedPolicies(
entityReference: string,
): Promise<RoleBasedPolicy[]> {
return mockPolicies.filter(pol => pol.entityReference === entityReference);
}

async getPolicies(): Promise<RoleBasedPolicy[]> {
return mockPolicies;
}

async getUserAuthorization(): Promise<{ status: string }> {
return {
status: 'Authorized',
Expand Down Expand Up @@ -90,12 +90,15 @@ class MockRBACApi implements RBACAPI {
return mockPermissionPolicies;
}

async deletePolicy(
async deletePolicies(
_entityRef: string,
_permission: string,
_policies: Policy[],
): Promise<number> {
return 204;
): Promise<Response> {
return {
ok: true,
status: 204,
statusText: 'Deleted Successfully',
} as Response;
}

async createRole(_role: Role): Promise<Response> {
Expand Down
21 changes: 21 additions & 0 deletions plugins/rbac/src/api/RBACBackendClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {

import {
PermissionPolicy,
Policy,
Role,
RoleBasedPolicy,
} from '@janus-idp/backstage-plugin-rbac-common';
Expand All @@ -22,6 +23,7 @@ export type RBACAPI = {
entityReference: string,
) => Promise<RoleBasedPolicy[] | Response>;
deleteRole: (role: string) => Promise<Response>;
deletePolicies: (role: string, policy: Policy[]) => Promise<Response>;
getRole: (role: string) => Promise<Role[] | Response>;
getMembers: () => Promise<MemberEntity[] | Response>;
listPermissions: () => Promise<PermissionPolicy[]>;
Expand Down Expand Up @@ -247,4 +249,23 @@ export class RBACBackendClient implements RBACAPI {
}
return jsonResponse;
}

async deletePolicies(entityReference: string, policies: Policy[]) {
const { token: idToken } = await this.identityApi.getCredentials();
const backendUrl = this.configApi.getString('backend.baseUrl');
const { kind, namespace, name } = getKindNamespaceName(entityReference);
const jsonResponse = await fetch(
`${backendUrl}/api/permission/policies/${kind}/${namespace}/${name}`,
{
headers: {
...(idToken && { Authorization: `Bearer ${idToken}` }),
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify(policies),
method: 'DELETE',
},
);
return jsonResponse;
}
}
2 changes: 1 addition & 1 deletion plugins/rbac/src/components/DeleteRole.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const DeleteRole = ({
<Tooltip title={tooltip || ''}>
<span data-testid={dataTestId}>
<IconButton
color="primary"
color="inherit"
onClick={() => openDialog(roleName)}
aria-label="Delete"
disabled={disable}
Expand Down
26 changes: 24 additions & 2 deletions plugins/rbac/src/components/DeleteRoleDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import CloseIcon from '@material-ui/icons/Close';
import ErrorIcon from '@material-ui/icons/Error';
import { Alert } from '@material-ui/lab';

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

import { rbacApiRef } from '../api/RBACBackendClient';
import { getMembers } from '../utils/rbac-utils';
import { useToast } from './ToastContext';
Expand Down Expand Up @@ -65,12 +67,32 @@ const DeleteRoleDialog = ({

const deleteRole = async () => {
try {
const policies = await rbacApi.getAssociatedPolicies(roleName);
let cleanupPoliciesResponse;
if (Array.isArray(policies)) {
const allowedPolicies = policies.filter(
(policy: RoleBasedPolicy) => policy.effect !== 'deny',
);
if (allowedPolicies.length > 0) {
cleanupPoliciesResponse = await rbacApi.deletePolicies(
roleName,
allowedPolicies,
);
}
if (cleanupPoliciesResponse && !cleanupPoliciesResponse.ok) {
setError(
`Unable to delete Policies. ${cleanupPoliciesResponse.statusText}`,
);
return;
}
}

const response = await rbacApi.deleteRole(roleName);
if (response.status === 200 || response.status === 204) {
setToastMessage(`Role ${roleName} deleted successfully`);
closeDialog();
} else {
setError(response.statusText);
setError(`Unable to delete the role. ${response.statusText}`);
}
} catch (err) {
setError(err instanceof Error ? err.message : `${err}`);
Expand Down Expand Up @@ -147,7 +169,7 @@ const DeleteRoleDialog = ({
</DialogContent>
{error && (
<Box maxWidth="650px" marginLeft="20px">
<Alert severity="error">{`Unable to delete. ${error}`}</Alert>
<Alert severity="error">{error}</Alert>
</Box>
)}
<DialogActions
Expand Down
1 change: 1 addition & 0 deletions plugins/rbac/src/components/EditRole.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const EditRole = ({
<Tooltip title={tooltip || ''}>
<span data-testid={dataTestId}>
<IconButton
color="inherit"
component={Link}
aria-label="Update"
disabled={disable}
Expand Down
2 changes: 1 addition & 1 deletion plugins/rbac/src/components/RbacPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe('RbacPage', () => {
mockUseRoles.mockReturnValue({
loading: true,
data: [],
retry: () => {},
retry: jest.fn(),
createRoleAllowed: false,
});
await renderInTestApp(<RbacPage />);
Expand Down
14 changes: 7 additions & 7 deletions plugins/rbac/src/components/RolesList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ describe('RolesList', () => {
mockUseRoles.mockReturnValue({
loading: false,
data: useRolesMockData,
retry: () => {},
retry: jest.fn(),
createRoleAllowed: false,
});
const { queryByText } = await renderInTestApp(<RolesList />);
Expand All @@ -79,7 +79,7 @@ describe('RolesList', () => {
mockUseRoles.mockReturnValue({
loading: false,
data: [],
retry: () => {},
retry: jest.fn(),
createRoleAllowed: false,
});
const { getByTestId } = await renderInTestApp(<RolesList />);
Expand All @@ -94,7 +94,7 @@ describe('RolesList', () => {
mockUseRoles.mockReturnValue({
loading: false,
data: useRolesMockData,
retry: () => {},
retry: jest.fn(),
createRoleAllowed: false,
});
const { getAllByTestId, getByText } = await renderInTestApp(<RolesList />);
Expand Down Expand Up @@ -125,7 +125,7 @@ describe('RolesList', () => {
},
},
],
retry: () => {},
retry: jest.fn(),
createRoleAllowed: false,
});
const { getAllByTestId } = await renderInTestApp(<RolesList />);
Expand Down Expand Up @@ -156,7 +156,7 @@ describe('RolesList', () => {
},
},
],
retry: () => {},
retry: jest.fn(),
createRoleAllowed: true,
});
const { getAllByTestId } = await renderInTestApp(<RolesList />);
Expand All @@ -170,7 +170,7 @@ describe('RolesList', () => {
mockUseRoles.mockReturnValue({
loading: false,
data: useRolesMockData,
retry: () => {},
retry: jest.fn(),
createRoleAllowed: false,
});
const { getByTestId } = await renderInTestApp(<RolesList />);
Expand All @@ -186,7 +186,7 @@ describe('RolesList', () => {
mockUseRoles.mockReturnValue({
loading: false,
data: useRolesMockData,
retry: () => {},
retry: jest.fn(),
createRoleAllowed: true,
});
const { getByTestId } = await renderInTestApp(<RolesList />);
Expand Down
29 changes: 23 additions & 6 deletions plugins/rbac/src/hooks/useRoles.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { useAsync, useAsyncRetry, useInterval } from 'react-use';
import { useAsyncRetry, useInterval } from 'react-use';

import { useApi } from '@backstage/core-plugin-api';
import { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/alpha';
Expand All @@ -17,15 +17,22 @@ import { rbacApiRef } from '../api/RBACBackendClient';
import { RolesData } from '../types';
import { getPermissions } from '../utils/rbac-utils';

export const useRoles = (pollInterval?: number) => {
export const useRoles = (
pollInterval?: number,
): {
loading: boolean;
data: RolesData[];
createRoleAllowed: boolean;
retry: () => void;
} => {
const rbacApi = useApi(rbacApiRef);
const {
loading: rolesLoading,
value: roles,
retry,
retry: roleRetry,
} = useAsyncRetry(async () => await rbacApi.getRoles());

const { loading: policiesLoading, value: policies } = useAsync(
const { loading: policiesLoading, value: policies } = useAsyncRetry(
async () => await rbacApi.getPolicies(),
[],
);
Expand Down Expand Up @@ -83,7 +90,17 @@ export const useRoles = (pollInterval?: number) => {
[roles, policies, deletePermissionResult, editPermissionResult],
);
const loading = rolesLoading && policiesLoading;
useInterval(() => retry(), loading ? null : pollInterval || 5000);
useInterval(
() => {
roleRetry();
},
loading ? null : pollInterval || 10000,
);

return { loading, data, retry, createRoleAllowed };
return {
loading,
data,
createRoleAllowed,
retry: roleRetry,
};
};

0 comments on commit fb0ee8c

Please sign in to comment.